/*
 * Copyright 2011-17 Fraunhofer ISE, energy & meteo Systems GmbH and other contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.openmuc.openiec61850;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.openmuc.openiec61850.internal.mms.asn1.Data;
import org.openmuc.openiec61850.internal.mms.asn1.Identifier;
import org.openmuc.openiec61850.internal.mms.asn1.TypeDescription;
import org.openmuc.openiec61850.internal.mms.asn1.TypeDescription.Structure;
import org.openmuc.openiec61850.internal.mms.asn1.TypeDescription.Structure.Components;
import org.openmuc.openiec61850.internal.mms.asn1.TypeSpecification;

public abstract class ModelNode implements Iterable<ModelNode> {

    protected ObjectReference objectReference;
    protected Map<String, ModelNode> children;
    ModelNode parent;

    /**
     * Returns a copy of model node with all of its children. Creates new BasicDataAttribute values but reuses
     * ObjectReferences, FunctionalConstraints.
     *
     * @return a copy of model node with all of its children.
     */
    public abstract ModelNode copy();

    /**
     * Returns the child node with the given name. Will always return null if called on a logical node because a logical
     * node need the functional constraint to uniquely identify a child. For logical nodes use
     * <code>getChild(String name, Fc fc)</code> instead.
     *
     * @param name
     *            the name of the requested child node
     * @return the child node with the given name.
     */
    public ModelNode getChild(String name) {
        return getChild(name, null);
    }

    /**
     * Returns the child node with the given name and functional constraint. The fc is ignored if this function is
     * called on any model node other than logical node.
     *
     * @param name
     *            the name of the requested child node
     * @param fc
     *            the functional constraint of the requested child node
     * @return the child node with the given name and functional constrain
     */
    public ModelNode getChild(String name, Fc fc) {
        return children.get(name);
    }

    @SuppressWarnings("unchecked")
    public Collection<ModelNode> getChildren() {
        if (children == null) {
            return null;
        }
        return (Collection<ModelNode>) ((Collection<?>) children.values());
    }

    protected Iterator<Iterator<? extends ModelNode>> getIterators() {
        List<Iterator<? extends ModelNode>> iterators = new ArrayList<>();
        if (children != null) {
            iterators.add(children.values().iterator());
        }
        return iterators.iterator();
    }

    /**
     * Returns the reference of the model node.
     *
     * @return the reference of the model node.
     */
    public ObjectReference getReference() {
        return objectReference;
    }

    /**
     * Returns the name of the model node.
     *
     * @return the name of the model node.
     */
    public String getName() {
        return objectReference.getName();
    }

    @Override
    public Iterator<ModelNode> iterator() {
        return children.values().iterator();
    }

    /**
     * Returns a list of all leaf nodes (basic data attributes) contained in the subtree of this model node.
     *
     * @return a list of all leaf nodes (basic data attributes) contained in the subtree of this model node.
     */
    public List<BasicDataAttribute> getBasicDataAttributes() {
        List<BasicDataAttribute> subBasicDataAttributes = new LinkedList<>();
        for (ModelNode child : children.values()) {
            subBasicDataAttributes.addAll(child.getBasicDataAttributes());
        }
        return subBasicDataAttributes;
    }

    @Override
    public String toString() {
        return getReference().toString();
    }

    void setParent(ModelNode parent) {
        this.parent = parent;
    }

    /**
     * Returns the parent node of this node.
     *
     * @return the parent node of this node.
     */
    public ModelNode getParent() {
        return parent;
    }

    Data getMmsDataObj() {
        return null;
    }

    void setValueFromMmsDataObj(Data data) throws ServiceError {
    }

    TypeDescription getMmsTypeSpec() {

        Components componentsSequenceType = new Components();

        List<TypeDescription.Structure.Components.SEQUENCE> structComponents = componentsSequenceType.getSEQUENCE();
        for (ModelNode child : children.values()) {
            TypeSpecification typeSpecification = new TypeSpecification();
            typeSpecification.setTypeDescription(child.getMmsTypeSpec());

            TypeDescription.Structure.Components.SEQUENCE component = new TypeDescription.Structure.Components.SEQUENCE();
            component.setComponentName(new Identifier(child.getName().getBytes()));
            component.setComponentType(typeSpecification);

            structComponents.add(component);
        }

        Structure structure = new Structure();
        structure.setComponents(componentsSequenceType);

        TypeDescription typeDescription = new TypeDescription();
        typeDescription.setStructure(structure);

        return typeDescription;
    }

}
