/*
 * Copyright 2004,2005 The Apache Software Foundation.
 *
 * 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.apache.rahas;

import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMXMLBuilderFactory;
import org.apache.axiom.om.OMXMLParserWrapper;
import org.apache.wss4j.common.util.Loader;

import javax.xml.namespace.QName;
import java.io.FileInputStream;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;

public class TokenRequestDispatcherConfig {

    public final static String CONFIG_PARAM_KEY = "token-dispatcher-configuration";
    public final static String CONFIG_FILE_KEY = "token-dispatcher-configuration-file";

    private final static QName DISPATCHER_CONFIG = new QName("token-dispatcher-configuration");
    private final static QName ISSUER = new QName("issuer");
    private final static QName CANCELER = new QName("canceler");
    private final static QName VALIDATOR = new QName("validator");
    private final static QName RENEWER = new QName("renewer");
    private final static QName TOKEN_TYPE = new QName("tokenType");
    private final static QName CLASS_ATTR = new QName("class");
    private final static QName DEFAULT_ATTR = new QName("default");
    private final static QName CONFIGURATION_ELEMENT = new QName("configuration");

    private Map issuers;
    private Map validators;
    private Map renewers;
    private Map configFiles = new Hashtable();
    private Map configElements = new Hashtable();
    private Map configParamNames = new Hashtable();

    private String defaultIssuerClassName;
    private String defaultCancelerClassName;
    private String defaultValidatorClassName;
    private String defaultRenewerClassName;

    public static TokenRequestDispatcherConfig load(OMElement configElem) throws TrustException {

        if (!DISPATCHER_CONFIG.equals(configElem.getQName())) {
            throw new TrustException("incorrectConfiguration");
        }
        TokenRequestDispatcherConfig conf = new TokenRequestDispatcherConfig();

        // Issuers
        handleIssuers(configElem, conf);
        
        //Validators
        handleValidators(configElem, conf);
        
        //Renewers
        handleRenewers(configElem, conf);
        
        // Cancelers
        handleCancelers(configElem, conf);
        
        

        //There must be a defulat issuer
        if (conf.defaultIssuerClassName == null) {
            throw new TrustException("defaultIssuerMissing");
        }
        return conf;
    }

    private static void handleCancelers(OMElement configElem,
                                        TokenRequestDispatcherConfig conf) throws TrustException {

        OMElement cancelerEle = configElem.getFirstChildWithName(CANCELER); // support only one canceler
        if (cancelerEle != null) {
            //get the class attr
            String cancelerClass = cancelerEle.getAttributeValue(CLASS_ATTR);
            if (cancelerClass == null) {
                throw new TrustException("missingClassName");
            }
            conf.defaultCancelerClassName = cancelerClass;
            processConfiguration(cancelerEle, conf, cancelerClass);
        }

        //TODO: imple
    }

    private static void handleIssuers(OMElement configElem,
                                      TokenRequestDispatcherConfig conf) throws TrustException {
        for (Iterator<OMElement> issuerElems = configElem.getChildrenWithName(ISSUER);
             issuerElems.hasNext();) {

            OMElement element = issuerElems.next();

            //get the class attr
            String issuerClass = element.getAttributeValue(CLASS_ATTR);
            if (issuerClass == null) {
                throw new TrustException("missingClassName");
            }
            String isDefault = element.getAttributeValue(DEFAULT_ATTR);
            if (isDefault != null && "true".equalsIgnoreCase(isDefault)) {
                //Use the first default issuer as the default isser
                if (conf.defaultIssuerClassName == null) {
                    conf.defaultIssuerClassName = issuerClass;
                } else {
                    throw new TrustException("badDispatcherConfigMultipleDefaultIssuers");
                }
            }

            processConfiguration(element, conf, issuerClass);

            //Process token types
            for (Iterator<OMElement> tokenTypes = element.getChildrenWithName(TOKEN_TYPE);
                 tokenTypes.hasNext();) {
                OMElement type = tokenTypes.next();
                String value = type.getText();
                if (value == null || value.trim().length() == 0) {
                    throw new TrustException("invalidTokenTypeDefinition",
                                             new String[]{"Issuer", issuerClass});
                }
                if (conf.issuers == null) {
                    conf.issuers = new Hashtable();
                }
                //If the token type is not already declared then add it to the
                //table with the issuer classname
                if (!conf.issuers.keySet().contains(value)) {
                    conf.issuers.put(value, issuerClass);
                }
            }
        }
    }
    
    private static void handleValidators(OMElement configElem,
            TokenRequestDispatcherConfig conf) throws TrustException {
        
        for (Iterator<OMElement> validatorElems = configElem.getChildrenWithName(VALIDATOR);
        validatorElems.hasNext();) {

            OMElement element = validatorElems.next();

           //get the class attr
           String validatorClass = element.getAttributeValue(CLASS_ATTR);
           if (validatorClass == null) {
               throw new TrustException("missingClassName");
           }
           String isDefault = element.getAttributeValue(DEFAULT_ATTR);
           if (isDefault != null && "true".equalsIgnoreCase(isDefault)) {
               //Use the first default issuer as the default isser
               if (conf.defaultValidatorClassName == null) {
                   conf.defaultValidatorClassName = validatorClass;
               } else {
                   throw new TrustException("badDispatcherConfigMultipleDefaultValidators");
               }
           }

           processConfiguration(element, conf, validatorClass);
    
           //Process token types
           for (Iterator<OMElement> tokenTypes = element.getChildrenWithName(TOKEN_TYPE);
                tokenTypes.hasNext();) {
               OMElement type = tokenTypes.next();
               String value = type.getText();
               if (value == null || value.trim().length() == 0) {
                   throw new TrustException("invalidTokenTypeDefinition",
                                            new String[]{"Validator", validatorClass});
               }
               if (conf.validators == null) {
                   conf.validators = new Hashtable();
               }
               //If the token type is not already declared then add it to the
               //table with the validator classname
               if (!conf.validators.keySet().contains(value)) {
                   conf.validators.put(value, validatorClass);
               }
           }
        }       
    }
    
    private static void handleRenewers(OMElement configElem,
            TokenRequestDispatcherConfig conf) throws TrustException {
        
        for (Iterator<OMElement> renewerElems = configElem.getChildrenWithName(RENEWER);
        renewerElems.hasNext();) {

            OMElement element = renewerElems.next();

           //get the class attr
           String renewerClass = element.getAttributeValue(CLASS_ATTR);
           if (renewerClass == null) {
               throw new TrustException("missingClassName");
           }
           String isDefault = element.getAttributeValue(DEFAULT_ATTR);
           if (isDefault != null && "true".equalsIgnoreCase(isDefault)) {
               //Use the first default issuer as the default isser
               if (conf.defaultRenewerClassName == null) {
                   conf.defaultRenewerClassName = renewerClass;
               } else {
                   throw new TrustException("badDispatcherConfigMultipleDefaultRenewers");
               }
           }

           processConfiguration(element, conf, renewerClass);
    
           //Process token types
           for (Iterator<OMElement> tokenTypes = element.getChildrenWithName(TOKEN_TYPE);
                tokenTypes.hasNext();) {
               OMElement type = tokenTypes.next();
               String value = type.getText();
               if (value == null || value.trim().length() == 0) {
                   throw new TrustException("invalidTokenTypeDefinition",
                                            new String[]{"Renewer", renewerClass});
               }
               if (conf.renewers == null) {
                   conf.renewers = new Hashtable();
               }
               //If the token type is not already declared then add it to the
               //table with the renwer classname
               if (!conf.renewers.keySet().contains(value)) {
                   conf.renewers.put(value, renewerClass);
               }
           }
        }       
    }

    private static void processConfiguration(OMElement element,
                                             TokenRequestDispatcherConfig conf,
                                             String implClass) {

        for (Iterator<OMElement> configs = element.getChildrenWithName(CONFIGURATION_ELEMENT);
             configs.hasNext();) {
            OMElement configEle = configs.next();
            String configType =
                    configEle.getAttribute(new QName("type")).getAttributeValue().trim();
            if (configType.equalsIgnoreCase("file")) { //Process configuration file information
                String issuerConfigFile = configEle.getText();
                if (issuerConfigFile != null) {
                    conf.configFiles.put(implClass, issuerConfigFile);
                }
            } else if (configType.equalsIgnoreCase("element"))
            { //Process configuration element information
                conf.configElements.put(implClass, configEle);
            } else if (configType.equalsIgnoreCase("parameter"))
            { //Process configuration parameter name information
                conf.configParamNames.put(implClass, configEle.getText());
            }
        }
    }

    public static TokenRequestDispatcherConfig load(String configFilePath) throws TrustException {
        FileInputStream fis;
        OMXMLParserWrapper builder;
        try {
            fis = new FileInputStream(configFilePath);
            builder = OMXMLBuilderFactory.createOMBuilder(fis);
        } catch (Exception e) {
            throw new TrustException("errorLoadingConfigFile", new String[]{configFilePath}, e);
        }
        return load(builder.getDocumentElement());
    }

    public TokenIssuer getDefaultIssuerInstace() throws TrustException {
        if (this.defaultIssuerClassName != null) {
            try {
                return createIssuer(this.defaultIssuerClassName);
            } catch (Exception e) {
                throw new TrustException("cannotLoadClass",
                                         new String[]{this.defaultIssuerClassName}, e);
            }
        } else {
            return null;
        }
    }

    public TokenCanceler getDefaultCancelerInstance() throws TrustException {
        if (this.defaultCancelerClassName != null) {
            try {
                return createCanceler(this.defaultCancelerClassName);
            } catch (Exception e) {
                throw new TrustException("cannotLoadClass",
                                         new String[]{this.defaultCancelerClassName}, e);
            }
        } else {
            return null;
        }
    }
    
    public TokenValidator getDefaultValidatorInstance() throws TrustException {
        if (this.defaultValidatorClassName != null) {
            try {
                return createValidator(this.defaultValidatorClassName);
            } catch (Exception e) {
                throw new TrustException("cannotLoadClass",
                                         new String[]{this.defaultValidatorClassName}, e);
            }
        } else {
            return null;
        }
    }
    
    public TokenRenewer getDefaultRenewerInstance() throws TrustException {
        if (this.defaultRenewerClassName != null) {
            try {
                return createRenewer(this.defaultRenewerClassName);
            } catch (Exception e) {
                throw new TrustException("cannotLoadClass",
                                         new String[]{this.defaultRenewerClassName}, e);
            }
        } else {
            return null;
        }
    }
    

    public String getDefaultIssuerName() {
        return this.defaultIssuerClassName;
    }


    public TokenIssuer getIssuer(String tokenType) throws TrustException {
        String issuerClassName = null;
        //try to find the issuer class name from the tokenType<->issuer map
        if (this.issuers != null) {
            issuerClassName = (String) this.issuers.get(tokenType);
        }
        //If a specific issuer is not found use the default issuer
        if (issuerClassName == null) {
            issuerClassName = this.defaultIssuerClassName;
        }
        try {
            return createIssuer(issuerClassName);
        } catch (Exception e) {
            throw new TrustException("cannotLoadClass",
                                     new String[]{issuerClassName}, e);
        }
    }
    
    public TokenValidator getValidator(String tokenType) throws TrustException {
        String validatorClassName = null;
        //try to find the validator class name from the tokenType<->validator map
        if (this.validators != null) {
            validatorClassName = (String) this.validators.get(tokenType);
        }
        //If a specific validator is not found use the default validator
        if (validatorClassName == null) {
            validatorClassName = this.defaultValidatorClassName;
        }
        try {
            return createValidator(validatorClassName);
        } catch (Exception e) {
            throw new TrustException("cannotLoadClass",
                                     new String[]{validatorClassName}, e);
        }
    }
    
    public TokenRenewer getRenewer(String tokenType) throws TrustException {
        String renewerClassName = null;
        //try to find the renewer class name from the tokenType<->Renewer map
        if (this.renewers != null) {
            renewerClassName = (String) this.renewers.get(tokenType);
        }
        //If a specific renewer is not found use the default renewer
        if (renewerClassName == null) {
            renewerClassName = this.defaultRenewerClassName;
        }
        try {
            return createRenewer(renewerClassName);
        } catch (Exception e) {
            throw new TrustException("cannotLoadClass",
                                     new String[]{renewerClassName}, e);
        }
    }

    /**
     * @param issuerClassName
     * @return TokenIssuer
     */
    private TokenIssuer createIssuer(String issuerClassName) throws Exception {
        TokenIssuer issuer = (TokenIssuer) Loader.loadClass(this.getClass().getClassLoader(), 
                                                                    issuerClassName).newInstance();
        issuer.setConfigurationElement((OMElement) this.configElements.get(issuerClassName));
        issuer.setConfigurationFile((String) this.configFiles.get(issuerClassName));
        issuer.setConfigurationParamName((String) this.configParamNames.get(issuerClassName));
        return issuer;
    }

    private TokenCanceler createCanceler(String cancelerClassName) throws Exception {
        TokenCanceler canceler = (TokenCanceler) Loader.loadClass(this.getClass().getClassLoader(),
                                                                   cancelerClassName).newInstance();
        canceler.setConfigurationElement((OMElement) this.configElements.get(cancelerClassName));
        canceler.setConfigurationFile((String) this.configFiles.get(cancelerClassName));
        canceler.setConfigurationParamName((String) this.configParamNames.get(cancelerClassName));
        return canceler;
    }
    
    private TokenValidator createValidator(String validatorClassName) throws Exception {
        TokenValidator validator = (TokenValidator) Loader.loadClass(this.getClass().getClassLoader(),
                                                                validatorClassName).newInstance();
        validator.setConfigurationElement((OMElement) this.configElements.get(validatorClassName));
        validator.setConfigurationFile((String) this.configFiles.get(validatorClassName));
        validator.setConfigurationParamName((String) this.configParamNames.get(validatorClassName));
        return validator;
    }
    
    private TokenRenewer createRenewer (String renewerClassName) throws Exception {
        TokenRenewer renewer = (TokenRenewer) Loader.loadClass(this.getClass().getClassLoader(),
                                                                    renewerClassName).newInstance();
        renewer.setConfigurationElement((OMElement) this.configElements.get(renewerClassName));
        renewer.setConfigurationFile((String) this.configFiles.get(renewerClassName));
        renewer.setConfigurationParamName((String) this.configParamNames.get(renewerClassName));
        return renewer;
    }
}
