/*
 * Cr�� le 5 f�vr. 2007
 *
 * Pour changer le mod�le de ce fichier g�n�r�, allez � :
 * Fen�tre&gt;Pr�f�rences&gt;Java&gt;G�n�ration de code&gt;Code et commentaires
 */

package fr.gouv.finances.cp.xemelios.controls;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Hashtable;
import java.util.Vector;
import javax.swing.JFrame;

import javax.swing.JOptionPane;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.validation.Schema;
import javax.xml.validation.Validator;

import org.apache.log4j.Logger;
import org.apache.xerces.jaxp.validation.XMLSchemaFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import fr.gouv.finances.cp.utils.Pair;
import fr.gouv.finances.cp.utils.xml.marshal.XmlOutputter;
import fr.gouv.finances.cp.xemelios.common.Constants;
import fr.gouv.finances.cp.xemelios.common.config.DocumentModel;
import fr.gouv.finances.cp.xemelios.common.config.DocumentsModel;
import fr.gouv.finances.cp.xemelios.controls.models.ControlsModel;
import fr.gouv.finances.cp.xemelios.controls.models.DocumentControlModel;
import fr.gouv.finances.cp.xemelios.controls.models.ParamModel;
import fr.gouv.finances.cp.xemelios.controls.tech.TECH01;
import fr.gouv.finances.cp.xemelios.controls.tech.TECH02;
import fr.gouv.finances.cp.xemelios.controls.tech.TECH03;
import fr.gouv.finances.cp.xemelios.importers.EtatImporteur;
import fr.gouv.finances.cp.xemelios.ui.MainWindow;
import fr.gouv.finances.cp.xemelios.ui.MainWindow.ImportContent;
import fr.gouv.finances.cp.xemelios.utils.FileUtils;

public class MainControl implements Runnable {
    public static final int STATUS_NOT_CONTROLLED = -1;
    public static final int STATUS_NOT_XML = 1;
    public static final int STATUS_INVALID_ENCODING = 2;
    public static final int STATUS_NOT_SCHEMA_VALID = 3;
    public static final int STATUS_NOT_VALID = 4;
    public static final int STATUS_CONTROL_NO_ERROR = 5;
    
    private final static Logger logger = Logger.getLogger(MainControl.class);
    private File cfgDir,fichierAController,rapport;
    private DocumentModel documentModel;
    private DocumentsModel docsModel;
    private Hashtable<String,Object> hashParams = new Hashtable<String,Object>();
    private DocumentControlModel dcm;
    private Date datecontrole;
    private NamespaceContext nameSpaces;
    private SaxXmlWriter saxWriter = new SaxXmlWriter();
    public static final String ID_DOCUMENT_RAPPORT = "DocumentRapport";
    private ControlsModel cm;
    private String IDrapport = "",domainesControles = "";
    private RapportWriter rapportWriter;
    private boolean hasTech01Error=false,hasTech02Error=false,hasTech03Error=false;
    private ControlContentHandler cch = null;
    private File fichierEnrichi = null;
    private String fichierEnrichiLocation=null, rapportLocation=null;
    
    private ControlProgressListener cpl = null;
    
    private int status = STATUS_NOT_CONTROLLED;
    
    public MainControl(DocumentModel dm,DocumentsModel docsMod){
        super();
        this.documentModel=dm;
        this.docsModel=docsMod;
        /**
         * La propri�t� syst�me SYS_PROP_DOC_DEF_DIR contient une liste de chemins.
         * Celui qui nous int�resse est dans le dm
         */
        // cfgDir = new File(System.getProperty(Constants.SYS_PROP_DOC_DEF_DIR));
        cfgDir = new File(dm.getBaseDirectory());
        cm = getControlsModel();
        dcm = cm.getDocumentById(dm.getId());
        nameSpaces = documentModel.getNamespaces();
        
    }
    public DocumentControlModel getDocumentControl() { return dcm; }
    
    public void run() {
        SAXParserFactory factorySAX = SAXParserFactory.newInstance();
        factorySAX.setNamespaceAware(true);
        
        FileInputStream fisTech;
        // Creation des contr�les techniques
        TECH01 tech01 = new TECH01();
        TECH02 tech02 = new TECH02();
        TECH03 tech03 = new TECH03();
        tech03.setDocumentModel(documentModel);
        
        // Contr�le de validit� synthaxique du document
        try {
            fisTech = new FileInputStream(fichierAController);
            SAXParser parserXmlTech = factorySAX.newSAXParser();
            tech01.setDocModel(documentModel);
            parserXmlTech.parse(fisTech,tech01);
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            //e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // Est-il valide synthaxiquement?
        for(Anomalie a:tech01.getAnomalies()){
            if(a.getControleID().matches("TECH01")){
                hasTech01Error = true;
                status = STATUS_NOT_XML;
            }
        }
        
        DocumentModel dmRapport = docsModel.getDocumentById(ID_DOCUMENT_RAPPORT);
        File tmpDir = new File(System.getProperty("java.io.tmpdir"),Constants.NOM_APP);
        if(!tmpDir.exists()){
            tmpDir.mkdirs();
        }
        if(rapportLocation==null) {
            rapportLocation = new File(tmpDir,"rapport"+fichierAController.getName()).getAbsolutePath();
        }
        rapport = new File(rapportLocation);
        datecontrole = new Date();
        
        FileOutputStream fos=null;
        try {
            fos = new FileOutputStream(rapport);
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        }
        rapportWriter = new RapportWriter(new XmlOutputter(fos,"ISO-8859-1"));
        
        try {
            if(!hasTech01Error) {
                cch = getControlContentHandler(documentModel,dcm,hashParams,MainWindow.instance,fichierAController.getName());
                cch.setParameters(hashParams);
                cch.setControlsModel(dcm.getControls());
                FileInputStream fis;
                
                // Controle de l'encoding
                if((dcm.getControlTechById("TECH02")==null) || 
                        (dcm.getControlTechById("TECH02")!=null && tech02.encodingValid(dcm.getControlTechById("TECH02"),fichierAController))) {
                    domainesControles = tech01.getDomaines();
                    if(dcm.getControlTechById("TECH03")!=null){
                        // Contr�le de validit� au sch�ma
                        XMLSchemaFactory xmlsf = new XMLSchemaFactory();
                        File fileSchema = new File(documentModel.getBaseDirectory(),dcm.getControlTechById("TECH03").getParamTechById("schemaLocation").getValue());
                        Schema schema;
                        try{
                            schema = xmlsf.newSchema(fileSchema);
                            Validator validateur = schema.newValidator();
                            FileInputStream fisTech03 = new FileInputStream(fichierAController);
                            SAXSource saxSrc=new SAXSource(new InputSource(fisTech03));
                            tech03.setDomainesControles(domainesControles);
                            validateur.setErrorHandler(tech03);
                            validateur.validate(saxSrc);
                            fisTech03.close();
                        } catch (SAXException saxe){
                            saxe.printStackTrace();
                        }
                    }
                    
                    // V�rification de la conformit� du document � son sch�ma & Exceptions dans xmlValidator ?
                    if((dcm.getControlTechById("TECH03")==null) || (dcm.getControlTechById("TECH03")!=null && tech03.getAnomalies().size()==0)) {
                        // R�cup des domaines contr�l�s
                        fis = new FileInputStream(fichierAController);
                        SAXParser parserXml = factorySAX.newSAXParser();
                        saxWriter.setNamespaceContext(nameSpaces);
                        if(fichierEnrichiLocation==null) {
                            fichierEnrichiLocation = new File(tmpDir,fichierAController.getName()).getAbsolutePath();
                        }
                        fichierEnrichi = new File(fichierEnrichiLocation);
                        //StreamResult sr = new StreamResult(fichierEnrichi);
                        DOMResult dr = new DOMResult();
                        saxWriter.setResult(dr);
                        cch.setSaxWriter(saxWriter);
                        cch.setNamespaceContext(nameSpaces);
                        parserXml.parse(fis,cch);
                        
                        StreamResult sr = new StreamResult(fichierEnrichi);
                        TransformerFactory tf = TransformerFactory.newInstance();
                        Transformer t = tf.newTransformer();
                        t.setOutputProperty(OutputKeys.ENCODING,tech02.getInputEncoding()==""?FileUtils.getFileEncoding(fichierAController):tech02.getInputEncoding());
                        t.transform(new DOMSource(dr.getNode()),sr);
                        sr.getOutputStream().flush();
                        sr.getOutputStream().close();
                        
                        // ajout CHM
                        fis.close();
                        
                        cpl.stopWaiting();
                        
                        writeRapport(cch.collectivite,cch.budget,cch.getVTotalAnomalies(),cch.MAX_ANOS);
                        // Import Interrompu ?
                        status = cch.getVTotalAnomalies().size()==0 ? STATUS_CONTROL_NO_ERROR : STATUS_NOT_VALID;
//                        if(!cch.importInterupted){
//                            // Si anomalies pr�sentes
//                            if(!(cch.getVTotalAnomalies().size()>0)){
//                                //JOptionPane jop = new JOptionPane();
//                                int reponse = JOptionPane.showConfirmDialog(MainWindow.instance,
//                                        "Le fichier "+fichierAController.getName()+" ne comporte pas d'anomalies.\nSouhaitez-vous quand m�me l'importer?",
//                                        "Question",
//                                        JOptionPane.YES_NO_OPTION);
//                                // Confirmation de l'import du flux malgr� l'absence d'anos
//                                if(JOptionPane.YES_OPTION == reponse){
//                                    // Import du doc
//                                    importer(documentModel,cch.collectivite,cch.budget,fichierEnrichi,false,null);
//                                    // 	Import du rapport si des anos sont pr�sentes
//                                    importer(dmRapport,cch.collectivite,cch.budget,rapport,true,new Triplet(IDrapport,cch.collectivite.key,cch.budget.key));
//                                } else {
//                                    cpl.notifyImportFinished(null);
//                                }
//                            }else{
//                                // Import du doc
//                                importer(documentModel,cch.collectivite,cch.budget,fichierEnrichi,false,null);
//                                
//                                // 	Import du rapport si des anos sont pr�sentes
//                                importer(dmRapport,cch.collectivite,cch.budget,rapport,true,new Triplet(IDrapport,cch.collectivite.key,cch.budget.key));
//                                
//                            }
//                        }
                        
                    }else{
                        if(tech03.getAnomalies().size()>0){
                            
                            logger.info("Le fichier "+fichierAController.getName()+" n'est pas conforme au schema de "+dcm.id);
                            Vector<Anomalie> vano = new Vector<Anomalie>();
                            for(Anomalie ano:tech03.getAnomalies()){
                                vano.add(ano);
                            }
                            status = STATUS_NOT_SCHEMA_VALID;
                            writeRapport(new Pair("00000000000000","Collectivite de Contr�le"),new Pair("00","Budget de Contr�le"),vano,0);
                            // Import du rapport si des anos sont pr�sentes
//                            importer(dmRapport,new Pair("00000000000000","Collectivite de Contr�le"),new Pair("00","Budget de Contr�le"),rapport,true,new Triplet(IDrapport,"00000000000000","00"));
                        } else {
                            //DisplayExceptionDlg dlgEx = new DisplayExceptionDlg(MainWindow.getInstance(),xmlValidator.getExceptionSaxe());
                            cpl.notifyTechniqueValidationFailed();
                            //dlgEx.setVisible(true);
                        }
                    }
                } else{
                    String encodingValide="";
                    for(int i = 0;i<dcm.getControlTechById("TECH02").getParams().size();i++){
                        String e = dcm.getControlTechById("TECH02").getParams().get(i).getValue();
                        if((i+1) == dcm.getControlTechById("TECH02").getParams().size()){
                            encodingValide+=e;
                        }else{
                            encodingValide+=e+",";
                        }
                    }
                    logger.info("Le fichier "+fichierAController.getName()+" n'est pas encod� en "+encodingValide+".");
                    Vector<Anomalie> vano = new Vector<Anomalie>();
                    vano.add(new Anomalie(IdGenerator.nextId(),
                            "TECH02","Contr�le de l'encoding du fichier.",
                            documentModel.getId(),
                            "",
                            "",
                            "",
                            "",
                            "",
                            "Le fichier est encod� en "+tech02.getInputEncoding()+" alors qu'il devrait �tre encod� en "+encodingValide+".",
                            "Le fichier doit �tre encod� en "+encodingValide+".",
                            "BLOQUANT","xpath namespacise",
                            new Hashtable<String,Object>()));
                    status = STATUS_INVALID_ENCODING;
                    writeRapport(new Pair("00000000000000","Collectivite de Contr�le"),new Pair("00","Budget de Contr�le"),vano,0);
                    // Import du rapport si des anos sont pr�sentes
//                    importer(dmRapport,new Pair("00000000000000","Collectivite de Contr�le"),new Pair("00","Budget de Contr�le"),rapport,true,new Triplet(IDrapport,"00000000000000","00"));
                }
            } else {    // il y a des erreurs de validite XML
                logger.info("Le fichier "+fichierAController.getName()+" n'est pas un fichier XML valide.");
                Vector<Anomalie> vano = new Vector<Anomalie>();
                for(Anomalie ano:tech01.getAnomalies()){
                    vano.add(ano);
                }
                writeRapport(new Pair("00000000000000","Collectivite de Contr�le"),new Pair("00","Budget de Contr�le"),tech01.getAnomalies(),0);
                // Import du rapport si des anos sont pr�sentes
//                importer(dmRapport,new Pair("00000000000000","Collectivite de Contr�le"),new Pair("00","Budget de Contr�le"),rapport,true,new Triplet(IDrapport,"00000000000000","00"));
            }
        } catch(TransformerException e) {
            e.printStackTrace();
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        
    }
    public Pair getCollectivite() throws IllegalStateException {
        if(status==STATUS_NOT_CONTROLLED) throw new IllegalStateException("You must run control before calling getCollectivite()");
        if(cch!=null) 
            return cch.getCollectivite();
        else
            return null;
    }
    public Pair getBudget() throws IllegalStateException {
        if(status==STATUS_NOT_CONTROLLED) throw new IllegalStateException("You must run control before calling getBudget()");
        if(cch!=null)
            return cch.getBudget();
        else
            return null;
    }
    public Vector<Anomalie> getTotalAnomalies() throws IllegalStateException {
        if(status==STATUS_NOT_CONTROLLED) throw new IllegalStateException("You must run control before calling getTotalAnomalies()");
        return cch!=null ? cch.getVTotalAnomalies() : new Vector<Anomalie>();
    }
    private ControlsModel getControlsModel(){
        ControlsModel ret = null;
        try {
            ControlParser cp = new ControlParser();
            cp.parse(new File(documentModel.getBaseDirectory(),documentModel.getControlConfigFile()));
            ControlsModel c = (ControlsModel) cp.getMarshallable();
            c.validate();
            ret = c;
        } catch (Exception ex) {
            logger.debug("Error in getControlsModel()!!!",ex);
        }
        return ret;
    }
    
    public void setFileToControl(File f){
        this.fichierAController=f;
    }
    public File getFileToControl() { return fichierAController; }
    public void setParameters(Hashtable<String,Object> params) {
        this.hashParams = params;
    }
    
        
    private void writeRapport(Pair collec,Pair budg,Vector<Anomalie> vano,int maxanos) throws IOException{
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String msgAno="";
        if(vano!=null && vano.size()>=maxanos && !vano.get(0).getControleID().startsWith("TECH")){
            msgAno="true";
        }else{
            msgAno="false";
        }
        
        IDrapport = datecontrole.getTime()+rapport.getName();
        // Entete du rapport
        Pair docid = new Pair("V",documentModel.getId());
        rapportWriter.writeElement("DocId",docid);
        rapportWriter.writeElement("NomFichier",new Pair("V",fichierAController.getName()));
        //rapportWriter.writeElement("DateControle",new Pair("V",simpleDateFormat.format(new Date())));
        rapportWriter.writeElement("DateControle",new Pair("V",simpleDateFormat.format(datecontrole)));
        
        rapportWriter.openElement("Collectivite");
        rapportWriter.writeElement("Siret",new Pair("V",collec.key));
        rapportWriter.writeElement("Libelle",new Pair("V",collec.libelle));
        rapportWriter.closeElement("Collectivite");
        
        rapportWriter.openElement("Budget");
        rapportWriter.writeElement("Code",new Pair("V",budg.key));
        rapportWriter.writeElement("Libelle",new Pair("V",budg.libelle));
        rapportWriter.closeElement("Budget");
        
        rapportWriter.writeElement("TropAnos",new Pair("V",msgAno));
        rapportWriter.openElement("Domaines");
        String domaines[] = domainesControles.split(" / ");
        for(String domain:domaines) rapportWriter.writeElement("Domaine",new Pair("V",domain));
        rapportWriter.closeElement("Domaines");
        rapportWriter.openElement("Params");
        writeParameters();
        rapportWriter.closeElement("Params");
        
        // Corps du rapport
        rapportWriter.openElement("DonneesRapport");
        rapportWriter.openElement("Rapport");
        rapportWriter.writeElement("Id",new Pair("V",datecontrole.getTime()+rapport.getName()));
        if(vano!=null){
            Collections.sort(vano,Collections.reverseOrder());
            for (Anomalie anomalie : vano) {
                writeAnomalie(anomalie);
            }
        }
        rapportWriter.closeElement("Rapport");
        rapportWriter.closeElement("DonneesRapport");
        rapportWriter.endRapport();
        logger.debug("Le rapport se trouve dans : "+rapport.getParent());
    }
    
    private void writeAnomalie(Anomalie a){
        rapportWriter.openElement("Anomalie");
        rapportWriter.writeElement("Id",new Pair("V",a.getIdAnomalie()));
        rapportWriter.writeElement("IdCtrl",new Pair("V",a.getControleID()));
        rapportWriter.writeElement("LibelleCtrl",new Pair("V",a.getControleLibelle()));
        rapportWriter.writeElement("EtatId",new Pair("V",a.getEtatID()));
        rapportWriter.writeElement("ElementImportable",new Pair("V",a.getelementImportable()));
        rapportWriter.writeElement("IdElementImportable",new Pair("V",a.getIdElementImportable()));
        rapportWriter.writeElement("ElementEnAnomalie",new Pair("V",a.getelementEnAnomalie()));
        rapportWriter.writeElement("LibelleLien",new Pair("V",a.getLibelleLien()));
        
        rapportWriter.openElement("Message");
        rapportWriter.writeData(a.getMessage().replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("&", "&amp;"));
        rapportWriter.closeElement("Message");
        
        rapportWriter.openElement("Regle");
        rapportWriter.writeData(a.getRegle().replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("&", "&amp;"));
        rapportWriter.closeElement("Regle");
        
        rapportWriter.writeElement("Severity",new Pair("V",a.getSeverity()));
        rapportWriter.writeElement("XPath",new Pair("V",a.getXPath()));
        if(a.getXslParams()!=null && a.getXslParams().size()>0){
            for(String key:a.getXslParams().keySet()){
                String value = a.getXslParams().get(key).toString();
                rapportWriter.openElement("XslParam");
                rapportWriter.writeElement("Key",new Pair("V",key));
                rapportWriter.writeElement("Value",new Pair("V",value));
                rapportWriter.closeElement("XslParam");
            }
        }
        rapportWriter.closeElement("Anomalie");
    }
    
    private String writeParameters(){
        StringBuffer params = new StringBuffer();
        for(ParamModel param:dcm.getParams()){
            String paramValue = "";
            if(hashParams.get(param.getId()).getClass().equals(Boolean.class)){
                if(hashParams.get(param.getId()).equals(true)){
                    paramValue = "Oui";
                } else if(hashParams.get(param.getId()).equals(false)){
                    paramValue = "Non";
                } else {
                    paramValue = "Nc";
                }
            } else if(hashParams.get(param.getId()).getClass().equals(BigDecimal.class)){
                paramValue = hashParams.get(param.getId()).toString();
            } else if(hashParams.get(param.getId()).getClass().equals(String.class)){
                paramValue = hashParams.get(param.getId()).toString();
            } else if(hashParams.get(param.getId()).getClass().equals(Integer.class)){
                paramValue = hashParams.get(param.getId()).toString();
            } else if(hashParams.get(param.getId()) instanceof String[]){
                if(((String[])hashParams.get(param.getId())).length == 0){
                    paramValue = "Non Renseign�";
                }
                for(int i = 0;i < ((String[])hashParams.get(param.getId())).length;i++){
                    paramValue += (i!=0?", ":"")+((String[])hashParams.get(param.getId()))[i];
                }
            } else {
                paramValue = hashParams.get(param.getId()).getClass().getName();
            }
            rapportWriter.writeElement("Param",new Pair("Name",param.getName()),new Pair("Value",paramValue));
        }
        return params.toString();
    }
    
    public void writeControlConfigFile() throws IOException {
        File output = new File(documentModel.getBaseDirectory(),documentModel.getControlConfigFile());
        XmlOutputter outputter = new XmlOutputter(new FileOutputStream(output),"ISO-8859-1");
        cm.marshall(outputter);
    }
    public int getControlStatus() { return status; }
    public DocumentModel getDocumentModel() { return documentModel; }
    public File getFichierEnrichi() throws IllegalStateException {
        if(status==STATUS_NOT_CONTROLLED) throw new IllegalStateException("You must run control before calling getFichierEnrichi()");
        return fichierEnrichi;
    }
    public File getRapport() throws IllegalStateException {
        if(status==STATUS_NOT_CONTROLLED) throw new IllegalStateException("You must run control before calling getRapport()");
        return rapport;
    }
    public String getIdRapport() { return IDrapport; }
    public void setControlProgressListener(ControlProgressListener cpl) { this.cpl=cpl; }
    public void setFichierEnrichiLocation(String absoluteFileName) throws IllegalStateException {
        if(status!=STATUS_NOT_CONTROLLED) throw new IllegalStateException("You must not change fichierEnrichi location after control");
        fichierEnrichiLocation=absoluteFileName;
    }
    public void setRapportLocation(String absoluteFileName) throws IllegalStateException {
        if(status!=STATUS_NOT_CONTROLLED) throw new IllegalStateException("You must not change rapport location after control");
        rapportLocation=absoluteFileName;
    }
    public ControlContentHandler getControlContentHandler(DocumentModel dm,DocumentControlModel docCtrlMod,Hashtable<String,Object> parameters,JFrame frame,String file) {
        return new ControlContentHandler(dm,docCtrlMod,parameters,frame,file);
    }
    /**
     * Permet de renvoyer les informations clefs sur le rapport genere
     */
    public static class Triplet{
        public String iDrapport;
        public String codeCollectivite;
        public String codeBudget;
        public Triplet(String idRapport,String codColl,String codBud){
            super();
            this.iDrapport=idRapport;
            this.codeCollectivite=codColl;
            this.codeBudget=codBud;
        }
    }
}
