/*
 * Copyright
 *   2007 axYus - www.axyus.com
 *   2005 C.Marchand - christophe.marchand@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 Lesser General Public License as published by
 * the Free Software Foundation; either version 3 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser 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.common.config;

import fr.gouv.finances.cp.xemelios.common.Constants;
import java.io.File;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.TreeSet;
import java.util.Vector;

import org.apache.log4j.Logger;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import fr.gouv.finances.cp.utils.Pair;
import fr.gouv.finances.cp.utils.xml.marshal.InvalidXmlDefinition;
import fr.gouv.finances.cp.utils.xml.marshal.NoeudModifiable;
import fr.gouv.finances.cp.utils.xml.marshal.XmlAttributes;
import fr.gouv.finances.cp.utils.xml.marshal.XmlMarshallable;
import fr.gouv.finances.cp.utils.xml.marshal.XmlOutputter;
import fr.gouv.finances.cp.xemelios.data.DataConfigurationException;

/**
 * Modelise un document
 * @author chm
 */
public class DocumentModel implements NoeudModifiable, EnvironmentDomain, Comparable {
    public static final transient String TAG = "document";
    private static Logger logger = Logger.getLogger(DocumentModel.class);
    private NoeudModifiable _NMParent = null;
    private String configXPath = null;
    
    private Vector<EtatModel> etats;
    private HashMap<String,EtatModel> hEtats;
    private Vector<EnvironmentDomain>vEtats;
    private Vector<String> entetes;
    private Vector<Pair> defaultBudgets;
    private HashMap<String,OptionModel> hDefaultBudgets; // attention, on n'utilise pas de Pair mais un OptionModel pour sa compatibilit� avec l'interface NoeudModifiable.
    private Vector<SpecialKeyModel> specialKeys;
    private HashMap<String,SpecialKeyModel> hSpecialKeys;
    private HashMap<String,EnteteModel> hEnteteModels;
    private String id, extension, schema, titre, balise, referentiel, importClass, indexCreation, libelleExtension, referentielXsltFile, defaultBrowsableEtat, referentielImportTransfo, persistenceConfigFile, globalImportXsltFile, globalUriTransformer;
    private RecherchePaireModel collectivitePath, budgetPath;
    private NamespaceModel namespaces = null;
    private boolean browsable = false,isControlable = false;
    private boolean groupMenuItems = true;
    private String controlConfigFile;
    private ParametersModel parameters;
    private String extendsDocId;
    private String pjNamespaceUri;
    private String displayInMenuIf;
    private int ordrePresentation=1000;
    
    private String baseDirectory = null;
    
    public DocumentModel(String tagName) {
        super();
        etats = new Vector<EtatModel>();
        hEtats = new HashMap<String,EtatModel>();
        vEtats = new Vector<EnvironmentDomain>();
        entetes = new Vector<String>();
        hEnteteModels = new HashMap<String,EnteteModel>();
        defaultBudgets = new Vector<Pair>();
        hDefaultBudgets = new HashMap<String,OptionModel>();
        specialKeys = new Vector<SpecialKeyModel>();
        hSpecialKeys = new HashMap<String,SpecialKeyModel>();
    }
    
    public void addCharacterData(String cData) throws SAXException { }
    public void addChild(XmlMarshallable child, String tagName) throws SAXException {
        if(EnteteModel.TAG.equals(tagName)) {
            EnteteModel em = (EnteteModel)child;
            em.setParentAsNoeudModifiable(this);
            String key = ((EnteteModel)child).getBalise();
            entetes.add(key);
            hEnteteModels.put(em.getId(),em);
        } else if(EtatModel.TAG.equals(tagName)) {
            EtatModel etat = (EtatModel)child;
            etat.setParent(this);
            etat.setParentAsNoeudModifiable(this);
            etats.add(etat);
            vEtats.add(etat);
//        } else if(CopyModel.TAG.equals(tagName)) {
//            CopyModel cm = (CopyModel)child;
//            String key = cm.getTag();
//            copies.add(cm);
//            mapCopies.put(key,cm);
            hEtats.put(etat.getId(), etat);
        } else if("budget-path".equals(tagName)) {
            budgetPath = (RecherchePaireModel)child;
            budgetPath.setParentAsNoeudModifiable(this);
        } else if("collectivite-path".equals(tagName)) {
            collectivitePath = (RecherchePaireModel)child;
            collectivitePath.setParentAsNoeudModifiable(this);
        } else if("default-budget".equals(tagName)) {
            OptionModel om = (OptionModel)child;
            om.setParentAsNoeudModifiable(this);
            Pair p =new Pair(om.getValue(),om.getLibelle());
            defaultBudgets.add(p);
            hDefaultBudgets.put(om.getValue(), om);
        } else if(SpecialKeyModel.TAG.equals(tagName)) {
            SpecialKeyModel skm = (SpecialKeyModel)child;
            skm.setParentAsNoeudModifiable(this);
            specialKeys.add(skm);
            hSpecialKeys.put(skm.getId(), skm);
        } else if(NamespaceModel.TAG.equals(tagName)) {
            namespaces = (NamespaceModel)child;
            namespaces.setParentAsNoeudModifiable(this);
        } else if (ParametersModel.TAG.equals(tagName)) {
            parameters = (ParametersModel)child;
            parameters.setParentAsNoeudModifiable(this);
        }
    }
    public XmlMarshallable getAttributes(XmlAttributes attributes) throws SAXException {
        id=(attributes.getValue("id")!=null?attributes.getValue("id"):id);
        extension=(attributes.getValue("extension")!=null?attributes.getValue("extension"):extension);
        schema=(attributes.getValue("schema")!=null?attributes.getValue("schema"):schema);
        titre=(attributes.getValue("titre")!=null?attributes.getValue("titre"):titre);
        balise=(attributes.getValue("balise")!=null?attributes.getValue("balise"):balise);
        referentiel=(attributes.getValue("referentiel")!=null?attributes.getValue("referentiel"):referentiel);
        importClass=(attributes.getValue("import-class")!=null?attributes.getValue("import-class"):importClass);
        indexCreation=(attributes.getValue("index-creation")!=null?attributes.getValue("index-creation"):indexCreation);
        libelleExtension=(attributes.getValue("libelle-extension")!=null?attributes.getValue("libelle-extension"):libelleExtension);
        referentielXsltFile=(attributes.getValue("referentiel-xslt-file")!=null?attributes.getValue("referentiel-xslt-file"):referentielXsltFile);
        if(attributes.getValue("browsable")!=null)
            browsable=attributes.getBooleanValue("browsable");
        if(attributes.getValue("isControlable")!=null)
            isControlable=attributes.getBooleanValue("isControlable");
        defaultBrowsableEtat = (attributes.getValue("default-browsable-etat")!=null?attributes.getValue("default-browsable-etat"):defaultBrowsableEtat);
        referentielImportTransfo=(attributes.getValue("referentiel-import-xslt")!=null?attributes.getValue("referentiel-import-xslt"):referentielImportTransfo);
        persistenceConfigFile = (attributes.getValue("persistence-config")!=null?attributes.getValue("persistence-config"):persistenceConfigFile);
        if(attributes.getValue("menu-grouping")!=null)
            groupMenuItems=("group".equals(attributes.getValue("menu-grouping")));
        controlConfigFile = (attributes.getValue("control-config-file")!=null?attributes.getValue("control-config-file"):controlConfigFile);
        pjNamespaceUri = (attributes.getValue("pj-namespace-uri")!=null?attributes.getValue("pj-namespace-uri"):pjNamespaceUri);
        // celle-la n'est jamais heritee
        setDisplayInMenuIf(attributes.getValue("displayInMenuIf"));
        if(attributes.getValue("ordre-presentation")!=null) ordrePresentation=attributes.getIntValue("ordre-presentation");
        globalImportXsltFile=(attributes.getValue("global-import-xslt-file")!=null?attributes.getValue("global-import-xslt-file"):globalImportXsltFile);
        globalUriTransformer=(attributes.getValue("global-uri-transformer")!=null?attributes.getValue("global-uri-transformer"):globalUriTransformer);
        return this;
    }
    public void marshall(XmlOutputter output) {	throw new Error("Not yet implemented"); }
    public void validate() throws InvalidXmlDefinition {
        // namespaces
        if(namespaces==null) throw new InvalidXmlDefinition("namespaces is required.");
        namespaces.validate();
        // special-keys
        if(specialKeys.size()>=3) throw new InvalidXmlDefinition("<"+TAG+"> must not have more than 3 <"+SpecialKeyModel.TAG+"/>.");
        TreeSet<Integer> spIndices = new TreeSet<Integer>();
        for(SpecialKeyModel skm:specialKeys) {
            spIndices.add(new Integer(skm.getPos()));
            skm.validate();
        }
        if(spIndices.size()!=specialKeys.size()) throw new InvalidXmlDefinition("//special-key/@pos must be uniques in a <"+TAG+"> element.");
        int i=1;
        for(Integer count:spIndices) {
            if(count.intValue()!=i) throw new InvalidXmlDefinition("//special-key/@pos attributes must start at 1 and be consecutives");
            i++;
        }
        // default-budget
        for(OptionModel om:hDefaultBudgets.values()) om.validate();
        if(collectivitePath!=null) collectivitePath.validate();
        if(budgetPath!=null) budgetPath.validate();
        if(parameters!=null) parameters.validate();
        for(EtatModel em:etats) em.validate();
        
        // verification de la presence des fichiers lies
        if(persistenceConfigFile!=null) {
            File f = new File(new File(getBaseDirectory()),persistenceConfigFile);
            if(!f.exists()) throw new InvalidXmlDefinition("file "+f.getAbsolutePath()+" can not be found");
        }
        if(referentielXsltFile!=null) {
            File f = new File(new File(getBaseDirectory()),referentielXsltFile);
            if(!f.exists()) throw new InvalidXmlDefinition("file "+f.getAbsolutePath()+" can not be found");
        }
        if(controlConfigFile!=null) {
            File f = new File(new File(getBaseDirectory()),controlConfigFile);
            if(!f.exists()) throw new InvalidXmlDefinition("file "+f.getAbsolutePath()+" can not be found");
        }
    }
    
    public String getBalise() {return balise;}
    public Vector<EtatModel> getEtats() {return etats;}
    public String getExtension() {return extension;}
    public String getId() {return id;}
    public String getIdValue() { return getId(); }
    
    public String getReferentiel() {return referentiel;}
    public String getSchema() {return schema;}
    public String getTitre() {return titre;}
    public String getImportClass() { return importClass; }
    public boolean isControlable(){ return isControlable;}
    
    public EtatModel getEtatById(String etatId) {
        for(Enumeration<EtatModel> enumer=etats.elements();enumer.hasMoreElements();) {
            EtatModel etat = enumer.nextElement();
            if(etat.getId().equals(etatId)) return etat;
        }
        return null;
    }
    public EtatModel getEtatByTagName(String tagName) {
        for(Enumeration<EtatModel> enumer=etats.elements();enumer.hasMoreElements();) {
            EtatModel etat = enumer.nextElement();
            if(etat.getBalise().equals(tagName)) return etat;
        }
        return null;
    }
    public Vector<String> getEntetes() {
        return entetes;
    }
    public RecherchePaireModel getBudgetPath() {
        return budgetPath;
    }
    public RecherchePaireModel getCollectivitePath() {
        return collectivitePath;
    }
    public String getLibelleExtension() {
        return libelleExtension==null ? getTitre() : libelleExtension;
    }
    public Hashtable<String, EnteteModel> getMap() {
        Hashtable<String, EnteteModel> tmp = new Hashtable<String, EnteteModel> ();
        for (String s:hEnteteModels.keySet()) {
            tmp.put(s,hEnteteModels.get(s));
        }
        return tmp;
    }
    public String getIndexCreation() {
        return indexCreation;
    }
    @Override
    public String toString() { return titre; }
    public Vector<Pair> getDefaultBudgets() { return defaultBudgets; }
    public String getReferentielXsltFile() { return referentielXsltFile; }
    @Override
    public DocumentModel clone() {
        DocumentModel dm = new DocumentModel(TAG);
        for(EtatModel em:this.etats) { try { dm.addChild(em.clone(dm),EtatModel.TAG); } catch(Throwable t) {}}
        for(String s:this.hEnteteModels.keySet()) { try {dm.addChild(this.hEnteteModels.get(s).clone(), EnteteModel.TAG);} catch (Throwable t) {}}
        dm.id = this.id;
        dm.extension = this.extension;
        dm.schema = this.schema;
        dm.titre = this.titre;
        dm.balise = this.balise;
        dm.referentiel = this.referentiel;
        dm.importClass = this.importClass;
        dm.indexCreation = this.indexCreation;
        dm.libelleExtension = this.libelleExtension;
        dm.referentielXsltFile = this.referentielXsltFile;
        dm.browsable = this.browsable;
        dm.isControlable = this.isControlable;
        dm.defaultBrowsableEtat=defaultBrowsableEtat;
        dm.extendsDocId=this.extendsDocId;
        dm.setDisplayInMenuIf(this.getDisplayInMenuIf());
        try {dm.addChild(this.collectivitePath.clone(), "collectivite-path");} catch (Throwable t) {}
        try {dm.addChild(this.budgetPath.clone(), "budget-path");} catch (Throwable t) {}
        dm.persistenceConfigFile=this.persistenceConfigFile;
        for (String s:this.hDefaultBudgets.keySet()) {try {dm.addChild(this.hDefaultBudgets.get(s).clone(), "default-budget");} catch (Throwable t) {}}
        dm.groupMenuItems=this.groupMenuItems;
        try {dm.addChild(this.namespaces.clone(), NamespaceModel.TAG);} catch (Throwable t) {}
        for(SpecialKeyModel sp:specialKeys) {try {dm.addChild(sp.clone(), sp.TAG);} catch (Throwable t) {}}
        dm.controlConfigFile=this.controlConfigFile;
        try {dm.addChild(this.parameters.clone(), ParametersModel.TAG);} catch (Throwable t) {}
        dm.baseDirectory=baseDirectory;
        dm.pjNamespaceUri=pjNamespaceUri;
        dm.globalImportXsltFile=this.globalImportXsltFile;
        dm.globalUriTransformer=this.globalUriTransformer;
        return dm;
    }
    public Vector<SpecialKeyModel> getSpecialKeys() {
        return specialKeys;
    }
    public boolean isBrowsable() {
        return browsable;
    }
    public EtatModel getDefaultBrowsableEtat() {
        if(defaultBrowsableEtat!=null) return getEtatById(defaultBrowsableEtat);
        return etats.elementAt(0);
    }
    /**
     * @return
     * @deprecated Now, xslt to apply before import are defined in persistence-config
     */
    @Deprecated
    public String getReferentielImportTransfo() {
        return referentielImportTransfo;
    }
    public String getPersistenceConfigFile() {
        return persistenceConfigFile;
    }
    public void setPersistenceConfigFile(String persistenceConfigFile) {
        this.persistenceConfigFile = persistenceConfigFile;
    }
    
    public NamespaceModel getNamespaces() {
        return namespaces;
    }
    public boolean isGroupMenuItem() { return groupMenuItems; }
    
    public EnvironmentDomain getChildAt(int domain,int pos) { return etats.elementAt(pos); }
    
    public boolean hasEnvironment(int domain) { return false; }
    
    public Enumeration<VariableModel> getVariables(int domain) { return null; }
    
    public int getChildCount(int domain) {
        int count = 0;
        for(EtatModel em:etats) {
            if(em.getDisplayInMenuIf()!=null) {
                if("true".equals(System.getProperty(em.getDisplayInMenuIf()))) count++;
            } else {
                count++;
            }
        }
        return count;
    }
    
    public Enumeration<EnvironmentDomain> children(int domain) {
        Vector<EnvironmentDomain> childs = new Vector<EnvironmentDomain>();
        for(EtatModel em:etats) {
            if(em.getDisplayInMenuIf()!=null) {
                if("true".equals(System.getProperty(em.getDisplayInMenuIf()))) childs.add(em);
            } else {
                childs.add(em);
            }
        }
        return childs.elements();
    }
    
    public Object getValue(final String path) throws DataConfigurationException {
        String sTmp;
        if(path.startsWith("/")) sTmp = path.substring(1); else sTmp = path;
        if(sTmp.startsWith(EtatModel.TAG)) {
            int pos = sTmp.indexOf('/');
            String next = sTmp.substring(pos);
            String condition = sTmp.substring(EtatModel.TAG.length(),pos);
            if(condition.startsWith("[") && condition.endsWith("]")) condition = condition.substring(1,condition.length()-1).trim();
            if(condition.startsWith("@id=")) {
                String etatId = condition.substring(5,condition.length()-1);
                EtatModel em = getEtatById(etatId);
                return em.getValue(next);
            } else {
                throw new DataConfigurationException("can only get etat by id");
            }
        } else {
            throw new DataConfigurationException("from document, you can only access to etat(s)");
        }
    }
    public void setValue(final String path, final Object value) throws DataConfigurationException {
        String sTmp;
        if(path.startsWith("/")) sTmp = path.substring(1); else sTmp = path;
        if(sTmp.startsWith(EtatModel.TAG)) {
            int pos = sTmp.indexOf('/');
            String next = sTmp.substring(pos);
            String condition = sTmp.substring(EtatModel.TAG.length(),pos);
            if(condition.startsWith("[") && condition.endsWith("]")) condition = condition.substring(1,condition.length()-1).trim();
            if(condition.startsWith("@id=")) {
                String etatId = condition.substring(5,condition.length()-1);
                EtatModel em = getEtatById(etatId);
                em.setValue(next,value);
            } else {
                throw new DataConfigurationException("can only get etat by id");
            }
        } else {
            throw new DataConfigurationException("from document, you can only access to etat(s)");
        }
    }
    
    public Vector<ElementModel> getElementsById(String elementId) {
        Vector<ElementModel> ret = new Vector<ElementModel>();
        for(EtatModel em:etats) {
            ElementModel el = em.getElementById(elementId);
            if(el!=null) ret.add(el);
        }
        return ret;
    }
    
    public String getControlConfigFile() { return controlConfigFile; }
    
    public void setBudgetPath(
            RecherchePaireModel budgetPath) {
        this.budgetPath = budgetPath;
    }
    
    public void setCollectivitePath(
            RecherchePaireModel collectivitePath) {
        this.collectivitePath = collectivitePath;
    }
    
    
    public ParametersModel getParameters() {
        return parameters;
    }
    
    public void setParentAsNoeudModifiable(NoeudModifiable p) {
        this._NMParent = p;
    }
    public NoeudModifiable getParentAsNoeudModifiable() {
        return this._NMParent;
    }
    public NoeudModifiable getChildAsNoeudModifiable(String tagName, String id) {
        if(EnteteModel.TAG.equals(tagName)) {
            return hEnteteModels.get(id);
        } else if(EtatModel.TAG.equals(tagName)) {
            return hEtats.get(id);
        } else if("budget-path".equals(tagName)) {
            return budgetPath;
        } else if("collectivite-path".equals(tagName)) {
            return collectivitePath;
//        } else if(DeleteFilterModel.TAG.equals(tagName)) {
//           deleteFilters.add((DeleteFilterModel)child);
        } else if("default-budget".equals(tagName)) {
            return hDefaultBudgets.get(id);
        } else if(SpecialKeyModel.TAG.equals(tagName)) {
            return hSpecialKeys.get(id);
        } else if(NamespaceModel.TAG.equals(tagName)) {
            return namespaces;
        } else if (ParametersModel.TAG.equals(tagName)) {
            return parameters;
        } else {
            return null;
        }
    }
    
    public void modifyAttr(String attrName, String value) {
        // TODO Raccord de m�thode auto-g�n�r�
        
    }
    
    public void modifyAttrs(Attributes attrs) {
        try {
            getAttributes(new XmlAttributes(attrs));
        } catch (Exception e) {
            logger.error("Erreur lors de la mise � jour des attributs : "+e);
        }
    }
    
    public String[] getChildIdAttrName(String childTagName) {
        if(EnteteModel.TAG.equals(childTagName)) {
            return new String[]{"id"};
        } else if(EtatModel.TAG.equals(childTagName)) {
            return new String[]{"id"};
        } else if("default-budget".equals(childTagName)) {
            return new String[]{"value"};
        } else if(SpecialKeyModel.TAG.equals(childTagName)) {
            return new String[]{"id"};
        } else {
            return null;
        }
    }
    
    public void resetCharData() {
    }
    
    public void setVEtats(Vector<EtatModel> v) {
        this.etats = v;
    }
    public void setHEtats(HashMap<String, EtatModel> h) {
        this.hEtats = h;
    }
    public HashMap<String, EtatModel> getHEtats() {
        return hEtats;
    }
    public void setBaseDirectory(String baseDir) { this.baseDirectory = baseDir; }
    public String getBaseDirectory() { return baseDirectory; }
    public void setExtendedDocId(String docId) { extendsDocId = docId; }
    public String getExtendedDocId() { return extendsDocId; }
    
    public Element createSmallDOM(Document doc) {
        Element ret = doc.createElementNS(Constants.CONFIG_NS_URI,"conf:"+TAG);
        ret.setAttribute("id",getId());
        ret.setAttribute("libelle",getTitre());
        for(EtatModel em:getEtats()) {
            Element etat = em.createSmallDOM(doc);
            ret.appendChild(etat);
        }
        return ret;
    }
    public DocumentsModel getParent() { return (DocumentsModel)_NMParent; }
    public String getPjNamespaceUri() { return pjNamespaceUri; }

    public String getDisplayInMenuIf() {
        return displayInMenuIf;
    }

    public void setDisplayInMenuIf(String displayInMenuIf) {
        this.displayInMenuIf = displayInMenuIf;
    }

    public int compareTo(Object o) {
        int ret = -1000;
        if(o instanceof DocumentModel) {
            DocumentModel dm = (DocumentModel)o;
            ret = ordrePresentation-dm.ordrePresentation;
            if(ret==0) ret = getTitre().compareTo(dm.getTitre());
        }
        return ret;
    }
    
    public String getConfigXPath() {
        if(configXPath==null) {
            if(getParentAsNoeudModifiable()!=null) configXPath = getParentAsNoeudModifiable().getConfigXPath();
            else configXPath = "";
            configXPath+="/"+TAG+"[@id='"+getId()+"']";
        }
        return configXPath;
    }
    public String getGlobalImportXsltFile() {
        return globalImportXsltFile;
    }
    public String getGlobalUriTransformer() { return globalUriTransformer; }
}
