/*
Copyright (C) 2000-2010  Ministere de la culture et de la communication (France), AJLSM
See LICENCE file
*/
package fr.gouv.culture.sdx.pipeline;

import fr.gouv.culture.sdx.utils.Utilities;
import fr.gouv.culture.sdx.utils.constants.Node;
import org.apache.avalon.framework.parameters.Parameters;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

import java.util.Hashtable;
import java.util.Iterator;
import java.util.TreeMap;


public class NodeCountTransformation extends AbstractNodeBasedTransformation {

    public static final String PARAM_NAME_BOUNDS_ELEMENT = "transform:bounds-element";
    public static final String PARAM_NAME_COUNT_MODE = "transform:count-mode";
    public static final String PARAM_VALUE_ELEMENT = "element";
    public static final String PARAM_VALUE_ATTRIBUTE = "attribute";

    protected String _boundsElem = null;
    protected String _countMode = PARAM_VALUE_ELEMENT;//defaulted
    //the below instance variables need to be reset at each pass
    protected boolean withinBoundsElem = false;
    protected Hashtable sortedCounters = new Hashtable();
    protected StringBuffer content = null;


    protected void setInternalParameters(Parameters params) {
        super.setInternalParameters(params);
        _boundsElem = params.getParameter(PARAM_NAME_BOUNDS_ELEMENT, null);
        _countMode = params.getParameter(PARAM_NAME_COUNT_MODE, PARAM_VALUE_ELEMENT);//defaulted
    }


    protected NodeCounter prepareCounter(String uri, String loc, String raw, Attributes atts) {
        //what node to count elements or attributes
        //use super.elementIsParameter() or super.attribute.isParameter() to get key name
        NodeCounter l_nodeCounter = null;
        String l_keyType = null;
        String l_nodeName = null;
        String l_nodeCountValue = null;
        String l_attributeValue = null;
        l_nodeName = super.elementIsParameter(uri, loc, raw, atts);
        if (Utilities.checkString(l_nodeName)) {
            l_keyType = PARAM_VALUE_ELEMENT;
            l_nodeCountValue = super.transParameters.getParameter(l_nodeName, null);
            this.content = new StringBuffer();

        }
        if (l_nodeName == null && atts != null) {
            l_nodeName = super.attributeIsParameter(uri, loc, raw, atts);
            if (Utilities.checkString(l_nodeName))
                l_keyType = PARAM_VALUE_ATTRIBUTE;
            l_nodeCountValue = (String) super._attributesToTrack.get(l_nodeName);
            l_attributeValue = atts.getValue(l_nodeName);
            if (_countMode.equals(PARAM_VALUE_ELEMENT))
                this.content = new StringBuffer();//we are counting the elements that have this att/value pair
        }
        //build a NodeCounter and set the params
        if (Utilities.checkString(l_nodeName)) {
            if (!sortedCounters.containsKey(l_nodeName)) {
                l_nodeCounter = new NodeCounter();
                l_nodeCounter._name = l_nodeName;
                l_nodeCounter._keyType = l_keyType;
                l_nodeCounter._countValue = l_nodeCountValue;
                sortedCounters.put(l_nodeName, l_nodeCounter);
            } else
                l_nodeCounter = (NodeCounter) sortedCounters.get(l_nodeName);
        }
        //should we count attributes with certain values
        //otherwisewe in endElement we count the elements values having the attribute=value pair
        if (_countMode.equals(PARAM_VALUE_ATTRIBUTE) && Utilities.checkString(l_nodeCountValue)
                && Utilities.checkString(l_attributeValue)
                && l_nodeCountValue.trim().equals(l_attributeValue)) {
            count(l_nodeCounter._name, l_attributeValue);
            this.content = null;
        }

        return l_nodeCounter;

        /*
        //looking for elemName{1*} values equal to 'loc' or 'raw'
        for (int i = 0; i < noKeys; i++) {
            String intString = Integer.toString(i);
            String elemParamKey = "elemName" + intString;
            String elemName = transParameters.getParameter(elemParamKey, null);
            String attParamKey = "attName" + intString;
            String attName = transParameters.getParameter(attParamKey, null);
            String attValParamKey = "attValue" + intString;
            String attValue = transParameters.getParameter(attValParamKey, null);
            if (Utilities.checkString(elemName)) {
                String elemCountKey = null;
                if (elemName.equals(loc))
                    elemCountKey = loc;
                else if (elemName.equals(raw))
                    elemCountKey = raw;
                if (Utilities.checkString(elemCountKey)) {
                    elemCountKey = "elem~" + elemCountKey;
                    this.collectKey = elemCountKey;
                    this.content = new StringBuffer();
                }
            }
            //looking for attName{1*} values equal to 'raw'
            else if (Utilities.checkString(attName)) {
                String attCountKey = null;
                String attVal = atts.getValue(attName);
                if (Utilities.checkString(attValue))
                    attCountKey = "attName~" + attName + "%%" + attVal;
                if (Utilities.checkString(attCountKey)) {
                    this.collectKey = attCountKey;
                    this.content = new StringBuffer();
                }
            }
            //looking for attValue{1*} values equal to 'raw'
            else if (Utilities.checkString(attValue)) {
                String attCountKey = null;
                for (int j = 0; j < atts.getLength(); j++) {
                    String attNameLoc = atts.getLocalName(j);
                    String attVal = atts.getValue(j);
                    if (attValue.equals(attVal)) {
                        attCountKey = "attValue~" + attNameLoc + "%%" + attValue;
                        if (Utilities.checkString(attCountKey)) {
                            this.collectKey = attCountKey;
                            this.content = new StringBuffer();
                        }

                    }

                }
            }

        }*/
    }


    public void startElement(String uri, String loc, String raw, Attributes a)
            throws SAXException {
        super.startElement(uri, loc, raw, a);
        setCurrentElementProperties(uri, loc, raw, a);
        if (!withinBoundsElem) {
            if (_boundsElem.equals(loc) || _boundsElem.equals(raw)
                    || _boundsElem.equals(_xpathString.getLocalXPath())
                    || _boundsElem.equals(_xpathString.getQualifiedXPath())) {
                this.resetFields();
                withinBoundsElem = true;
            }
        } else if (withinBoundsElem)
            prepareCounter(uri, loc, raw, a);

    }


    public void endElement(String uri, String loc, String raw)
            throws SAXException {
        if (withinBoundsElem) {
            if (this.content != null && _countMode.equals(PARAM_VALUE_ELEMENT)) {
                String content = this.content.toString();
                NodeCounter l_nodeCounter = prepareCounter(uri, loc, raw, super.peekCurrentAttributes());
                String l_counterName = l_nodeCounter._name;
                if (Utilities.checkString(l_nodeCounter._countValue) && !l_nodeCounter._countValue.trim().equals(content.trim()))
                //we have a good value that doesnt match the mapped value so we dont count
                {
                } else
                    count(l_counterName, content);
            }
            if (_boundsElem.equals(loc) || _boundsElem.equals(raw)
                    || _boundsElem.equals(_xpathString.getLocalXPath())
                    || _boundsElem.equals(_xpathString.getQualifiedXPath())) {
                withinBoundsElem = false;
                sendSummary();
                this.resetFields();
            }

            this.content = null;
        }

        super.endElement(uri, loc, raw);
        resetCurrentElementProperties(uri, loc, raw);


    }

    protected void count(String counterName, String content) {

        if (this.sortedCounters.containsKey(counterName) && Utilities.checkString(content)) {
            NodeCounter l_nodeCounter = (NodeCounter) sortedCounters.get(counterName);
            TreeMap l_sortedOccurences = l_nodeCounter._sortedCounter;
            Object key = getKeyForContentType(content);
            Integer l_nodeCount = (Integer) l_sortedOccurences.get(key);
            if (l_nodeCount == null) {
                l_nodeCount = new Integer(1);
            } else {
                int count = l_nodeCount.intValue();
                count++;
                l_nodeCount = new Integer(count);
            }
            l_sortedOccurences.put(key, l_nodeCount);
        }

    }

    private Object getKeyForContentType(String content) {
        Object key = null;
        try {
            key = Integer.valueOf(content);
        } catch (NumberFormatException e) {
            key = content;
        }
        return key;
    }

    protected void sendSummary() throws SAXException {
        AttributesImpl atts1 = new AttributesImpl();
        atts1.addAttribute("", "bounds-element", "bounds-element", Node.Type.CDATA, _boundsElem);
        atts1.addAttribute("", "count-mode", "count-mode", Node.Type.CDATA, _countMode);
        super.startElement("", "node-counter", "transform:node-counter", atts1);

        Iterator counts = sortedCounters.keySet().iterator();
        while (counts.hasNext()) {
            NodeCounter nodeCounter = (NodeCounter) counts.next();
            AttributesImpl atts2 = new AttributesImpl();
            atts2.addAttribute("", "name", "name", Node.Type.CDATA, nodeCounter._name);
            if (Utilities.checkString(nodeCounter._countValue))
                atts2.addAttribute("", "value", "value", Node.Type.CDATA, nodeCounter._countValue);
            super.startElement("", "node-count", "transform:node-count", atts2);
            Iterator countValues = nodeCounter._sortedCounter.keySet().iterator();
            while (countValues.hasNext()) {
                Object key = countValues.next();
                String l_keyType = nodeCounter._keyType;
                String countName = nodeCounter._name;
                String countValue = nodeCounter._countValue;
                String nodeValue = key.toString();
                String occurs = nodeCounter._sortedCounter.get(key).toString();
                char[] value = nodeValue.toCharArray();
                AttributesImpl atts3 = new AttributesImpl();
                atts3.addAttribute("", "value", "value", Node.Type.CDATA, countValue);
                atts3.addAttribute("", "name", "name", Node.Type.CDATA, countName);
                atts3.addAttribute("", "key-type", "key-type", Node.Type.CDATA, l_keyType);
                atts3.addAttribute("", "occurs", "occurs", Node.Type.CDATA, occurs);
                super.startElement("", "count", "transform:count", atts3);
                super.characters(value, 0, value.length);
                super.endElement("", "count", "transform:count");

            }
            super.endElement("", "node-count", "transform:node-count");

        }
        super.endElement("", "node-counter", "transform:node-counter");

    }

    protected void resetFields() {
        super.resetFields();
        withinBoundsElem = false;
        sortedCounters = new Hashtable();
        content = null;
    }

    public void characters(char c[], int start, int len)
            throws SAXException {
        super.characters(c, start, len);
        if (withinBoundsElem) {
            if (this.content != null)
                this.content.append(c, start, len);
        }
    }

    protected class NodeCounter {
        String _keyType = "";
        String _name = "";
        String _countValue = "";
        TreeMap _sortedCounter = new TreeMap();//to stock values, and occurences
    }

    public void recycle() {
        super.recycle();
        this.resetFields();
    }


}
