/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License).  You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the license at
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Copyright (c) Ericsson AB, 2004-2007. All rights reserved.
 */
package org.jvnet.glassfish.comms.clb.core.common.chr.dcr;

import org.jvnet.glassfish.comms.clb.core.common.chr.HashKeyExtractor;
import org.jvnet.glassfish.comms.clb.core.util.LoadbalancerUtil;
import org.jvnet.glassfish.comms.clb.proxy.http.util.HttpRequest;
import org.jvnet.glassfish.comms.util.LogUtil;

import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.Level;

import javax.servlet.sip.SipServletRequest;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;


public class DcrConfigurableHashKeyExtractor implements HashKeyExtractor {
    private static final String SIP_RULES = "//sip-rules";
    private static final String HTTP_RULES = "//http-rules";
    private static LogUtil logger = new LogUtil(LogUtil.CLB_LOG_DOMAIN);

    /**
     * HashMap mapping an XML tag to a Condition class implementing the hash
     * extraction function.
     */
    static HashMap<String, String> ruleMap = new HashMap<String, String>(8);

    static {
        ruleMap.put("session-case", SessionCaseCondition.class.getName());
        ruleMap.put("if", IfOperator.class.getName());
        ruleMap.put("and", AndOperator.class.getName());
        ruleMap.put("or", OrOperator.class.getName());
        ruleMap.put("header", HeaderCondition.class.getName());
        ruleMap.put("request-uri", RequestUriCondition.class.getName());
        ruleMap.put("cookie", CookieCondition.class.getName());
    }

    // Document with rules
    private org.w3c.dom.Document document = null;

    /** Root Nodes in the rule document with condition for sip and http */
    private Condition rootSipConditionElement;
    private Condition newRootSipConditionElement;
    private Condition rootHttpConditionElement;
    private Condition newRootHttpConditionElement;

    /** DCR XML file location. */
    String ruleFileName;

    public DcrConfigurableHashKeyExtractor(String ruleFileName)
        throws DcrRulesException {
        this.ruleFileName = ruleFileName;

        File ruleFile = new File(ruleFileName);
        processNewRuleFile(ruleFile);
    }

    public String getHashKey(HttpRequest request) {
        return rootHttpConditionElement.evaluate(request);
    }

    public String getHashKey(SipServletRequest request) {
        return LoadbalancerUtil.encodeParameter(rootSipConditionElement.evaluate(request));
    }

    private void processNewRuleFile(File ruleFile) throws DcrRulesException {
        try {
            FileInputStream fis = new FileInputStream(ruleFile);

            createMappingRules(fis);
            setupConditions();

            // Everything went well, switch to the new configuration
            rootSipConditionElement = newRootSipConditionElement;
            rootHttpConditionElement = newRootHttpConditionElement;
            newRootHttpConditionElement = null;
            newRootSipConditionElement = null;

            // Delete dcr.xml.new (if exists) which indicated a new rule file
            File ruleFileNew = new File(ruleFileName + ".new");

            if (ruleFileNew.exists()) {
                ruleFileNew.delete();

                if (logger.isLoggable(Level.FINE)) {
                    logger.logMsg(Level.FINE, "Deleted ucr.xml.new");
                }
            }
        } catch (FileNotFoundException e) {
            throw new DcrRulesException(
                "User Centric Rule File could not be opened: " +
                ruleFile.getName() + ".", e);
        }
    }

    private void createMappingRules(InputStream is) throws DcrRulesException {
        if (is == null) {
            throw new DcrRulesException("The stream must not be null.");
        } else {
            document = DomUtil.parseXmlDocument(is);

            XPath xpath = XPathFactory.newInstance().newXPath();
            NodeList nlist = null;

            try {
                nlist = (NodeList) xpath.evaluate("//*", document,
                        XPathConstants.NODESET);
            } catch (XPathExpressionException e) {
                throw new DcrRulesException("Error when opening opening DCR XML.",
                    e);
            }

            int noNodes = nlist.getLength();

            for (int i = 0; i < noNodes; i++) {
                Element element = (Element) nlist.item(i);
                String elementName = element.getNodeName();
                String className = ruleMap.get(elementName);
                Class clazz = null;

                if (className != null) {
                    try {
                        clazz = Class.forName(className);

                        Object obj = clazz.newInstance();
                        element.setUserData("instance", obj, null);
                        ((Condition) obj).addElmentData(element);

                        if (logger.isLoggable(Level.FINE)) {
                            logger.logMsg(Level.FINE,
                                "Creating new " + obj.getClass().getName());
                        }
                    } catch (Throwable e) {
                        throw new DcrRulesException("Failed to instantiate condition.",
                            e);
                    }
                }
            }

            try {
                Element sipRootElement = (Element) xpath.evaluate(SIP_RULES,
                        document, XPathConstants.NODE);

                if (sipRootElement != null) {
                    ArrayList<Element> elementArray = DomUtil.getChildElements(sipRootElement);

                    if (!elementArray.isEmpty()) {
                        Element firstSipCondition = elementArray.get(0);
                        newRootSipConditionElement = (Condition) firstSipCondition.getUserData(
                                "instance");
                    }
                }

                Element httpRootElement = (Element) xpath.evaluate(HTTP_RULES,
                        document, XPathConstants.NODE);

                if (httpRootElement != null) {
                    ArrayList<Element> elementArray = DomUtil.getChildElements(httpRootElement);

                    if (!elementArray.isEmpty()) {
                        Element firstHttpCondition = elementArray.get(0);
                        newRootHttpConditionElement = (Condition) firstHttpCondition.getUserData(
                                "instance");
                    }
                }
            } catch (XPathExpressionException e) {
                throw new DcrRulesException("Error parsing DCR XML file.", e);
            }
        }
    }

    public void setupConditions() {
        // WHATTODO Is this method really needed? Seems to do nothing useful.
        try {
            XPath xpath = XPathFactory.newInstance().newXPath();

            NodeList nodeList = (NodeList) xpath.evaluate("//*", document,
                    XPathConstants.NODESET);

            int noNodes = nodeList.getLength();

            for (int i = 0; i < noNodes; i++) {
                if (nodeList.item(i) instanceof Element) {
                    Element e = (Element) nodeList.item(i);
                    Object obj = e.getUserData("instance");

                    if (obj != null) {
                        Condition c = (Condition) obj;
                        c.addChildren(e.getChildNodes());
                    }
                }
            }
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }
}
