/*
 * Scone - The Web Enhancement Framework
 * Copyright (c) 2004 Harald Weinreich, Volkert Buchmann, Frank Wollenweber, Torsten Haß
 *
 * This program 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.
 * 
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package scone.usertesttool;


import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.SAXException;

import scone.util.ErrorLog;

/**
 * This class persists the states of all run tests and holds
 * the highest test number for every xml test description
 *
 * @author Torsten Hass
 * @version 1.0, 08/18/2003
 */
class StatesOfTests {

    private static StatesOfTests _instance = null;
    private ArrayList testDescriptions;     // holds the filenames for each test description xml file without file extension and path
    private ArrayList unfinishedTests;      // holds the filenames for each unfinished test result xml file without file extension and path
    private Document document;
    private String xmlPath;                 // holds the path where the test description xml files are stored in
    private String resultPath;              // holds the path where the test result xml files are stored in 
    
    /**
     * Get a sole instance of BrowserHistory, using the Singleton pattern.
     */
    static StatesOfTests getInstance() {
        if (_instance == null) {
            _instance = new StatesOfTests();
        }
        return _instance;
    }

    private StatesOfTests() {
        testDescriptions = new ArrayList();
        unfinishedTests = new ArrayList();
    }
    
    
    public void setDirs(String xmlPath, String resultPath) {
        // read xml file
        this.xmlPath = xmlPath;
        this.resultPath = resultPath;
        
        File file = new File(forceEndingSlash(resultPath) + "StateOfTests.xml");
        if (file.exists()) {
            // read all test description xml files from StateOfTests.xml
            document = parseXmlFile(forceEndingSlash(resultPath) + "StateOfTests.xml");
            NodeList nodeList = document.getElementsByTagName("testDescription");
            for (int i=0; i<nodeList.getLength(); i++) {
                File file2 = new File(forceEndingSlash(xmlPath)+getAttribByName("name", nodeList.item(i)));
                if (file2.exists()) {
                    testDescriptions.add(new TestDescription(getAttribByName("name", nodeList.item(i)), Integer.parseInt(getAttribByName("lastTestNumber", nodeList.item(i)))));
                }
            }                   
            // read all unfinished test result xml files from StateOfTests.xml
            nodeList = document.getElementsByTagName("unfinishedTest");
            String filename;
            String numberOfTasks;
            String completedTasks;
            for (int i=0; i<nodeList.getLength(); i++) {
                File file2 = new File(forceEndingSlash(xmlPath)+getAttribByName("name", nodeList.item(i)));
                if (file2.exists() && file2.getName().endsWith(".xml")) {
                    filename = getAttribByName("name", nodeList.item(i));
                    numberOfTasks = getAttribByName("numberOfTasks", nodeList.item(i));
                    completedTasks = getAttribByName("completedTasks", nodeList.item(i));
                    try {
                        unfinishedTests.add(new UnfinishedTest(filename, Integer.parseInt(numberOfTasks), Integer.parseInt(completedTasks)));
                    } catch (NumberFormatException e) {
                        // do not add to list if conversion error occures
                    }
                }
            }                   
        } 
        // look for manually added xml files

        File xmlDir = new File(xmlPath);
        if (xmlDir.listFiles() != null) {
            // look for test description xml files
            File[] fileArray = xmlDir.listFiles();
            for (int i = 0; i < fileArray.length; i++) {
                if (fileArray[i].isFile() && fileArray[i].toString().toLowerCase().endsWith(".xml")) {
                    if (containsTestDescription(fileArray[i].toString())) {
                        // write present test descriptions into testDescriptions list 
                        boolean found = false;
                        for (int j=0; j<testDescriptions.size();j++) {
                            found = ((TestDescription)testDescriptions.get(j)).getXmlFilename().equals(fileArray[i].getName().substring(0, fileArray[i].getName().length()-4));
                        }         
                        if (!found) {
                            testDescriptions.add(new TestDescription(fileArray[i].getName().substring(0, fileArray[i].getName().length()-4), 0));
                        }
                        
                    }
                }
            }
        }
        File destDir = new File (resultPath);
        int testNumber = 0;
        if (destDir.listFiles() != null) {
            String testNumString = null;
            for (int i = 0; i < destDir.listFiles().length; i++) {              // for all file in directory
                // look for test result xml files save the highest test number in testDescriptions list
                testNumber = 0;
                for (int j = 0; j < testDescriptions.size(); j++) {             // for all entries in testDescriptons
                    String descName = ((TestDescription)testDescriptions.get(j)).getXmlFilename()+".";
                    if ((destDir.listFiles()[i].getName().startsWith(descName)) && (destDir.listFiles()[i].getName().endsWith(".xml"))) { // if result filename starts with test description xml filename 
                        testNumString = destDir.listFiles()[i].getName().substring(descName.length(), descName.length()+4);
                        if ((testNumString.compareTo("9999") <= 0) && (testNumString.compareTo("0000") >= 0)) {
                            try {
                                if (testNumber < Integer.parseInt(testNumString)) {
                                    testNumber = (Integer.parseInt(testNumString));
                                    ((TestDescription)testDescriptions.get(j)).setLastTestNumber(testNumber);
                                }
                            } catch (Exception e) {
                                // catch ParseInt exceptions
                            }
                        }
                    }
                }
                boolean found = false;
                for (int j=0; j<unfinishedTests.size();j++) {
                    found = destDir.listFiles()[i].getName().toLowerCase().equals(((UnfinishedTest)unfinishedTests.get(j)).getXmlFilename().toLowerCase()+".xml");
                }         
                if (!found) {
                    int[] unfinished = {0,0};
                    if (destDir.listFiles()[i].getName().endsWith(".xml")) {
                        if (testIsUnfinished(destDir.listFiles()[i].toString(), unfinished)) {
                            String filename = destDir.listFiles()[i].getName().substring(0, destDir.listFiles()[i].getName().length()-4);
                            unfinishedTests.add(new UnfinishedTest(filename, unfinished[0], unfinished[1]));
                        }
                    }
                }
            }
        }
        writeToDisk();
    }


    public String getTestDescriptionPath () {
        return forceEndingSlash(xmlPath);
    }
    
    
    public String getTestResultPath() {
        return forceEndingSlash(resultPath);
    }
    

    public int getLastTestNumberFor (String xmlFilename) {
        int result = 0;
        for (int i = 0; i < testDescriptions.size(); i++) { 
            if (((TestDescription)testDescriptions.get(i)).getXmlFilename().equals(xmlFilename)) {
                result = ((TestDescription)testDescriptions.get(i)).getLastTestNumber();
            }
        }
        return result;
    }


    public void setLastTestNumberFor (String xmlFilename, int lastTestNumber) {
        boolean replaced = false;
        for (int i = 0; i < testDescriptions.size(); i++) { 
            if (((TestDescription)testDescriptions.get(i)).getXmlFilename().equals(xmlFilename)) {
                ((TestDescription)testDescriptions.get(i)).setLastTestNumber(lastTestNumber);
                replaced = true;
            }
        }
        // if xmlFilename could not be found, it has to be added
        if (!replaced) {
            testDescriptions.add(new TestDescription (xmlFilename, lastTestNumber));
        }
        writeToDisk();
    }
        

    public String[] getTestDescriptions() {
        String[] descFilenames = new String [testDescriptions.size()];
        for (int i = 0; i < testDescriptions.size(); i++) {
            descFilenames[i] = ((TestDescription)testDescriptions.get(i)).getXmlFilename();
        }
        return descFilenames;
    }
    

    public String[] getUnfinishedTests () {
        String[] unfinishedFiles = new String [unfinishedTests.size()];
        for (int i = 0; i < unfinishedTests.size(); i++) {
            unfinishedFiles[i] = ((UnfinishedTest)unfinishedTests.get(i)).getXmlFilename();
        }
        return unfinishedFiles;
    }
    
    
    public int getNumberOfTasksFromUnfinishedTest (String filename) {
        int result = 0;
        for (int i = 0; i < unfinishedTests.size(); i++) {
            if (((UnfinishedTest)unfinishedTests.get(i)).getXmlFilename().equals(filename)) {
                result = ((UnfinishedTest)unfinishedTests.get(i)).getNumberOfTasks();
            }
        }
        return result;
    }
    
    
    public int getCompletedTasksFromUnfinishedTest (String filename) {
        int result = 0;
        for (int i = 0; i < unfinishedTests.size(); i++) {
            if (((UnfinishedTest)unfinishedTests.get(i)).getXmlFilename().equals(filename)) {
                result = ((UnfinishedTest)unfinishedTests.get(i)).getCompletedTasks();
            }
        }
        return result;
    }
    
    
    public int getCompletedTasksFromUnfinishedTest (int testNumber) {
        int result = 0;
        result = ((UnfinishedTest)unfinishedTests.get(testNumber)).getCompletedTasks();
        return result;
    }
    
    
    public void addToUnfinishedTests (String filename, int numberOfTasks, int completedTasks) {
        boolean found = false;
        for (int i = 0; i < unfinishedTests.size(); i++) {
            if (((UnfinishedTest)unfinishedTests.get(i)).getXmlFilename().equals(filename)) {
                found = true;
                ((UnfinishedTest)unfinishedTests.get(i)).setNumberOfTasks(numberOfTasks);
                ((UnfinishedTest)unfinishedTests.get(i)).setCompletedTasks(completedTasks);
            }
        }
        if (!found) {
            unfinishedTests.add(new UnfinishedTest(filename, numberOfTasks, completedTasks));
        }
        writeToDisk();
    }
    
    
    public void removeFromUnfinishedTests (String filename) {
        for (int i = 0; i < unfinishedTests.size(); i++) {
            if (((UnfinishedTest)unfinishedTests.get(i)).getXmlFilename().equals(filename)) {
                unfinishedTests.remove(i);
            }
        }
        writeToDisk();
    }
        

    public void writeToDisk () {
        Document stateDoc;
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        try {

            DocumentBuilder builder = factory.newDocumentBuilder();
            stateDoc = builder.newDocument();

            Element statesOfTestsNode = stateDoc.createElement("statesOfTests"); 
            stateDoc.appendChild(statesOfTestsNode);
            Text tNode = stateDoc.createTextNode("\n  ");
            statesOfTestsNode.appendChild(tNode);

            Element testDescriptionsNode = stateDoc.createElement("testDescriptions"); 
            statesOfTestsNode.appendChild(testDescriptionsNode);
            tNode = stateDoc.createTextNode("\n");
            testDescriptionsNode.appendChild(tNode);
            // append all recently used testDescriptions
            for (int i = 0; i < testDescriptions.size(); i++) { 
                Text textNode = stateDoc.createTextNode("    ");
                testDescriptionsNode.appendChild(textNode);
                Element testDescriptionNode = stateDoc.createElement("testDescription");
                testDescriptionsNode.appendChild(testDescriptionNode);
                testDescriptionNode.setAttribute("name", ((TestDescription)testDescriptions.get(i)).getXmlFilename());
                testDescriptionNode.setAttribute("lastTestNumber", Integer.toString(((TestDescription)testDescriptions.get(i)).getLastTestNumber()));
                textNode = stateDoc.createTextNode("\n");
                testDescriptionsNode.appendChild(textNode);
            }
            tNode = stateDoc.createTextNode("  ");
            testDescriptionsNode.appendChild(tNode);

            tNode = stateDoc.createTextNode("\n  ");
            statesOfTestsNode.appendChild(tNode);
            
            Element unfinishedTestsNode = stateDoc.createElement("unfinishedTests"); 
            statesOfTestsNode.appendChild(unfinishedTestsNode);
            tNode = stateDoc.createTextNode("\n");
            unfinishedTestsNode.appendChild(tNode);
            // append all recently used testDescriptions
            for (int i = 0; i < unfinishedTests.size(); i++) { 
                Text textNode = stateDoc.createTextNode("    ");
                unfinishedTestsNode.appendChild(textNode);
                Element unfinishedTestNode = stateDoc.createElement("unfinishedTest");
                unfinishedTestsNode.appendChild(unfinishedTestNode);
                unfinishedTestNode.setAttribute("name", ((UnfinishedTest) unfinishedTests.get(i)).getXmlFilename());
                unfinishedTestNode.setAttribute("numberOfTasks", Integer.toString(((UnfinishedTest) unfinishedTests.get(i)).getNumberOfTasks()));
                unfinishedTestNode.setAttribute("completedTasks", Integer.toString(((UnfinishedTest) unfinishedTests.get(i)).getCompletedTasks()));
                textNode = stateDoc.createTextNode("\n");
                unfinishedTestsNode.appendChild(textNode);
            }
            tNode = stateDoc.createTextNode("  ");
            unfinishedTestsNode.appendChild(tNode);

            try {
                TransformerFactory tFactory = TransformerFactory.newInstance();
                Transformer transformer = tFactory.newTransformer();
                
                String fileName = forceEndingSlash(resultPath) + "StateOfTests.xml";
                File outputFile = new File(fileName);
                
                DOMSource source = new DOMSource(stateDoc);
                StreamResult result = new StreamResult(outputFile);
                transformer.transform(source, result);
            } catch (TransformerConfigurationException tce) {
                // Error generated by the parser
                ErrorLog.log(this, "writeToDisk", "Transformer Factory error", tce);
                
                // use the contained exception, if any
                Throwable x = tce;
                if (tce.getException() != null) {
                    x = tce.getException();
                    //ErrorLog.log(this, "writeXmlResults", "Error contained in Transformer Factory error", x);
                }
            } catch (TransformerException te) {
                //Error generated by the parser
                ErrorLog.log(this, "writeToDisk", "Transformation error", te);
                
                // use the contained exception, if any
                Throwable x = te;
                if (te.getException() != null) {
                    x = te.getException();
                    //ErrorLog.log(this, "writeXmlResults", "Error contained in Transformation error", x);
                }
            }
        } catch (ParserConfigurationException pce) {
            // Parser with specified options can't be built
            pce.printStackTrace();
        }

    }                


    public Document parseXmlFile (String fileName) {
        // Parse the xml-File to a Document
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder builder = factory.newDocumentBuilder();
            document = builder.parse( new File(fileName));
        } catch (SAXException sxe) {
            // Error generated during parsing)
            ErrorLog.log(this, "parseXmlFile", "Error generated during parsing", sxe);
            Exception  x = sxe;
            if (sxe.getException() != null)
                x = sxe.getException();
            x.printStackTrace();
            System.out.println("\n\n\nScone: UserTestTool ERROR!\n\nThe XML file\n"+
                fileName+"\nhas an error. The description of the error is:\n"+
                x.getMessage()+"\nPlease fix the error and restart Scone,\n"+
                "to avoid any problems with the \"TEA-UserTestTool\" plugin.\n\n");

        } catch (ParserConfigurationException pce) {
            // Parser with specified options can't be built
            pce.printStackTrace();
            ErrorLog.log(this, "parseXmlFile", "Parser with specified options can't be built", pce);
        } catch (IOException ioe) {
            // I/O error
            ioe.printStackTrace();
            ErrorLog.log(this, "parseXmlFile", "I/O error", ioe);
        }
        return document;
    }        


    private String getText (Node node) {
        String textString = "";
        org.w3c.dom.NodeList nList = node.getChildNodes();
        for (int i = 0; i<nList.getLength(); i++) {
            if (nList.item(i).getNodeType() == org.w3c.dom. Node.TEXT_NODE) {
                textString += nList.item(i).getNodeValue();    
            }
        }
        return textString;
    }
    

    private String getAttribByName (String attrName, Node node) {
        org.w3c.dom.NamedNodeMap nMap = node.getAttributes();
        String result = "";
        int i = 0;
        while (i < nMap.getLength()) {
            if (nMap.item(i).getNodeName().toLowerCase().equals(attrName.toLowerCase())) {
                result = getText(nMap.item(i));
            }
            i++;
        }
        return result;
    }

    protected boolean containsTestDescription(String filename) {
        Document document = parseXmlFile(filename);
        if (document.getElementsByTagName("userTest").getLength() > 0) {
            return true;
        } else {
            return false;
        }
    }
      
    protected boolean testIsUnfinished(String filename, int[] unfinished) {
        Document document = parseXmlFile(filename);
        org.w3c.dom.NodeList nList = document.getElementsByTagName("userTestResult");
        if (nList.getLength() == 1) {
            try {
                unfinished[0] = Integer.parseInt(getAttribByName("numberOfTasks", nList.item(0)));
                unfinished[1] = Integer.parseInt(getAttribByName("completedTasks", nList.item(0)));
                if (unfinished[0] != unfinished[1]) {
                    return true;
                } else {
                    return false;
                }
            } catch (Exception e) {
            }
        } else {
            return false;
        }
        return false;
    }
        

    protected String forceEndingSlash(String path) {
        if (!(path.endsWith("/") || path.endsWith("\\"))) {
            path += "/";
        }
        return path;
    }
     


/**
 * TestDescription objects hold information about one test description xml. 
 * These informations are: xmlFilename (without file extension (".xml")) and
 * the last used test number.
 * 
 * @author Torsten Hass
 * @version 1.0, 7/29/2003
 */
class TestDescription {

    private String  xmlFilename = "";   // holds the filename of the xml test description file
    private int     lastTestNumber = 0; // holds the number of the last test
    
    public TestDescription (String xmlFilename, int lastTestNumber) {
        this.xmlFilename = xmlFilename;
        this.lastTestNumber = lastTestNumber;
    }
    
    public String getXmlFilename() {
        return xmlFilename;
    }
    
    public int getLastTestNumber() {
        return lastTestNumber;
    }
    
    public void setLastTestNumber(int lastTestNumber) {
        this.lastTestNumber = lastTestNumber;
    }
    
}


/**
 * UnfinishedTest objects hold information about one test result xml files 
 * which are not finished (not all tasks were completed). These informations 
 * are: xmlFilename (without file extension (".xml")), the number of tasks, 
 * contained in the corresponding test description xml and the number of 
 * completed tasks.
 * 
 * @author Torsten Hass
 * @version 1.0, 8/22/2003
 */
class UnfinishedTest {

    private String  xmlFilename = "";   // holds the filename of the xml test description file
    private int     numberOfTasks = 0;  // holds the number of tasks contained in the test description file
    private int     completedTasks = 0; // holds the number of completed tasks
    
    public UnfinishedTest (String xmlFilename, int numberOfTasks, int completedTasks) {
        this.xmlFilename = xmlFilename;
        this.numberOfTasks = numberOfTasks;
        this.completedTasks = completedTasks;
    }
    
    public String getXmlFilename() {
        return xmlFilename;
    }
    
    public int getNumberOfTasks() {
        return numberOfTasks;
    }
    
    public int getCompletedTasks() {
        return completedTasks;
    }
    
    public void setNumberOfTasks (int numberOfTasks) {
        this.numberOfTasks = numberOfTasks;
    }
    
    public void setCompletedTasks (int completedTasks) {
        this.completedTasks = completedTasks;
    }
    
}

}
