/*
 * Licensed to The OpenNMS Group, Inc (TOG) under one or more
 * contributor license agreements.  See the LICENSE.md file
 * distributed with this work for additional information
 * regarding copyright ownership.
 *
 * TOG licenses this file to You under the GNU Affero General
 * Public License Version 3 (the "License") or (at your option)
 * any later version.  You may not use this file except in
 * compliance with the License.  You may obtain a copy of the
 * License at:
 *
 *      https://www.gnu.org/licenses/agpl-3.0.txt
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied.  See the License for the specific
 * language governing permissions and limitations under the
 * License.
 */
package org.opennms.netmgt.collectd;

import java.net.InetAddress;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.opennms.netmgt.collection.api.AttributeGroupType;
import org.opennms.netmgt.collection.api.CollectionResource;
import org.opennms.netmgt.collection.api.ServiceParameters;
import org.opennms.netmgt.config.DataCollectionConfigFactory;
import org.opennms.netmgt.config.api.DataCollectionConfigDao;
import org.opennms.netmgt.config.datacollection.MibObject;
import org.opennms.netmgt.snmp.proxy.LocationAwareSnmpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Represents SNMP collection data for a single collection period.
 * It is particularly used to create a CollectionSet for a specific
 * remote agent with {@link #createCollectionSet} and to provide
 * data to CollectionSet and other classes that are created during
 * collection.
 *
 * @author ranger
 * @version $Id: $
 */
public class OnmsSnmpCollection {
    
    private static final Logger LOG = LoggerFactory.getLogger(OnmsSnmpCollection.class);

    private final LocationAwareSnmpClient m_client;
    private final ServiceParameters m_params;
    private NodeResourceType m_nodeResourceType;
    private IfResourceType m_ifResourceType;
    private IfAliasResourceType m_ifAliasResourceType;
    private Map<String, ResourceType> m_genericIndexResourceTypes;
    private DataCollectionConfigDao m_dataCollectionConfigDao;
    private List<SnmpAttributeType> m_nodeAttributeTypes;
    private List<SnmpAttributeType> m_indexedAttributeTypes;
    private List<SnmpAttributeType> m_aliasAttributeTypes;

    /**
     * <p>Constructor for OnmsSnmpCollection.</p>
     *
     * @param agent a {@link org.opennms.netmgt.collection.api.CollectionAgent} object.
     * @param params a {@link org.opennms.netmgt.collection.api.ServiceParameters} object.
     */
    public OnmsSnmpCollection(SnmpCollectionAgent agent, ServiceParameters params, LocationAwareSnmpClient client) {
        this(agent, params, null, client);
    }

    /**
     * <p>Constructor for OnmsSnmpCollection.</p>
     *
     * @param agent a {@link org.opennms.netmgt.collection.api.CollectionAgent} object.
     * @param params a {@link org.opennms.netmgt.collection.api.ServiceParameters} object.
     * @param config a {@link org.opennms.netmgt.config.api.DataCollectionConfigDao} object.
     */
    public OnmsSnmpCollection(SnmpCollectionAgent agent, ServiceParameters params, DataCollectionConfigDao config, LocationAwareSnmpClient client) {
        setDataCollectionConfigDao(config);
        m_params = params;
        m_client = Objects.requireNonNull(client);

        if (Boolean.getBoolean("org.opennms.netmgt.collectd.OnmsSnmpCollection.loadResourceTypesInInit")) {
            getResourceTypes(agent);
        }
    }

    /**
     * <p>getServiceParameters</p>
     *
     * @return a {@link org.opennms.netmgt.collection.api.ServiceParameters} object.
     */
    public ServiceParameters getServiceParameters() {
        return m_params;
    }

    /**
     * <p>getName</p>
     *
     * @return a {@link java.lang.String} object.
     */
    public String getName() {
        return m_params.getCollectionName();
    }

    /**
     * <p>getSnmpPort</p>
     *
     * @param current a int.
     * @return a int.
     */
    public int getSnmpPort(int current) {
        return m_params.getSnmpPort(current);
    }

    /**
     * <p>getSnmpRetries</p>
     *
     * @param current a int.
     * @return a int.
     */
    public int getSnmpRetries(int current) {
        return m_params.getSnmpRetries(current);
    }

    /**
     * <p>getSnmpTimeout</p>
     *
     * @param current a int.
     * @return a int.
     */
    public int getSnmpTimeout(int current) {
        return m_params.getSnmpTimeout(current);
    }

    /**
     * <p>getSnmpReadCommunity</p>
     *
     * @param current a {@link java.lang.String} object.
     * @return a {@link java.lang.String} object.
     */
    public String getSnmpReadCommunity(String current) {
        return m_params.getSnmpReadCommunity(current);
    }

    /**
     * <p>getSnmpWriteCommunity</p>
     *
     * @param current a {@link java.lang.String} object.
     * @return a {@link java.lang.String} object.
     */
    public String getSnmpWriteCommunity(String current) {
        return m_params.getSnmpWriteCommunity(current);
    }

    /**
     * <p>getSnmpProxyFor</p>
     *
     * @param current a {@link java.net.InetAddress} object.
     * @return a {@link java.net.InetAddress} object.
     */
    public InetAddress getSnmpProxyFor(InetAddress current) {
        return m_params.getSnmpProxyFor(current);
    }

    /**
     * <p>getSnmpVersion</p>
     *
     * @param current a int.
     * @return a int.
     */
    public int getSnmpVersion(int current) {
        return m_params.getSnmpVersion(current);
    }

    /**
     * <p>getSnmpMaxVarsPerPdu</p>
     *
     * @param current a int.
     * @return a int.
     */
    public int getSnmpMaxVarsPerPdu(int current) {
        return m_params.getSnmpMaxVarsPerPdu(current);
    }

    /**
     * <p>getSnmpMaxRepetitions</p>
     *
     * @param current a int.
     * @return a int.
     */
    public int getSnmpMaxRepetitions(int current) {
        return m_params.getSnmpMaxRepetitions(current);
    }

    /**
     * <p>getSnmpMaxRequestSize</p>
     *
     * @param current a int.
     * @return a int.
     */
    public int getSnmpMaxRequestSize(int current) {
        return m_params.getSnmpMaxRequestSize(current);
    }

    /**
     * <p>getSnmpSecurityName</p>
     *
     * @param current a {@link java.lang.String} object.
     * @return a {@link java.lang.String} object.
     */
    public String getSnmpSecurityName(String current) {
        return m_params.getSnmpSecurityName(current);
    }

    /**
     * <p>getSnmpAuthPassPhrase</p>
     *
     * @param current a {@link java.lang.String} object.
     * @return a {@link java.lang.String} object.
     */
    public String getSnmpAuthPassPhrase(String current) {
        return m_params.getSnmpAuthPassPhrase(current);
    }

    /**
     * <p>getSnmpAuthProtocol</p>
     *
     * @param current a {@link java.lang.String} object.
     * @return a {@link java.lang.String} object.
     */
    public String getSnmpAuthProtocol(String current) {
        return m_params.getSnmpAuthProtocol(current);
    }

    /**
     * <p>getSnmpPrivPassPhrase</p>
     *
     * @param current a {@link java.lang.String} object.
     * @return a {@link java.lang.String} object.
     */
    public String getSnmpPrivPassPhrase(String current) {
        return m_params.getSnmpPrivPassPhrase(current);
    }

    /**
     * <p>getSnmpPrivProtocol</p>
     *
     * @param current a {@link java.lang.String} object.
     * @return a {@link java.lang.String} object.
     */
    public String getSnmpPrivProtocol(String current) {
        return m_params.getSnmpPrivProtocol(current);
    }

    private DataCollectionConfigDao getDataCollectionConfigDao() {
        if (m_dataCollectionConfigDao == null) {
            setDataCollectionConfigDao(DataCollectionConfigFactory.getInstance());
        }
        return m_dataCollectionConfigDao;
    }

    /**
     * <p>setDataCollectionConfig</p>
     *
     * @param config a {@link org.opennms.netmgt.config.api.DataCollectionConfigDao} object.
     */
    public void setDataCollectionConfigDao(DataCollectionConfigDao config) {
        m_dataCollectionConfigDao = config;
    }

    /**
     * <p>getStorageFlag</p>
     *
     * @return a {@link java.lang.String} object.
     */
    public String getStorageFlag() {
        String collectionName = getName();
        String storageFlag = getDataCollectionConfigDao().getSnmpStorageFlag(collectionName);
        if (storageFlag == null) {
            LOG.warn("getStorageFlag: Configuration error, failed to retrieve SNMP storage flag for collection: {}", collectionName);
            storageFlag = AbstractSnmpCollector.SNMP_STORAGE_PRIMARY;
        }
        return storageFlag;
    }

    /**
     * <p>toString</p>
     *
     * @return a {@link java.lang.String} object.
     */
    @Override
    public String toString() {
        return getName();
    }

    /**
     * <p>createCollectionSet</p>
     *
     * @param agent a {@link org.opennms.netmgt.collection.api.CollectionAgent} object.
     * @return a {@link org.opennms.netmgt.collectd.SnmpCollectionSet} object.
     */
    public SnmpCollectionSet createCollectionSet(SnmpCollectionAgent agent) {
        return new SnmpCollectionSet(agent, this, m_client);
    }
    
    private List<SnmpAttributeType> getIndexedAttributeTypes(SnmpCollectionAgent agent) {
        if (m_indexedAttributeTypes == null) {
            m_indexedAttributeTypes = loadAttributeTypes(agent, DataCollectionConfigDao.ALL_IF_ATTRIBUTES);
        }
        return m_indexedAttributeTypes;
    }
    
    /**
     * <p>getIndexedAttributeTypesForResourceType</p>
     *
     * @param agent a {@link org.opennms.netmgt.collection.api.CollectionAgent} object.
     * @param resourceType a {@link org.opennms.netmgt.collectd.ResourceType} object.
     * @return a {@link java.util.List} object.
     */
    public List<SnmpAttributeType> getIndexedAttributeTypesForResourceType(SnmpCollectionAgent agent, ResourceType resourceType) {
        LinkedList<SnmpAttributeType> resAttrTypes = new LinkedList<>();
        for(SnmpAttributeType attrType : getIndexedAttributeTypes(agent)) {
            if (attrType.getResourceType().equals(resourceType)) {
                resAttrTypes.add(attrType);
            }
        }
        return resAttrTypes;
    }

    /**
     * <p>getNodeAttributeTypes</p>
     *
     * @param agent a {@link org.opennms.netmgt.collection.api.CollectionAgent} object.
     * @return a {@link java.util.List} object.
     */
    public List<SnmpAttributeType> getNodeAttributeTypes(SnmpCollectionAgent agent) {
        if (m_nodeAttributeTypes == null) {
            m_nodeAttributeTypes = loadAttributeTypes(agent, DataCollectionConfigDao.NODE_ATTRIBUTES);
        }
        return m_nodeAttributeTypes;
    }

    /**
     * <p>loadAttributeTypes</p>
     *
     * @param agent a {@link org.opennms.netmgt.collection.api.CollectionAgent} object.
     * @param ifType a int.
     * @return a {@link java.util.List} object.
     */
    public List<SnmpAttributeType> loadAttributeTypes(SnmpCollectionAgent agent, int ifType) {
        String sysObjectId = agent.getSysObjectId();
        String hostAddress = agent.getHostAddress();
        List<MibObject> oidList = getDataCollectionConfigDao().getMibObjectList(getName(), sysObjectId, hostAddress, ifType);

        Map<String, AttributeGroupType> groupTypes = new HashMap<String, AttributeGroupType>();

        List<SnmpAttributeType> typeList = new LinkedList<>();
        for (MibObject mibObject : oidList) {
            String instanceName = mibObject.getInstance();
            AttributeGroupType groupType = findGroup(groupTypes, mibObject);
            SnmpAttributeType attrType = SnmpAttributeType.create(getResourceType(agent, instanceName), getName(), mibObject, groupType);
            groupType.addAttributeType(attrType);
            typeList.add(attrType);
        }
        LOG.debug("getAttributeTypes({}, {}): {}", agent, ifType, typeList);
        return typeList;
    }

    private AttributeGroupType findGroup(Map<String, AttributeGroupType> groupTypes, MibObject mibObject) {
        AttributeGroupType groupType = groupTypes.get(mibObject.getGroupName());
        if (groupType == null) {
            groupType = new AttributeGroupType(mibObject.getGroupName(), mibObject.getGroupIfType());
            groupTypes.put(mibObject.getGroupName(), groupType);
        }
        return groupType;
    }

    /**
     * <p>getResourceType</p>
     *
     * @param agent a {@link org.opennms.netmgt.collection.api.CollectionAgent} object.
     * @param instanceName a {@link java.lang.String} object.
     * @return a {@link org.opennms.netmgt.collectd.ResourceType} object.
     */
    public ResourceType getResourceType(SnmpCollectionAgent agent, String instanceName) {
        if (MibObject.INSTANCE_IFINDEX.equals(instanceName)) {
            return getIfResourceType(agent);
        } else if (getGenericIndexResourceType(agent, instanceName) != null) {
            return getGenericIndexResourceType(agent, instanceName);
        } else {
            return getNodeResourceType(agent);
        }
    }

    /**
     * <p>getNodeResourceType</p>
     *
     * @param agent a {@link org.opennms.netmgt.collection.api.CollectionAgent} object.
     * @return a {@link org.opennms.netmgt.collectd.NodeResourceType} object.
     */
    public NodeResourceType getNodeResourceType(SnmpCollectionAgent agent) {
        if (m_nodeResourceType == null)
            m_nodeResourceType = new NodeResourceType(agent, this);
        return m_nodeResourceType;
    }

    /**
     * <p>getIfResourceType</p>
     *
     * @param agent a {@link org.opennms.netmgt.collection.api.CollectionAgent} object.
     * @return a {@link org.opennms.netmgt.collectd.IfResourceType} object.
     */
    public IfResourceType getIfResourceType(SnmpCollectionAgent agent) {
        if (m_ifResourceType == null) {
            m_ifResourceType = new IfResourceType(agent, this);
        }
        return m_ifResourceType;
    }

    /**
     * <p>getIfAliasResourceType</p>
     *
     * @param agent a {@link org.opennms.netmgt.collection.api.CollectionAgent} object.
     * @return a {@link org.opennms.netmgt.collectd.IfAliasResourceType} object.
     */
    public IfAliasResourceType getIfAliasResourceType(SnmpCollectionAgent agent) {
        if (m_ifAliasResourceType == null) {
            m_ifAliasResourceType = new IfAliasResourceType(agent, this, m_params, getIfResourceType(agent));            
        }
        return m_ifAliasResourceType;

    }
    
    /**
     * <p>getGenericIndexResourceTypes</p>
     *
     * @param agent a {@link org.opennms.netmgt.collection.api.CollectionAgent} object.
     * @return a {@link java.util.Collection} object.
     */
    public Collection<ResourceType> getGenericIndexResourceTypes(SnmpCollectionAgent agent) {
        return Collections.unmodifiableCollection(getGenericIndexResourceTypeMap(agent).values());
    }

    private Map<String, ResourceType> getGenericIndexResourceTypeMap(SnmpCollectionAgent agent) {
        if (m_genericIndexResourceTypes == null) {
            Collection<org.opennms.netmgt.config.datacollection.ResourceType> configuredResourceTypes =
                getDataCollectionConfigDao().getConfiguredResourceTypes().values();
            Map<String,ResourceType> resourceTypes = new HashMap<String,ResourceType>();
            for (org.opennms.netmgt.config.datacollection.ResourceType configuredResourceType : configuredResourceTypes) {
                try {
                    resourceTypes.put(configuredResourceType.getName(), new GenericIndexResourceType(agent, this, configuredResourceType));
                } catch (IllegalArgumentException e) {
                    LOG.warn("Ignoring resource type {} ({}) because it is not properly configured.", configuredResourceType.getLabel(), configuredResourceType.getName());
                }
            }
            m_genericIndexResourceTypes = resourceTypes;
        }
        return m_genericIndexResourceTypes;
    }
    
    private ResourceType getGenericIndexResourceType(SnmpCollectionAgent agent, String name) {
        return getGenericIndexResourceTypeMap(agent).get(name);
    }

    private Collection<ResourceType> getResourceTypes(SnmpCollectionAgent agent) {
        HashSet<ResourceType> set = new HashSet<ResourceType>(3);
        set.add(getNodeResourceType(agent));
        set.add(getIfResourceType(agent));
        set.add(getIfAliasResourceType(agent));
        set.addAll(getGenericIndexResourceTypeMap(agent).values());
        return set;
    }

    /**
     * <p>getAttributeTypes</p>
     *
     * @param agent a {@link org.opennms.netmgt.collection.api.CollectionAgent} object.
     * @return a {@link java.util.Collection} object.
     */
    public Collection<SnmpAttributeType> getAttributeTypes(SnmpCollectionAgent agent) {
        HashSet<SnmpAttributeType> set = new HashSet<>();
        for (ResourceType resourceType : getResourceTypes(agent)) {
            set.addAll(resourceType.getAttributeTypes());
        }
        return set;

    }

    /**
     * <p>getResources</p>
     *
     * @param agent a {@link org.opennms.netmgt.collection.api.CollectionAgent} object.
     * @return a {@link java.util.Collection} object.
     */
    public Collection<? extends CollectionResource> getResources(SnmpCollectionAgent agent) {
        LinkedList<CollectionResource> resources = new LinkedList<>();
        for (ResourceType resourceType : getResourceTypes(agent)) {
            resources.addAll(resourceType.getResources());
        }
        return resources;
    }

    boolean isSelectCollectionOnly() {
        if (getStorageFlag().equals(AbstractSnmpCollector.SNMP_STORAGE_PRIMARY) || getStorageFlag().equals(AbstractSnmpCollector.SNMP_STORAGE_SELECT)) {
            return true;
        }

        return false;
    }

    /**
     * <p>loadAliasAttributeTypes</p>
     *
     * @param agent a {@link org.opennms.netmgt.collection.api.CollectionAgent} object.
     * @return a {@link java.util.List} object.
     */
    public List<SnmpAttributeType> loadAliasAttributeTypes(SnmpCollectionAgent agent) {
        IfAliasResourceType resType = getIfAliasResourceType(agent);
        MibObject ifAliasMibObject = new MibObject();
        ifAliasMibObject.setOid(".1.3.6.1.2.1.31.1.1.1.18");
        ifAliasMibObject.setAlias("ifAlias");
        ifAliasMibObject.setType("string");
        ifAliasMibObject.setInstance("ifIndex");
        
        ifAliasMibObject.setGroupName("aliasedResource");
        ifAliasMibObject.setGroupIfType(AttributeGroupType.IF_TYPE_ALL);
    
        AttributeGroupType groupType = new AttributeGroupType(ifAliasMibObject.getGroupName(), ifAliasMibObject.getGroupIfType());
    
        SnmpAttributeType type = SnmpAttributeType.create(resType, resType.getCollectionName(), ifAliasMibObject, groupType);
        return Collections.singletonList(type);
    }

    /**
     * <p>getAliasAttributeTypes</p>
     *
     * @param agent a {@link org.opennms.netmgt.collection.api.CollectionAgent} object.
     * @return a {@link java.util.List} object.
     */
    public List<SnmpAttributeType> getAliasAttributeTypes(SnmpCollectionAgent agent) {
        if (m_aliasAttributeTypes == null) {
            m_aliasAttributeTypes = loadAliasAttributeTypes(agent);
        }
        return m_aliasAttributeTypes;
    }

    LocationAwareSnmpClient getClient(){
       return m_client;
    }

}
