/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF 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.apache.synapse.securevault;


import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMNamespace;
import org.apache.synapse.securevault.secret.SecretCallbackHandler;
import org.apache.synapse.securevault.secret.SecretCallbackHandlerFactory;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

/**
 * Factory for creating <code>SecretResolver</code> instances
 */
public class SecretResolverFactory {

    /**
     * Creates an <code>SecretResolver</code> instance from an XML
     *
     * @param configuration <code>SecretResolver</code> configuration as XML object, DOM
     * @param isCapLetter   whether the XML element begins with a cap letter
     * @return an <code>SecretResolver</code> instance
     */
    public static SecretResolver create(Element configuration, boolean isCapLetter) {
        SecretResolver secretResolver = new SecretResolver();
        String secureVaultElementName;
        String passwordProvider = null;
        String secureVaultNamespace = null;
        String secureVaultNamespacePrefix = null;

        if(isCapLetter){
            secureVaultElementName = SecurityConstants.SECURE_VAULT_CAP;
        } else {
            secureVaultElementName = SecurityConstants.SECURE_VAULT_SIMPLE;
        }

        NamedNodeMap nodeMap  = configuration.getAttributes();
        String namespaceUri = null;

        for(int i = 0; i < nodeMap.getLength(); i++){
            String nodeName = nodeMap.item(i).getNodeName();
            secureVaultNamespacePrefix = nodeName.substring(nodeName.
                    indexOf(SecurityConstants.NS_SEPARATOR) + 1);
            namespaceUri  = nodeMap.item(i).getNodeValue();
            if(namespaceUri.startsWith(SecurityConstants.SECURE_VAULT_NS)){
                secureVaultNamespace =  namespaceUri;
                break;
            }
        }

        if(secureVaultNamespace != null){

            NodeList nodeList = configuration.getElementsByTagName(secureVaultNamespacePrefix +
                                                                   SecurityConstants.NS_SEPARATOR +
                                                                   secureVaultElementName);
            if(nodeList.item(0) != null){
                NamedNodeMap namedNodeMap  = nodeList.item(0).getAttributes();
                for(int i = 0; i < namedNodeMap.getLength(); i++){
                    namedNodeMap.item(i).getPrefix();
                    String attributeName = namedNodeMap.item(i).getNodeName();
                    if(attributeName.equals(SecurityConstants.SECURE_VAULT_ATTRIBUTE)){
                        passwordProvider = namedNodeMap.item(i).getNodeValue();
                        break;
                    }
                }
            }
        }

        initPasswordManager(secretResolver, passwordProvider);

        if(secretResolver.isInitialized()){
            addProtectedTokens(configuration, secretResolver, secureVaultNamespacePrefix);
        }

        return secretResolver;
    }

    /**
     * Creates an <code>SecretResolver</code> instance from an XML
     *
     * @param configuration <code>SecretResolver</code> configuration as XML object, OMElement
     * @param isCapLetter   whether the XML element begins with a cap letter
     * @return an <code>SecretResolver</code> instance
     */
    public static SecretResolver create(OMElement configuration, boolean isCapLetter) {

        SecretResolver secretResolver = new SecretResolver();
        String secureVaultElementName;
        String passwordProvider = null;
        OMNamespace secureVaultNamespace = null;

        if(isCapLetter){
            secureVaultElementName = SecurityConstants.SECURE_VAULT_CAP;
        } else {
            secureVaultElementName = SecurityConstants.SECURE_VAULT_SIMPLE;
        }

        Iterator iterator = configuration.getAllDeclaredNamespaces();
        while(iterator.hasNext()){
            OMNamespace omNamespace = (OMNamespace) iterator.next();
            if(omNamespace.getNamespaceURI().startsWith(SecurityConstants.SECURE_VAULT_NS)){
                secureVaultNamespace = omNamespace;
                break;
            }
        }

        if(secureVaultNamespace != null){
            Iterator itr = configuration.
                    getChildrenWithName(new QName(secureVaultNamespace.getNamespaceURI(),                                                  secureVaultElementName, secureVaultNamespace.getPrefix()));
            if(itr.hasNext()){
                passwordProvider = ((OMElement)itr.next()).
                        getAttributeValue(new QName((SecurityConstants.SECURE_VAULT_ATTRIBUTE)));
            }

            initPasswordManager(secretResolver, passwordProvider);

            if(secretResolver.isInitialized()){
                addProtectedTokens(configuration, secureVaultNamespace, secretResolver);
            }
        }
        return secretResolver;
    }



    /**
     * Creates an <code>SecretResolver</code> instance from a set of property
     *
     * @param properties     configuration properties
     * @param propertyPrefix prefix to identify suitable configuration properties
     * @return an <code>SecretResolver</code> instance
     */
    public static SecretResolver create(Properties properties, String propertyPrefix) {

        SecretResolver secretResolver = new SecretResolver();

        String prefix = propertyPrefix;
        if (propertyPrefix != null && !"".equals(propertyPrefix) && !propertyPrefix.endsWith(".")) {
            prefix += ".";
        }
        initPasswordManager(secretResolver, properties.getProperty(prefix +
                SecurityConstants.SECRET_PROVIDER));

        if (secretResolver.isInitialized()) {
            String protectedTokens = properties.getProperty(prefix +
                    SecurityConstants.PROTECTED_TOKENS_SIMPLE);
            if (protectedTokens != null && !"".equals(protectedTokens.trim())) {
                ArrayList<String> tokens = new ArrayList<String>(Arrays
                        .asList(protectedTokens.split(",")));
                for (String token : tokens) {
                    secretResolver.addProtectedToken(token);
                }
            }
        }
        return secretResolver;
    }

    /**
     * Creates an <code>SecretResolver</code> instance from a set of DOM Node
     *
     * @param namedNodeMap DOM node set
     * @return an <code>SecretResolver</code> instance
     */
    public static SecretResolver create(NamedNodeMap namedNodeMap) {

        SecretResolver secretResolver = new SecretResolver();

        Node namedItem = namedNodeMap.getNamedItem(SecurityConstants.PASSWORD_PROVIDER_SIMPLE);
        if (namedItem != null) {
            String passwordProvider = namedItem.getNodeValue();
            if (passwordProvider != null && passwordProvider.trim().length() > 0) {
                initPasswordManager(secretResolver, passwordProvider);
            }
        }

        if (secretResolver.isInitialized()) {
            Node protectedTokenAttr = namedNodeMap.getNamedItem(
                    SecurityConstants.PROTECTED_TOKENS_SIMPLE);
            ArrayList<String> protectedTokenList;
            if (protectedTokenAttr != null) {
                String protectedTokens = protectedTokenAttr.getNodeValue();
                if (protectedTokens != null && protectedTokens.trim().length() > 0) {
                    protectedTokenList = new ArrayList<String>(Arrays.asList(protectedTokens
                            .split(",")));
                    for (String token : protectedTokenList) {
                        if (token != null && !"".equals(token)) {
                            secretResolver.addProtectedToken(token);
                        }
                    }
                }
            }
        }
        return secretResolver;
    }

    private static void initPasswordManager(SecretResolver secretResolver, String provider) {
        SecretCallbackHandler callbackHandler =
                SecretCallbackHandlerFactory.createSecretCallbackHandler(provider);
        if (callbackHandler != null) {
            secretResolver.init(callbackHandler);
        }
    }

    private static void addProtectedTokens(Node node, SecretResolver secretResolver,
                                           String secureVaultNamespacePrefix){
        NodeList nodeList = node.getChildNodes();
        for(int i = 0; i < nodeList.getLength(); i++){
            NamedNodeMap nodeMap = nodeList.item(i).getAttributes();
            if(nodeMap != null){
                for(int j = 0; j < nodeMap.getLength(); j++){
                    String attributeName = nodeMap.item(j).getNodeName();
                    if((secureVaultNamespacePrefix + SecurityConstants.NS_SEPARATOR +
                        SecurityConstants.SECURE_VAULT_ALIAS).equals(attributeName)){
                        secretResolver.addProtectedToken(nodeMap.item(j).getNodeValue());
                    }
                }
            }
            addProtectedTokens(nodeList.item(i), secretResolver, secureVaultNamespacePrefix);
        }
    }

    private static void addProtectedTokens(OMElement configuration, OMNamespace secureVaultNamespace,
                                           SecretResolver secretResolver){

        Iterator iterator = configuration.
                getChildrenWithNamespaceURI(secureVaultNamespace.getNamespaceURI());

        while(iterator.hasNext()){
            OMElement omElement = (OMElement)iterator.next();
            String attributeValue = omElement.
                    getAttributeValue(new QName(secureVaultNamespace.getNamespaceURI(),
                                                SecurityConstants.SECURE_VAULT_ALIAS,
                                                secureVaultNamespace.getPrefix()));

            if(attributeValue != null && !attributeValue.equals("")){
                secretResolver.addProtectedToken(attributeValue);
            }
            addProtectedTokens(omElement, secureVaultNamespace, secretResolver);
        }
    }
}
