/*
 *  Copyright (c) 2005-2009, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  WSO2 Inc. licenses this file to you 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.wso2.carbon.governance.api.generic;

import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.wso2.carbon.governance.api.common.GovernanceArtifactManager;
import org.wso2.carbon.governance.api.common.dataobjects.GovernanceArtifact;
import org.wso2.carbon.governance.api.exception.GovernanceException;
import org.wso2.carbon.governance.api.generic.dataobjects.DetachedGenericArtifactImpl;
import org.wso2.carbon.governance.api.generic.dataobjects.GenericArtifact;
import org.wso2.carbon.governance.api.generic.dataobjects.GenericArtifactImpl;
import org.wso2.carbon.governance.api.util.GovernanceArtifactConfiguration;
import org.wso2.carbon.governance.api.util.GovernanceUtils;
import org.wso2.carbon.registry.common.TermData;
import org.wso2.carbon.registry.core.Association;
import org.wso2.carbon.registry.core.Registry;
import org.wso2.carbon.registry.core.Resource;
import org.wso2.carbon.registry.core.exceptions.RegistryException;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Manager class for a generic governance artifact.
 */
@SuppressWarnings("unused")
public class GenericArtifactManager {

    private String artifactNameAttribute;
    private String artifactNamespaceAttribute;
    private String artifactElementNamespace;
    private GovernanceArtifactManager manager;
    private String mediaType;

    /**
     * Constructor accepting an instance of the registry, and also details on the type of manager.
     *
     * @param registry                   the instance of the registry.
     * @param mediaType                  the media type of resources being saved or fetched.
     * @param artifactNameAttribute      the attribute that specifies the name of the artifact.
     * @param artifactNamespaceAttribute the attribute that specifies the namespace of the artifact.
     * @param artifactElementRoot        the attribute that specifies the root artifact element.
     * @param artifactElementNamespace   the attribute that specifies the artifact element's
     *                                   namespace.
     * @param pathExpression             the expression that can be used to compute where to store
     *                                   the artifact.
     * @param relationshipDefinitions    the relationship definitions for the types of associations
     *                                   that will be created when the artifact gets updated.
     */
    public GenericArtifactManager(Registry registry, String mediaType,
                                     String artifactNameAttribute,
                                     String artifactNamespaceAttribute, String artifactElementRoot,
                                     String artifactElementNamespace, String pathExpression,
                                     Association[] relationshipDefinitions) {
        manager = new GovernanceArtifactManager(registry, mediaType, artifactNameAttribute,
                artifactNamespaceAttribute, artifactElementRoot, artifactElementNamespace,
                pathExpression, relationshipDefinitions);
        this.artifactNameAttribute = artifactNameAttribute;
        this.artifactNamespaceAttribute = artifactNamespaceAttribute;
        this.artifactElementNamespace = artifactElementNamespace;
        this.mediaType = mediaType;
    }

    /**
     * Constructor accepting an instance of the registry, and key identifying the type of manager.
     *
     * @param registry              the instance of the registry.
     * @param key                   the key short name of the artifact type.
     * @throws RegistryException    Thrown when rxt configuration is not in registry.
     */
    public GenericArtifactManager(Registry registry, String key) throws RegistryException {
        try {
            GovernanceArtifactConfiguration configuration =
                    GovernanceUtils.findGovernanceArtifactConfiguration(key, registry);
            if (configuration != null) {
                artifactNameAttribute = configuration.getArtifactNameAttribute();
                artifactNamespaceAttribute = configuration.getArtifactNamespaceAttribute();
                artifactElementNamespace = configuration.getArtifactElementNamespace();
                manager = new GovernanceArtifactManager(registry, configuration.getMediaType(),
                        artifactNameAttribute, artifactNamespaceAttribute,
                        configuration.getArtifactElementRoot(), artifactElementNamespace,
                        configuration.getPathExpression(), configuration.getLifecycle(),
                        configuration.getValidationAttributes(), configuration.getRelationshipDefinitions());
                mediaType = configuration.getMediaType();
            } else {
                throw new GovernanceException("Artifact type '" + key
                        + "' is not in registry or unable to find relevant configuration.");
            }
        } catch (RegistryException e) {
            throw new GovernanceException("Unable to obtain governance artifact configuration for rxt: " + key, e);
        }
    }

    /**
     * Creates a new artifact from the given qualified name.
     *
     * @param qName the qualified name of this artifact.
     *
     * @return the artifact added.
     * @throws GovernanceException if the operation failed.
     */
    public GenericArtifact newGovernanceArtifact(QName qName) throws GovernanceException {
        GenericArtifactImpl genericArtifact =
                new GenericArtifactImpl(manager.newGovernanceArtifact(), mediaType) {};
        genericArtifact.setQName(qName);
        return genericArtifact;
    }

    /**
     * Creates a new artifact from the given qualified name.
     *
     * @param qName the qualified name of this artifact.
     *
     * @return the artifact added.
     * @throws GovernanceException if the operation failed.
     */
    public GenericArtifact newGovernanceArtifact(QName qName, byte[] content) throws GovernanceException {
        GenericArtifact genericArtifact = newGovernanceArtifact(qName);
        genericArtifact.setContent(content);
        return genericArtifact;
    }

    /**
     * Creates a new artifact from the given content.
     *
     * @param content the artifact content.
     *
     * @return the artifact added.
     * @throws GovernanceException if the operation failed.
     */
    public GenericArtifact newGovernanceArtifact(OMElement content)
            throws GovernanceException {
        GenericArtifactImpl genericArtifact =
                new GenericArtifactImpl(manager.newGovernanceArtifact(content), mediaType) {};
        String name = GovernanceUtils.getAttributeValue(content,
                artifactNameAttribute, artifactElementNamespace);
        String namespace = (artifactNamespaceAttribute != null) ?
                GovernanceUtils.getAttributeValue(content,
                        artifactNamespaceAttribute, artifactElementNamespace) : null;
        if (name != null && !name.equals("")) {
            genericArtifact.setQName(new QName(namespace, name));
            //This is to fix the REGISTRY-1603.
//        } else {
//            throw new GovernanceException("Unable to compute QName from given XML payload, " +
//                    "please ensure that the content passed in matches the configuration.");
        }
        return genericArtifact;
    }


    /**
     * Creates a new artifact from the given string content.
     *
     * @param omContent the artifact content in string
     *
     * @return the artifact added.
     * @throws GovernanceException if the operation failed.
     */
    public GenericArtifact newGovernanceArtifact(String omContent) throws GovernanceException {
        XMLInputFactory factory = XMLInputFactory.newInstance();
        factory.setProperty(XMLInputFactory.IS_COALESCING, true);

        try {
            XMLStreamReader reader = factory.createXMLStreamReader(new StringReader(omContent));
            GenericArtifact artifact = this.newGovernanceArtifact(new StAXOMBuilder(reader).getDocumentElement());
            artifact.setContent(omContent.getBytes());

            return artifact;
        } catch (XMLStreamException e) {
            throw new GovernanceException("Error in creating generic artifact form content: " + omContent, e);
        }
    }

    /**
     * Adds the given artifact to the registry. Please do not use this method to update an
     * existing artifact use the update method instead. If this method is used to update an existing
     * artifact, all existing properties (such as lifecycle details) will be removed from the
     * existing artifact.
     *
     * @param artifact the artifact.
     *
     * @throws GovernanceException if the operation failed.
     */
    public void addGenericArtifact(GenericArtifact artifact) throws GovernanceException {
         manager.addGovernanceArtifact(artifact);
    }

    /**
     * Updates the given artifact on the registry.
     *
     * @param artifact the artifact.
     *
     * @throws GovernanceException if the operation failed.
     */
    public void updateGenericArtifact(GenericArtifact artifact) throws GovernanceException {
        manager.updateGovernanceArtifact(artifact);
    }

    /**
     * Fetches the given artifact on the registry.
     *
     * @param artifactId the identifier of the artifact.
     *
     * @return the artifact.
     * @throws GovernanceException if the operation failed.
     */
    public GenericArtifact getGenericArtifact(String artifactId) throws GovernanceException {
        GovernanceArtifact governanceArtifact = manager.getGovernanceArtifact(artifactId);
        if (governanceArtifact == null) {
            return null;
        }
        if (governanceArtifact instanceof GenericArtifactImpl &&
                ((GenericArtifactImpl)governanceArtifact).getMediaType() != null &&
                !mediaType.equals(((GenericArtifactImpl)governanceArtifact).getMediaType())) {
            // A wrong artifact manager has been used to retrieve the artifact. Fix for REGISTRY-2064.
            return null;
        }
        return new GenericArtifactImpl(governanceArtifact, mediaType) {};
    }

    /**
     * Removes the given artifact from the registry.
     *
     * @param artifactId the identifier of the artifact.
     *
     * @throws GovernanceException if the operation failed.
     */
    public void removeGenericArtifact(String artifactId) throws GovernanceException {
        manager.removeGovernanceArtifact(artifactId);
    }

    /**
     * Finds all artifacts matching the given filter criteria.
     *
     * @param criteria the filter criteria to be matched.
     *
     * @return the artifacts that match.
     * @throws GovernanceException if the operation failed.
     */
    public GenericArtifact[] findGenericArtifacts(Map<String, List<String>> criteria)
            throws GovernanceException {
        return getGenericArtifacts(manager.findGovernanceArtifacts(criteria));
    }

    /**
     * Find all possible terms and its count for the given facet field and query criteria
     * @param criteria the filter criteria to be matched
     * @param facetField field used for faceting
     * @param authRequired authorization required flag
     * @return term results
     * @throws GovernanceException
     */
    public TermData[] getTermData(Map<String, List<String>> criteria, String facetField, boolean authRequired) throws GovernanceException {
        return manager.getTermData(criteria, facetField, authRequired);
    }

    /**
     * Finds and returns GenericArtifact instances matching the search query
     *
     * @param query The query string that needs to be searched for
     * @return The GenericArtifact list that matching the query
     * @throws GovernanceException if the operation failed
     */
    public GenericArtifact[] findGovernanceArtifacts(String query) throws GovernanceException {
        return getGenericArtifacts(manager.findGovernanceArtifacts(query));
    }

    /**
     * Finds all artifacts matching the given filter criteria.
     *
     * @param criteria the filter criteria to be matched.
     *
     * @return the artifacts that match.
     * @throws GovernanceException if the operation failed.
     */
    public GenericArtifact[] findGenericArtifacts(GenericArtifactFilter criteria)
            throws GovernanceException {
        List<GenericArtifact> artifacts = new ArrayList<GenericArtifact>();
        for (GenericArtifact artifact : getAllGenericArtifacts()) {
            if (artifact != null) {
                if (criteria.matches(artifact)) {
                    artifacts.add(artifact);
                }
            }
        }
        return artifacts.toArray(new GenericArtifact[artifacts.size()]);
    }

    /**
     * Finds all artifacts on the registry.
     *
     * @return all artifacts on the registry.
     * @throws GovernanceException if the operation failed.
     */
    public GenericArtifact[] getAllGenericArtifacts() throws GovernanceException {
        return getGenericArtifacts(manager.getAllGovernanceArtifacts());
    }

    /**
     * Retrieve all the generic artifacts which associated with the given lifecycle
     *
     * @param lcName Name of the lifecycle
     *
     * @return GenericArtifact array
     * @throws GovernanceException
     */
    public GenericArtifact[] getAllGenericArtifactsByLifecycle(String lcName) throws GovernanceException {
        return getGenericArtifacts(manager.getAllGovernanceArtifactsByLifecycle(lcName));
    }

    /**
     * Retrieve all the generic artifacts which associated with the given lifecycle in the given lifecycle state
     *
     * @param lcName  Name of the lifecycle
     * @param lcState Name of the current lifecycle state
     *
     * @return GenericArtifact array
     * @throws GovernanceException
     */
    public GenericArtifact[] getAllGenericArtifactsByLifecycleStatus(String lcName, String lcState) throws GovernanceException {
        return getGenericArtifacts(manager.getAllGovernanceArtifactsByLIfecycleStatus(lcName, lcState));
    }

    // Method to obtain artifacts from governance artifacts.
    private GenericArtifact[] getGenericArtifacts(GovernanceArtifact[] governanceArtifacts) {
        List<GenericArtifact> artifacts =
                new ArrayList<GenericArtifact>(governanceArtifacts.length);
        for (GovernanceArtifact governanceArtifact : governanceArtifacts) {
            if(governanceArtifact != null) {
                artifacts.add(new GenericArtifactImpl(governanceArtifact, mediaType) {});
            }
        }
        return artifacts.toArray(new GenericArtifact[artifacts.size()]);
    }

    /**
     * Finds all identifiers of the artifacts on the registry.
     *
     * @return an array of identifiers of the artifacts.
     * @throws GovernanceException if the operation failed.
     */
    public String[] getAllGenericArtifactIds() throws GovernanceException {
        return manager.getAllGovernanceArtifactIds();
    }


    public static GenericArtifact newDetachedGovernanceArtifact(QName artifactName,String mediaType ){
        return new DetachedGenericArtifactImpl(artifactName ,mediaType);
    }

    /**
     * Check whether GovernanceArtifact is exists in the Registry without loading whole artifact into memory.
     * This method only work for Configurable Governance Artifacts and doe not work for Content Artifacts such
     * as WSDL, WADL, Swagger, XMLSchema etc.
     *
     * @param artifact GovernanceArtifact to check it's existence.
     * @return true or false
     * @throws GovernanceException if the operation failed.
     */
    public boolean isExists(GovernanceArtifact artifact) throws GovernanceException {
        return manager.isExists(artifact);
    }

    public void removeGenericArtifact(GenericArtifact artifact) throws GovernanceException {
        manager.removeGenericArtifact(artifact);
    }

    public void setContent(Resource resource, GovernanceArtifact artifact) throws GovernanceException {
        manager.setContent(resource, artifact);
    }

}
