/* XMLDoc.java
 * This class serves as a single point for constructing an XML Document object given the file name.
 *
 *@author Sada, Manmohan
 *@date March 2003
 *@version 1.4
 *@company WERU-GMPRC-USDA
 */
package usda.weru.mcrew;

import de.schlichtherle.io.File;

import de.schlichtherle.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.logging.Level;
import javax.xml.parsers.*;

import org.apache.log4j.Logger;
import org.w3c.dom.*;
import org.w3c.dom.traversal.*;
import org.xml.sax.helpers.*;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
 * Class that declares the different types of documents needed for XML documents 
 * to be proccessed and be able to parse through correctly in the application.
 */
public class XMLDoc {

    private static final Logger LOGGER = Logger.getLogger(XMLDoc.class);
    /**
     * This array of string holds the different DTD file names to be used as reference 
     * while processng the XML documents in order to structure the WEPS/MCREW data
     * to be used for analysis purpose in the application.
     */
    private static final String[] defaultDTDfiles = {"identity.dtd", "comment.dtd", "operation_db.dtd", "crop_db.dtd", "weps_man_db.dtd"};
    /**
     * This DTD file describes the type of data for each element that would be needed
     * for the WEPS management database.
     */
    private static final String sWepsManDBDTDfile = "weps_man_db.dtd";
    /**
     * This XSL style sheet file for the WEPS management data that describes the
     * way it needs to be tranformed to show each element from the XML file on an
     * HTML page.
     */
    private static final String sWepsManDBXSLfile = "weps_man_db.xsl";
    /**
     * This XSL style sheet file for the operation database that describes the
     * way it needs to be tranformed to show each element from the XML file on an
     * HTML page.
     */
    private static final String sOperationDBXSLfile = "operation_db.xsl";
    /**
     * This XSL style sheet file for the crop database that describes the
     * way it needs to be tranformed to show each element from the XML file on an
     * HTML page.
     */
    private static final String sCropDBXSLfile = "crop_db.xsl";
    /**
     * Tell what the target of the XML style sheet will be.
     */
    private static final String sStyleSheetTarget = "xml-stylesheet";
    /**
     * defines where the data for the style sheet would reside. Gives the link
     * for .html pages to be created during that time.
     */
    private static final String sStyleSheetData = "type=\"text/xsl\" href=";

    /**
     * Tells the index at which the operation DTD file in the array reside.
     */
    private static final int kOperationDBDTD = 2;
    /**
     * Tells the index at which the crop DTD file in the array reside.
     */
    private static final int kCropDBDTD = 3;
    /**
     * Tells the index at which the WEPS management DTD file in the array reside.
     */
    public static final int kWepsManDBDTD = 4;
    //private static DocumentBuilderFactory mFactory;
    
    
    
    private static DocumentBuilder createBuilder(){
        try{
            DocumentBuilderFactory mFactory = DocumentBuilderFactory.newInstance();
            mFactory.setIgnoringElementContentWhitespace(true); // Ignore whitespace
            mFactory.setIgnoringComments(true); // Ignore comments
            mFactory.setValidating(true);
            mFactory.setNamespaceAware(true);

            DocumentBuilder mParser = mFactory.newDocumentBuilder();
            mParser.setErrorHandler(new McrewXMLHandler());
            mParser.setEntityResolver(new McrewEntityResolver());

            return mParser;
        }
        catch(Exception e){
            LOGGER.error("Unable to create XML DocumentBuilder", e);
            return null;
        }
    }


    /**
     * Returns a newly constructed document object
     * @param pFilename Name to be used in creating/constructing a new DOM file.
     * @return The newly constructed DOM file
     */
    public static Document getDocument(String pFilename) {
        
        try {

            DocumentBuilder builder = createBuilder();


            if (builder != null) {
                File file = null;


                if ((new File(pFilename)).isAbsolute()) {
                    file = new File(pFilename);
                }
                else {
                    file = new File(".", pFilename);
                }


                FileInputStream is = new FileInputStream(file);                                
                                
                Document doc = builder.parse(is);


                return doc;
            }
            else {
            }
        }

        catch (SAXException e) {
            LOGGER.error("Error reading file \"" + pFilename + "\"", e);
        }
        catch (IOException e) {
            LOGGER.error("Error reading file \"" + pFilename + "\"", e);
        }
        catch (IllegalArgumentException e) {
            LOGGER.error("Error reading file \"" + pFilename + "\"", e);

        }
        return null;
    }

    /**
     * Returns the document type for one of the operation, crop or management files
     */
    private static DocumentType getDocType(DOMImplementation domImpl, String pObjectName) {
        DocumentType docType = null;

        if (pObjectName.equals(XMLConstants.smanagement_template)) {
            docType = domImpl.createDocumentType(XMLConstants.sWepsManDBRootName, null, defaultDTDfiles[kWepsManDBDTD]);
        //return wepsmandbDocType;
        }
        else if (pObjectName.equals(XMLConstants.soperation)) {
            docType = domImpl.createDocumentType(XMLConstants.sOperationDBRootName, null, defaultDTDfiles[kOperationDBDTD]);
        //return wepsmandbDocType;
        }
        else if (pObjectName.equals(XMLConstants.scrop)) {
            docType = domImpl.createDocumentType(XMLConstants.sCropDBRootName, null, defaultDTDfiles[kCropDBDTD]);
        //return wepsmandbDocType;
        }
        else if (pObjectName.equals(XMLConstants.smanagement_skeleton)) {
            docType = domImpl.createDocumentType(XMLConstants.smanagement, null, null);
        }
        return docType;
    }

    /**
     * Creates an XML document with the required element name and data following the 
     * DTD and XSl guidelines for arranging the data in pre-described manner according 
     * to the standard DOM implementations.
     * @param pDocumentName The name with which the document should be created and saved.
     * @return The newly constructed DOM file
     */
    public static Document createDocument(String pDocumentName) {
        DocumentType docType;
        Document doc = null;
        ProcessingInstruction styleSheet;

        DocumentBuilder builder = createBuilder();
        if(builder == null){
            return null;
        }
        DOMImplementation domImpl = builder.getDOMImplementation();
        if(domImpl == null){
            return null;
        }

        docType = getDocType(domImpl, pDocumentName);
        if (docType == null) {
            //System.err.println("XMLDoc:createDocument():" + "DocType for " + pDocumentName + " is null");
            return null;
        }

        if (pDocumentName.equals(XMLConstants.smanagement_template)) {
            doc = domImpl.createDocument(null, XMLConstants.sWepsManDBRootName, docType);
            styleSheet = doc.createProcessingInstruction(sStyleSheetTarget, sStyleSheetData + "\"" + sWepsManDBXSLfile + "\"");

        }
        else if (pDocumentName.equals(XMLConstants.soperation)) {
            doc = domImpl.createDocument(null, XMLConstants.sOperationDBRootName, docType);
            styleSheet = doc.createProcessingInstruction(sStyleSheetTarget, sStyleSheetData + "\"" + sOperationDBXSLfile + "\"");

        }
        else if (pDocumentName.equals(XMLConstants.scrop)) {
            doc = domImpl.createDocument(null, XMLConstants.sCropDBRootName, docType);
            styleSheet = doc.createProcessingInstruction(sStyleSheetTarget, sStyleSheetData + "\"" + sCropDBXSLfile + "\"");
        }
        else if (pDocumentName.equals(XMLConstants.smanagement_skeleton)) {
            doc = domImpl.createDocument(null, XMLConstants.smanagement, docType);
            styleSheet = null;
        }
        else {
            return null;
        }

        Node root = doc.getDocumentElement();
        if (styleSheet != null) {
            doc.insertBefore(styleSheet, root);
        }

        return doc;
    }

    /* 
     */
    /**
     * This method gives the text value of the text child for a given Node.
     * Eg. For a node testNode, use this function to extract its text data as
     * XMLDoc.getTextData(testNode);
     * @param pNode The node whose text data is requested.
     * @return Thetext data value itself.
     */
    public static String getTextData(Node pNode) {
        DocumentTraversal traversable;

        if (pNode.getOwnerDocument() == null) {
            traversable = (DocumentTraversal) pNode;
        }
        else {
            traversable = (DocumentTraversal) pNode.getOwnerDocument();
        }

        TreeWalker walker = traversable.createTreeWalker(pNode, NodeFilter.SHOW_ALL, null, false);
        Node nodeChild = walker.firstChild(); // nodeChild is 'identity' or 'actioname' or 'paramdefn'
        if (nodeChild != null) {
            if (nodeChild.hasChildNodes()) {
                //System.out.println("The node "+ pNode.getNodeName()+ " seems to have child nodes and not just a text node" + " @XMLDoc:getTextData()");
                return null;
            }
            String textData = nodeChild.getNodeValue();
            return XMLDoc.getStringFromXMLSafeString(textData.trim()); // Remove any adjacent empty spaces
        }
        ////System.out.println("The node "+ pNode.getNodeName()+ " does not have any text data node" + " @XMLDoc:getTextData()");
        return null;
    }

    /**
     * This is a convenience method to set the text data of a text child for a given Node.
     * A new node can only be created w.r.t a Document, so we have the document parameter 
     * passed as argument to this method.
     * @param pNode Thenode whose data is to be set to the new value.
     * @param pText The new value passed as argument.
     * @param doc The document w.r.t whom the node is being created and the value set.
     */
    public static void setTextData(Node pNode, String pText, Document doc) {
        ////System.out.println("XMLDoc :setTextData : The node name is : " + pNode.getNodeName() + "   The Node Value  is : " + pText);
        Node textNode = doc.createTextNode(getXMLSafeString(pText));
        pNode.appendChild(textNode);

    }

    public static String getXMLSafeString(String s) {
        if (s == null) {
            return null;
        }
        String safe = s;
        safe = safe.replaceAll("&", "&amp;");
        safe = safe.replaceAll("\"", "&quot;");
        safe = safe.replaceAll("'", "&apos;");
        safe = safe.replaceAll("<", "&lt;");
        safe = safe.replaceAll(">", "&gt;");
        return safe;
    }

    public static String getStringFromXMLSafeString(String safe) {
        if (safe == null) {
            return null;
        }
        String s = safe;
        s = s.replaceAll("&quot;", "\"");
        s = s.replaceAll("&apos;", "'");
        s = s.replaceAll("&lt;", "<");
        s = s.replaceAll("&gt;", ">");
        s = s.replaceAll("&amp;", "&");
        return s;
    }
}

class McrewXMLHandler extends DefaultHandler {

    public McrewXMLHandler() {
        super();
    }

    public void fatalError(SAXParseException exception) {
    //System.out.println("McrewXMLHandler:fatalError() ");

    }
}

/* EntityResolver
 * Resolves the entityies i.e currrenlty this resolves all dtd refences
 */
class McrewEntityResolver implements EntityResolver {

    public InputSource resolveEntity(String publicId, String systemId) {
        
        if (systemId == null) {
            //System.err.println("McrewEntityResolver:resolveEntiity() " + "SystemId is null!!");	
            return null;
        }
        
        String configDirPath = ConfigData.getConfigDir();
        

        File file = new File(systemId);
        String resolveFileName = file.getName();
        
        File file2 = new File(configDirPath, resolveFileName);
        if (file2.exists()){
            try {
                return new InputSource(new java.io.FileInputStream(file2));
            }
            catch (FileNotFoundException ex) {
                //I've already checked for this.
            }
        }
        
        return null;
    }
} //end of class MCrewEntityResolver

