/*
 * Copyright
 *   2007 axYus - www.axyus.com
 *   2007 JP.Tessier - jean-philippe.tessier@axyus.com
 *
 * This file is part of XEMELIOS.
 *
 * XEMELIOS is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * XEMELIOS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with XEMELIOS; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package fr.gouv.finances.cp.xemelios.controls;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Stack;
import java.util.TreeSet;
import java.util.Vector;

import javax.xml.namespace.NamespaceContext;

import org.apache.log4j.Logger;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import fr.gouv.finances.dgfip.utils.Pair;
import fr.gouv.finances.cp.utils.ui.DlgGetPair;
import fr.gouv.finances.dgfip.utils.xml.XmlAttributesImpl;
import fr.gouv.finances.dgfip.xemelios.common.ToolException;
import fr.gouv.finances.dgfip.xemelios.common.config.DocumentModel;
import fr.gouv.finances.cp.xemelios.controls.comm.COLLBUDG;
import fr.gouv.finances.cp.xemelios.controls.models.AddOnModel;
import fr.gouv.finances.cp.xemelios.controls.models.ControlModel;
import fr.gouv.finances.cp.xemelios.controls.models.DocumentControlModel;
import fr.gouv.finances.cp.xemelios.controls.processors.Processor;
import fr.gouv.finances.cp.xemelios.ui.MainWindow;
import fr.gouv.finances.dgfip.utils.xml.SAXWriter;
import fr.gouv.finances.dgfip.xemelios.auth.UnauthorizedException;
import org.xml.sax.helpers.AttributesImpl;

/**
 * @author Jean-Philippe TESSIER
 *
 * Permet d'appeler les contrôles unitaires afin de
 * traiter les anomalies rencontrées que renvoient ces controles.
 * Ces anomalies sont envoyées à un serializer qui les ajoutent au fichier contôler
 * par l'intermédiares de tags namespacisés en anos afin de pouvoir les traités dans XéMèLios
 */
public abstract class AbstractControlContentHandler extends DefaultHandler implements ContentHandler {

    private static final Logger logger = Logger.getLogger(AbstractControlContentHandler.class);
    protected Pair collectivite, budget;
    public int MAX_ANOS = 300;
    private ArrayList<AbstractUnitControl> controlsToCall;
    private DocumentControlModel docControlMod;
    private DocumentModel docModel;
    //private DocumentModel docsModel;
    private Hashtable<String, Object> hParams;
    private Stack<String> stack;
    protected MainWindow owner;
    //private String etat;
    private Vector<Anomalie> anos = new Vector<Anomalie>();
    private Vector<Anomalie> vTotalAnomalies;
    private Vector<ControlModel> controlsModel;
    private SAXWriter saxWriter;
    private NamespaceContext nsCtx;
    private boolean startEtatPassed = false;
    public boolean importInterupted = false;
    private String fileName;
//	private DlgControl dlgControl;
    private ControlProgressListener cpl;
    ArrayList<AddOnModel> addOns = null;

    public void setControlProgressListener(ControlProgressListener cpl) {
        this.cpl = cpl;
    }

    public AbstractControlContentHandler(DocumentModel dm, DocumentControlModel docCtrlMod, Hashtable<String, Object> parameters, MainWindow frame, String file, ControlProgressListener cpl) {
        super();
        setControlProgressListener(cpl);
        this.docModel = dm;
        this.docControlMod = docCtrlMod;
        this.owner = frame;
        this.fileName = file;
        this.nsCtx = dm.getNamespaces();
        vTotalAnomalies = new Vector<Anomalie>();
        stack = new Stack<String>();
        controlsToCall = new ArrayList<AbstractUnitControl>();
        hParams = parameters;
        if (docCtrlMod.getEngine() != null) {
            addOns = docCtrlMod.getEngine().getAddOns();
        }
        if (addOns == null) {
            addOns = new ArrayList<AddOnModel>();
        }
        for (AddOnModel addOn : addOns) {
            addOn.getProcessor().setNamespaceContext(nsCtx);
        }

        // Le premier contrôle lit le code collectivité ainsi que le code budgétaire.
        try {
            COLLBUDG cbc = new COLLBUDG();
            cbc.setDocumentModel(docControlMod);
            cbc.setCch(this);
            cbc.setColl_path(docCtrlMod.getCollectivitePath().getCodePath().getPath());
            cbc.setColl_lib(docCtrlMod.getCollectivitePath().getLibellePath().getPath());
            cbc.setBudg_path(docCtrlMod.getBudgetPath().getCodePath().getPath());
            cbc.setBudg_lib(docCtrlMod.getBudgetPath().getLibellePath().getPath());
            controlsToCall.add(cbc);
        } catch (Exception e) {
            e.printStackTrace();
        }
        for (ControlModel ctrlm : docCtrlMod.getControls()) {
            try {
                Object o = new Object();
                if (!ctrlm.id.startsWith("TECH")) {
                    o = Class.forName(ctrlm.getClasse()).newInstance();
                }
                if (o instanceof AbstractUnitControl) {
                    AbstractUnitControl auc = (AbstractUnitControl) o;
                    auc.setParameters(getParameters());
                    auc.setDocumentModel(docControlMod);
                    auc.setFileName(fileName);
                    controlsToCall.add(auc);
                }
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    public void setFileName(String fileName) {
        for (AbstractUnitControl auc : controlsToCall) {
            auc.setFileName(fileName);
        }
    }

    @Override
    public void startDocument() throws SAXException {
        for (AbstractUnitControl auc : controlsToCall) {
            try {
                auc.startDocument();
            } catch (SAXException saxEx) {
                logger.debug("in startDocument cch !!!", saxEx);
            }
        }
        saxWriter.startDocument();
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
        stack.push(localName);
        String xpath = getXpathExpression();
        XmlAttributesImpl attrs = new XmlAttributesImpl();
        for (int i = 0; i < atts.getLength(); i++) {
            attrs.addAttribute(atts.getURI(i), atts.getLocalName(i), atts.getQName(i), atts.getType(i), atts.getValue(i));
        }
        if (stack.size() == 1) {
            // Ajout du namespace ano: au document
            startEtatPassed = true;
            saxWriter.startPrefixMapping(nsCtx.getPrefix(Anomalie.ANOMALY_NS_URI), Anomalie.ANOMALY_NS_URI);
        }
        attrs.addAttribute(Anomalie.ANOMALY_NS_URI, "node-id",
                (nsCtx.getPrefix(Anomalie.ANOMALY_NS_URI) != null ? nsCtx.getPrefix(Anomalie.ANOMALY_NS_URI) + ":" : "") + "node-id",
                "CDATA",
                IdGenerator.nextId());
        for (AddOnModel addOn : addOns) {
            if (addOn.isConcernedBy(Processor.EVENT.START_ELEMENT, uri, localName, qName)) {
                addOn.process(Processor.EVENT.START_ELEMENT, uri, localName, qName, attrs);
            }
        }
        for (AbstractUnitControl auc : controlsToCall) {
            auc.startElement(uri, localName, qName, attrs, getXpathExpression());
        }
        saxWriter.startElement(uri, localName, qName, attrs);
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        Vector<Anomalie> vAno = new Vector<Anomalie>();
        for (AbstractUnitControl auc : controlsToCall) {
            try {
                anos = auc.endElement(uri, localName, qName, getXpathExpression());
                if (anos != null && !anos.isEmpty()) {
                    if (!(vTotalAnomalies.size() > MAX_ANOS)) {
                        vTotalAnomalies.addAll(anos);
                        vAno.addAll(anos);
                    }
                }
            } catch (SAXException saxEx) {
                logger.debug("in endElement!!!", saxEx);
            }
        }
        if (!vAno.isEmpty()) {
            for (Anomalie ano : vAno) {
                writeAnomalie(ano);
            }
        }
        if (stack.size() == 1) {
            saxWriter.endPrefixMapping(nsCtx.getPrefix(Anomalie.ANOMALY_NS_URI));
        }

        saxWriter.endElement(uri, localName, qName);
        stack.pop();

    }

    @Override
    public void endDocument() throws SAXException {
        try {
            if (collectivite == null || collectivite.key == null || collectivite.libelle == null) {
                try {
                    collectivite = getCollectivite(docModel, fileName);
                    if (!owner.getConnectedUser().hasCollectivite(collectivite.key, docModel)) {
                        throw new UnauthorizedException("Voux n'êtes pas autorisé à contrôler des fichier de cette collectivite" + collectivite.libelle != null ? " (" + collectivite.libelle + ")" : "");
                    }
                } catch (ToolException e) {
                    e.printStackTrace();
                }
            }
            if (budget == null || budget.key == null || budget.libelle == null || budget.libelle.equals("")) {
                try {
                    budget = getBudget(docModel, collectivite, fileName);
                } catch (ToolException e) {
                    e.printStackTrace();
                }

            }
        } catch (UnauthorizedException ex) {
            throw new SAXException(ex);
        }
        saxWriter.endDocument();
    }

    private Hashtable<String, Object> getParameters() {
        return hParams;
    }

    public void setParameters(Hashtable<String, Object> ho) {
        hParams = ho;
    }

    private String getXpathExpression() {
        StringBuffer sb = new StringBuffer();
        for (String s : stack) {
            sb.append(s + "/");
        }
        return sb.toString();
    }

    public void setControlsModel(Vector<ControlModel> cm) {
        this.controlsModel = cm;
    }

    public void setSaxWriter(SAXWriter sxw) {
        saxWriter = sxw;
    }

    public Vector<Anomalie> getVTotalAnomalies() {
        return vTotalAnomalies;
    }

    public abstract Pair getCollectivite(DocumentModel dm, String fileName) throws ToolException;

    public abstract Pair getBudget(DocumentModel dm, Pair collectivite, String fileName) throws ToolException, UnauthorizedException;

    protected class QueryCollectivite implements Runnable {

        private Pair ret;
        private String fileName;
        private Pair defaultCollectivite = null;

        public Pair getDefaultCollectivite() {
            return defaultCollectivite;
        }

        public void setDefaultCollectivite(
                Pair defaultCollectivite) {
            this.defaultCollectivite = defaultCollectivite;
        }

        public QueryCollectivite(String fileName) {
            super();
            this.fileName = fileName;
        }

        public void run() {
            DlgGetPair dlg = new DlgGetPair(owner, "Sur quelle collectivité porte ce document : " + fileName + " ?", null, null, collectivite);
            if (dlg.run()) {
                ret = new Pair(dlg.getCode(), dlg.getLibelle());
            } else {
                importInterupted = true;
//				dlgControl.notifyImportInterupted();
//				dlgControl.setVisible(false);
                cpl.notifyImportInterrupted(true);
            }
        }

        public Pair getRet() {
            return ret;
        }
    }

    protected class QueryBudget implements Runnable {

        private String fileName;
        private TreeSet<Pair> existing;
        private Pair ret;
        private String collectivite = null;

        public QueryBudget(String fileName, TreeSet<Pair> existing, String collectivite) {
            super();
            this.fileName = fileName;
            this.existing = existing;
            this.collectivite = collectivite;
        }

        public void run() {
            DlgGetPair dlg = null;
            dlg = new DlgGetPair(owner, "Sur quel budget porte ce document : " + fileName + " ?", existing, collectivite, budget);

            if (dlg.run()) {
                ret = new Pair(dlg.getCode(), dlg.getLibelle());
            } else {
                if (!importInterupted) {
//					dlgControl.notifyImportInterupted();
                    importInterupted = true;
                    cpl.notifyImportInterrupted(false);
                }
            }
            logger.debug("fin de QueryBudget.run()");
        }

        public Pair getRet() {
            logger.debug("QueryBudget.getRet()");
            return ret;
        }
    }

    @Override
    public void startPrefixMapping(String prefix, String uri) throws SAXException {
        super.startPrefixMapping(prefix, uri);
        for (AbstractUnitControl auc : controlsToCall) {
            auc.startPrefixMapping(prefix, uri, getXpathExpression());
        }
        saxWriter.startPrefixMapping(prefix, uri);
    }

    @Override
    public void endPrefixMapping(String prefix) throws SAXException {
        super.endPrefixMapping(prefix);
        for (AbstractUnitControl auc : controlsToCall) {
            auc.endPrefixMapping(prefix, getXpathExpression());
        }
        saxWriter.endPrefixMapping(prefix);
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        for (AbstractUnitControl auc : controlsToCall) {
            try {
                auc.characters(ch, start, length, getXpathExpression());
            } catch (SAXException saxEx) {
                logger.debug("in characters!!!", saxEx);
            }
        }
        saxWriter.characters(ch, start, length);
    }

    @Override
    public void setDocumentLocator(Locator locator) {
        saxWriter.setDocumentLocator(locator);
    }

    @Override
    public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
        saxWriter.ignorableWhitespace(ch, start, length);
    }

    @Override
    public void processingInstruction(String target, String data) {
        try {
            saxWriter.processingInstruction(target, data);
        } catch (SAXException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void skippedEntity(String name) throws SAXException {
        saxWriter.skippedEntity(name);
    }

    public void setNamespaceContext(NamespaceContext nsCtx) {
        this.nsCtx = nsCtx;
    }

    public Pair getBudget() {
        return budget;
    }

    public void setBudget(Pair budget) {
        this.budget = budget;
    }

    public Pair getCollectivite() {
        return collectivite;
    }

    public void setCollectivite(Pair collectivite) {
        this.collectivite = collectivite;
        logger.debug("setting collectivite: " + collectivite);
    }

    private void writeAnomalie(Anomalie ano) {
        AttributesImpl attrs = new AttributesImpl();
        attrs.addAttribute(Anomalie.ANOMALY_NS_URI,"anoId",nsCtx.getPrefix(Anomalie.ANOMALY_NS_URI)+":anoId","string",ano.getIdAnomalie());
        attrs.addAttribute(Anomalie.ANOMALY_NS_URI,"ctrlLibelle",nsCtx.getPrefix(Anomalie.ANOMALY_NS_URI)+":ctrlLibelle","string",ano.getControleLibelle());
        attrs.addAttribute(Anomalie.ANOMALY_NS_URI,"ctrlId",nsCtx.getPrefix(Anomalie.ANOMALY_NS_URI)+":ctrlId","string",ano.getControleID());
        attrs.addAttribute(Anomalie.ANOMALY_NS_URI,"severity",nsCtx.getPrefix(Anomalie.ANOMALY_NS_URI)+":severity","string",ano.getSeverity());
        attrs.addAttribute(Anomalie.ANOMALY_NS_URI,"visibility",nsCtx.getPrefix(Anomalie.ANOMALY_NS_URI)+":visibility","string",ano.isVisible()?"visible":"hidden");
        try {
            saxWriter.startElement(Anomalie.ANOMALY_NS_URI,"Anomalie",nsCtx.getPrefix(Anomalie.ANOMALY_NS_URI)+":Anomalie",attrs);
        } catch(Throwable t) {
            logger.error("while calling startElement on Anomalie",t);
        }
        try {
            startElement(Anomalie.ANOMALY_NS_URI,"ctrlRegleFonct",nsCtx.getPrefix(Anomalie.ANOMALY_NS_URI)+":ctrlRegleFonct",new AttributesImpl());
            saxWriter.addCData(SAXWriter.unescapeText(ano.getRegle()));
            saxWriter.endElement(Anomalie.ANOMALY_NS_URI,"ctrlRegleFonct",nsCtx.getPrefix(Anomalie.ANOMALY_NS_URI)+":ctrlRegleFonct");
        } catch(Throwable t) {
            logger.error("while writting ctrlRegleFonct",t);
        }
        try {
            saxWriter.startElement(Anomalie.ANOMALY_NS_URI,"message",nsCtx.getPrefix(Anomalie.ANOMALY_NS_URI)+":message",new AttributesImpl());
            saxWriter.addCData(SAXWriter.unescapeText(ano.getMessage()));
            saxWriter.endElement(Anomalie.ANOMALY_NS_URI,"message",nsCtx.getPrefix(Anomalie.ANOMALY_NS_URI)+":message");
        } catch(Throwable t) {
            logger.error("while writting message",t);
        }
        for(Node node:ano.getNodes()) {
            AttributesImpl attrs2 = new AttributesImpl();
            attrs2.addAttribute(Anomalie.ANOMALY_NS_URI,"id",nsCtx.getPrefix(Anomalie.ANOMALY_NS_URI)+":id","string",node.getId());
            try {
                saxWriter.startElement(Anomalie.ANOMALY_NS_URI,"node",nsCtx.getPrefix(Anomalie.ANOMALY_NS_URI)+":node",attrs2);
                saxWriter.endElement(Anomalie.ANOMALY_NS_URI,"node",nsCtx.getPrefix(Anomalie.ANOMALY_NS_URI)+":node");
            } catch(Throwable t) {
                logger.error("while writting node",t);
            }
        }
        try {
            saxWriter.endElement(Anomalie.ANOMALY_NS_URI,"Anomalie",nsCtx.getPrefix(Anomalie.ANOMALY_NS_URI)+":Anomalie");
        } catch(Throwable t) {
            logger.error("while calling endElement on Anomalie",t);
        }
    }
}
