package com.fujitsu.iflowqa.testframe;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.fujitsu.iflow.common.DOMUtils;
import com.fujitsu.iflow.common.JSConversions;

/**
* CTF5 Compiler
*
* The Component Test Framework 2005 (CTF5) is driven by ant
* scripts that call the individual test cases.  This compiler
* reads the XML file that is the ant script, and generates
* a Java class that does the same thing.  The result is a
* pure java solution that tests the same conditions.
*/
public
class CTF5Compiler
{
    Vector allTestElements;
    Hashtable allTestClasses;
    String currentSuite;
    String currentTask;

    class TestDefStruct
    {
        public Element testElement;
        public String testSuite;
        public String testTask;
    }

    public CTF5Compiler()
    {
        allTestElements = new Vector();
        allTestClasses = new Hashtable();
        currentSuite = "";
        currentTask = "";
    }

    void convertTest(TestDefStruct tds, Writer out)
        throws Exception
    {
        Element ele = tds.testElement;
        String runParam = ele.getAttribute("run");
        if (runParam == null) {
            throw new Exception("test tag is apparently missing a run attribute");
        }
        int dotpos = runParam.lastIndexOf(".");
        String methodName = runParam.substring(dotpos+1);
        String fullClassName = runParam.substring(0,dotpos);
        dotpos = fullClassName.lastIndexOf(".");
        String className = fullClassName.substring(dotpos+1);
        String classPackage = fullClassName.substring(0, dotpos);

        out.write("\n");
        out.write("        //---- "+tds.testSuite+" ---- "+methodName+" ----\n");
        NodeList nl = ele.getElementsByTagName("param");
        int len = nl.getLength();
        for (int i=0; i<len; i++) {
            Element param = (Element) nl.item(i);
            String paramName = param.getAttribute("name");
            String paramValue= param.getAttribute("value");
            if (paramValue.startsWith("${")) {
                paramValue = "tr.getRequiredParameter("+JSConversions.quote4JS(paramValue.substring(2,paramValue.length()-1))+")";
            }
            else {
                paramValue = JSConversions.quote4JS(paramValue);
            }
            out.write("        TestEnvironment.setTestParam("
                    +JSConversions.quote4JS(paramName)+","
                    +paramValue+");\n");
        }
        out.write("        i"+className+"."+methodName+"();\n");
    }

    void searchForTest(Element ele)
        throws Exception
    {
        String tagName = ele.getTagName();

        if (tagName.equals("test")) {
            TestDefStruct tds = new TestDefStruct();
            tds.testElement = ele;
            tds.testSuite = currentSuite;
            tds.testTask = currentTask;

            allTestElements.add(tds);
            String runParam = ele.getAttribute("run");
            int dotpos = runParam.lastIndexOf(".");
            String fullClassName = runParam.substring(0,dotpos);
            // we just need to know how many different classes are involved
            // so use a hashtable to collect this, does not matter what gets
            // stored as the object of the hashtable.
            allTestClasses.put(fullClassName, fullClassName);
            return;
        }
        else if (tagName.equals("taskdef")) {
            currentTask = ele.getAttribute("name");
        }
        else if (tagName.equals("suite")) {
            currentSuite = ele.getAttribute("name");
        }
        NodeList nl = ele.getChildNodes();
        int len = nl.getLength();
        for (int i=0; i<len; i++)
        {
            Node n = nl.item(i);
            if (n instanceof Element) {
                searchForTest((Element)n);
            }
        }
    }


    public
    void convertOneFile(String path)
        throws Exception
    {
        File thePath = new File(path);
        if (!thePath.exists()) {
            System.out.println("The path provided does not exist! ("+path+")");
            return;
        }

        System.out.println("Reading: "+path);
        Document dom = DOMUtils.parseXMLInputStream(new FileInputStream(thePath));
        searchForTest(dom.getDocumentElement());

        int slashpos = path.lastIndexOf("/")+1;
        String basePath = path.substring(0, slashpos);
        String fileName = path.substring(slashpos);
        String outClassName = fileName.substring(0, fileName.length()-4);

        String newFilePath = basePath + outClassName + "Set.java";

        File outFile = new File(newFilePath);
        Writer fileOut = new OutputStreamWriter(new FileOutputStream(outFile), "UTF-8");
        System.out.println("Writing to: "+newFilePath);

        fileOut.write("/**\n");
        fileOut.write(" * "+outClassName+"Set is a CTF6 test driver\n");
        fileOut.write(" * Gen from "+path+"\n");
        fileOut.write(" * \n");
        fileOut.write(" * This a Java version of all of the test cases converted from XML Ant script.\n");
        fileOut.write(" * The old CTF5 frameword would pass values by placing them into the global\n");
        fileOut.write(" * TestEnvironment class, call the test method with zero parameters, and then\n");
        fileOut.write(" * inside the test reading the parameters back out.\n");
        fileOut.write(" * This has all the problems normally associated with passing by global variable.\n");
        fileOut.write(" * In order to remain compatible, this converted class does the same thing.\n");
        fileOut.write(" * After conversion, each class should be edited, and the test methods changed:\n");
        fileOut.write(" * 1. Test method should be changed to have passed parameters\n");
        fileOut.write(" * 2. Test method should get global parameters directly from the TestRecorder object\n");
        fileOut.write(" *    so these do not have to be passed to every invocation of the test\n");
        fileOut.write(" * 3. The calls should be changed to pass the parameters that way which will make\n");
        fileOut.write(" *    the runTests method much shorter and easier to read.\n");
        fileOut.write(" * 4. The runTests method should be put into the same class with the test methods\n");
        fileOut.write(" *    so that everything about the test is contained in a well defined module.\n");
        fileOut.write(" * When that is done, please delete this driver class.\n");
        fileOut.write(" * \n");
        fileOut.write(" */\n");
        fileOut.write("package com.fujitsu.iflowqa.testframe\n");
        fileOut.write("\n");
        fileOut.write("include com.fujitsu.iflowqa.testframe.TestSet;\n");
        fileOut.write("include com.fujitsu.iflowqa.testframe.TestRecorder;\n");
        Enumeration enum = allTestClasses.keys();
        while (enum.hasMoreElements()) {
            String testClass = (String) enum.nextElement();
            fileOut.write("include "+testClass+";\n");
        }
        fileOut.write("\n");
        fileOut.write("public class "+outClassName+"Set implements TestSet\n");
        fileOut.write("{\n");
        fileOut.write("    //zero parameter constructor\n");
        fileOut.write("    public "+outClassName+"Set()  {}\n");
        fileOut.write("\n");
        fileOut.write("    //This is the one and only method that is called from the CTF6 driver\n");
        fileOut.write("    //Its job is to contain all the information for the test sets\n");
        fileOut.write("    public void runTest(TestRecorder tr)\n");
        fileOut.write("    {\n");
        fileOut.write("\n");

        enum = allTestClasses.elements();
        while (enum.hasMoreElements()) {
            String testClass = (String) enum.nextElement();
            int dotpos = testClass.lastIndexOf(".");
            String classOnly = testClass.substring(dotpos+1);
            fileOut.write("        "+classOnly+" i"+classOnly+" = new "+classOnly+"();\n");
        }
        fileOut.write("\n");

        enum = allTestElements.elements();
        while (enum.hasMoreElements()) {
            TestDefStruct tds = (TestDefStruct) enum.nextElement();
            convertTest(tds, fileOut);
        }

        fileOut.write("    }\n");
        fileOut.write("\n");
        fileOut.write("}\n");
        fileOut.flush();

        System.out.println("DONE: "+newFilePath);
    }

    public
    static
    void main(String[] args)
    {
        if (args.length != 1) {
            System.out.println("CTF Compiler requires a path to the XML file to convert");
            return;
        }
        String path = args[0];
        if (path == null) {
            System.out.println("CTF Compiler requires a path to the XML file to convert");
            return;
        }
        path = path.replace('\\','/');
        if (!path.endsWith("/")) {
            path = path + "/";
        }
        File fileDir = new File(path);
        if (!fileDir.isDirectory()) {
            System.out.println("CTF Compiler requires a path to a flolder that contains XML files: ("+path+")");
            return;
        }
        try {
            CTF5Compiler ctf = new CTF5Compiler();
            File[] children = fileDir.listFiles();
            int num=children.length;
            for (int i=0; i<num; i++) {
                String childName = children[i].getName();
                if (childName.endsWith(".xml")) {
                    ctf.convertOneFile(path+childName);
                }
            }
        }
        catch (Exception e) {
            System.out.println("--------------------------");
            System.out.println("Got an exception:");
            System.out.println(e.toString());
            System.out.println("--------------------------");
        }
    }
}
