/********************************************************************************
 *                                                                              *
 *  COPYRIGHT (C) 1997-2005 FUJITSU SOFTWARE CORPORATION.  ALL RIGHTS RESERVED. *
 *                                                                              *
 ********************************************************************************/

package com.fujitsu.iflowqa.testframe;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Hashtable;
import java.util.Properties;

import com.fujitsu.iflow.model.event.ProcessInstanceEvent;
import com.fujitsu.iflow.model.event.ProcessInstanceListener;
import com.fujitsu.iflow.model.util.IflowEnumeration;
import com.fujitsu.iflow.model.wfcore.ListSpec;
import com.fujitsu.iflow.model.workflow.ProcessInstance;
import com.fujitsu.iflow.model.workflow.ProcessInstantiator;
import com.fujitsu.iflow.model.workflow.WFObjectFactory;

/*
 * This will perform the WS-I test for JavaAction calling Command Interface Webservices.
 */

public class WsiJavaActionToCmdIntf extends BPMTestBase implements ProcessInstanceListener
{
    private static final String MAIN_TEMPLATE = "WSI-JA-CMD-TEST.xpdl";
    private static final String INVOKED_TEMPLATE = "WSI-CMD-OBJECT.xpdl";
    private final String testName = "WS-I Test:JavaAction to Command Interface";
    private int processState = -1;
    private int actionWaitTime = 20000;
    private long errorStateTime = -1;
    private String testDirPath;

    public WsiJavaActionToCmdIntf()
    {
    }

    public
    void
    runTests(TestRecorder ntr)
    throws Exception
    {
        try {
            init(ntr);
            login();
            String[] names = {MAIN_TEMPLATE, INVOKED_TEMPLATE};
            testDirPath = tr.getTestDir() + "WSI/JavaActionToCmdIntf/";

            String serverHostPort = tr.getRequiredProperty("buildPath");
            String testClientHost = tr.getProperty("testClientHost",server);
            if (testClientHost.length() == 0) {
                testClientHost = server;
            }

            // set the testClientHost name in the main template
            String mainTemplatePath = testDirPath + MAIN_TEMPLATE;
            updateFile(mainTemplatePath, "<E>\"http://", "/ibpm/jsp/IFWebServices.wsdl", testClientHost);

            long[] planIds = importTemplates(testDirPath, names);
            tr.log("Templates imported. New ids: " + planIds[0] + ", " + planIds[1]);

            // Fix the serverName in monitorConfig.xml
            tr.log("Updating serverName in monitorConfig.xml...");
            String monitorConfigPath = testDirPath + "monitorConfig.xml";
            updateFile(monitorConfigPath, "<wmc:schemeAndHostPort>http://", "<", serverHostPort);

            // Change the logDuration in monitorConfig.xml.
            String monitorLogDurationStr = tr.getProperty("monitorLogDuration","180");
            int monitorLogDuration = Integer.parseInt(monitorLogDurationStr);
            tr.log("monitorConfig.xml updated. Setting monitor logDuration to " + monitorLogDuration + " seconds...");
            updateFile(monitorConfigPath, "<wmc:logDuration>", "<", monitorLogDurationStr);

            // Fix the serverName in analyzerConfig.xml
            tr.log("logDuration set. Updating serverName in analyzerConfig.xml...");
            String analyserConfigPath = testDirPath + "analyzerConfig.xml";
            updateFile(analyserConfigPath, "<wac:wsdlURI>http://", "/iflowws", serverHostPort);

            // Save the WSDL to taskmanager\iflowjsp\jsp\IFWebServices.wsdl and modify port number to
            //  WS-I Monitor's man-in-the-middle port 5349.
            tr.log("analyzerConfig.xml updated. Saving and updating the WSDL...");
            String wsdlUrl = tr.getRequiredProperty("webserviceurl");
            saveWsdl(wsdlUrl, testClientHost);

            // Read property waitForMonitorInit
            String waitForMonitorInitStr = tr.getProperty("waitForMonitorInit","5");
            int waitForMonitorInit = Integer.parseInt(waitForMonitorInitStr);

            // Start monitor
            tr.log("WSDL saved and updated. Starting the monitor...");
            Process monitorProc = null;
            if (System.getProperty("os.name").startsWith("Windows")) {
                monitorProc = runCommand(testDirPath + "runmon.bat");
            }
            else {
                monitorProc = runCommand(testDirPath + "runmon.sh");
            }
            long monitorStartTime = System.currentTimeMillis();

            tr.log("Monitor started. Waiting 'waitForMonitorInit' = " + waitForMonitorInit + " seconds for monitor to initialize...");
            waitForAction(waitForMonitorInit*1000);

            // Start process. Set planId to the id of WSI-CMD-OBJECT.xpdl after import.
            tr.log("Wait for monitor initialization completed. Starting the process...");
            ProcessInstantiator pitor = WFObjectFactory.getProcessInstantiator(planIds[0], wfSession);
            Properties diProps = new Properties();
            diProps.setProperty("planId", Long.toString(planIds[1]));
            diProps.setProperty("userId", username);
            diProps.setProperty("password", password);
            pitor.setDataItemValues(diProps);
            ProcessInstance pi = pitor.startNewProcess();
            pi.addProcessInstanceListener(this);
            long pid = pi.getId();
            tr.log("Started process with id: " + pid);

            // Check for process state. If ERROR, report error in detail.
            while(processState != ProcessInstance.STATE_CLOSED && processState != ProcessInstance.STATE_ERROR) {
                tr.log("Waiting for process to complete..");
                waitForAction(actionWaitTime);
            }

            long monitorCompleteTime = monitorStartTime + monitorLogDuration*1000;
            if (processState == ProcessInstance.STATE_ERROR) {
                if (errorStateTime > monitorCompleteTime) {
                    tr.markFailed(testName, "Process went to error state because Monitor exited before process completed. Increase the value of 'monitorLogDuration' in 'test.conf' and re-run test.");
                }
                reportErrorState(pi);
                return;
            }

            long currentTime = System.currentTimeMillis();
            if (currentTime < monitorCompleteTime) {
                long waitTime = monitorCompleteTime - currentTime + 2000;
                tr.log("Process completed. Waiting " + waitTime/1000 + " seconds for monitor to complete...");
                waitForAction(waitTime);
            }
            /*
            // If COMPLETED, send "exit" command to the monitor and stop it.
            //Runtime.getRuntime().exec("exit");
            tr.log("Sending 'exit' command to monitor");
            OutputStream monitorOutputStream = monitorProc.getOutputStream();
            monitorOutputStream.write("exit".getBytes());
            waitForAction();
            monitorProc.destroy();
            */

            // Run analyzer
            tr.log("Monitor completed. Starting the analyzer...");
            Process analyzerProc = null;
            if (System.getProperty("os.name").startsWith("Windows")) {
                analyzerProc = runCommand(testDirPath + "runana.bat");
            }
            else {
                analyzerProc = runCommand(testDirPath + "runana.sh");
            }
            int analyzerLogDuration = Integer.parseInt(tr.getProperty("analyzerLogDuration","300"));
            tr.log("Analyzer started. Waiting 'analyzerLogDuration' = " + analyzerLogDuration + " seconds for analyzer to complete...");
            waitForAction(analyzerLogDuration * 1000);

            // Look for report. Parse it and check the final PASSED/FAILED status. Report it. We're DONE!
            String reportPath = testDirPath + "report.xml";
            tr.log("Analyzer completed. Looking for report file " + reportPath);
            String reportString = readFile(reportPath);
            tr.log("Report file found. Looking for test result reported in the report file...");
            int pos1 = reportString.indexOf("<summary result=\"");
            if (pos1 < 0) {
                tr.log("Test result not found in the report file. Most likely analyzer exited too soon. Increase analyzerLogDuration in 'test.conf' and re-run test.");
                return;
            }
            int pos2 = reportString.indexOf("\">", pos1);
            String result = reportString.substring(pos1+17, pos2);
            tr.log("WS-I test result reported in report file = '" + result + "'");

            if (result.equals("passed")) {
                tr.markPassed(testName);
            }
            else {
                tr.markFailed(testName, "Refer to " + reportPath + " for details.");
            }

            /*
            // Remove templates and processes only if test passed.
            // If it failed, leave them there for further analysis.
            close();
            */
        }
         catch (Exception e) {
            e.printStackTrace();
            tr.log("WsiJavaActionToCmdIntf caught exception: ") ;
            throw e ;
        }
    }


    private
    String
    stringifyState(
        int state)
        throws Exception
    {
        switch (state) {
            case ProcessInstance.STATE_CREATED : return ("STATE_CREATED");
            case ProcessInstance.STATE_RUNNING : return ("STATE_RUNNING");
            case ProcessInstance.STATE_OPEN : return ("STATE_OPEN");
            case ProcessInstance.STATE_ABORTED : return ("STATE_ABORTED");
            case ProcessInstance.STATE_CLOSED : return ("STATE_CLOSED");
            case ProcessInstance.STATE_SUSPENDED : return ("STATE_SUSPENDED");
            case ProcessInstance.STATE_ERROR: return ("STATE_ERROR");
        }
        throw new Exception("stringifyState got a state that it does not know anything about: "+state);
    }


    private
    void
    reportErrorState(
        ProcessInstance pi)
    throws Exception
    {
        int _processHistoryFields[] = {
            ProcessInstance.HISTORY_EVENT_TYPE,
            ProcessInstance.HISTORY_RESPONSIBLE,
            ProcessInstance.HISTORY_CONSUMER_ID,
            ProcessInstance.HISTORY_ISHANDLED,
            ProcessInstance.HISTORY_EVENTDATA
            };

        IflowEnumeration sElements = pi.getHistory(_processHistoryFields);
        Integer consumerIdKey = new Integer(ListSpec.PROCESSHISTORY_CONSUMERID);
        Integer responisbleKey = new Integer(ListSpec.PROCESSHISTORY_RESPONSIBLE);
        Integer eventTypeKey = new Integer(ListSpec.PROCESSHISTORY_EVENTTYPE);
        Integer isHandledKey = new Integer(ListSpec.PROCESSHISTORY_ISHANDLED);
        Integer eventDataKey = new Integer(ListSpec.PROCESSHISTORY_EVENTDATA);

        while(sElements.hasMoreElements()) {
            Hashtable historyData = (Hashtable) sElements.nextElement();
            String isHandled = (String) historyData.get(isHandledKey);
            // Display the error rows.
            if (isHandled.equals("error")) {
                long consumerId = ((Long) historyData.get(consumerIdKey)).longValue();
                String eventType = (String) historyData.get(eventTypeKey);
                String message = (String) historyData.get(eventDataKey);
                String responsible = (String) historyData.get(responisbleKey);

                int pos = eventType.indexOf(':');
                String modifier = null;
                if (pos >= 0) {
                    modifier = eventType.substring(pos+1);
                    eventType = eventType.substring(0,pos);
                }
                String niceStatus;
                if (eventType.equals("__ProcessCreated")) {
                    niceStatus = "Process was created.";
                }
                else if (eventType.equals("__Start")) {
                    niceStatus = "Process was started.";
                }
                else if (eventType.equals("__CommitEdit")) {
                    niceStatus = "Process was edited.";
                }
                else if (eventType.equals("__Activate")) {
                    niceStatus = "Start node activated.";
                }
                else if (eventType.equals("__Close")) {
                    niceStatus = "Process was completed; conclusion was '"+modifier+"'.";
                }
                else if (eventType.equals("__Accept")) {
                    niceStatus = "User accepted workitem #"+modifier;
                }
                else if (eventType.equals("__Decline")) {
                    niceStatus = "User declined workitem #"+modifier;
                }
                else {
                    if (modifier != null) {
                        niceStatus = "User selected choice '"+eventType+"' from workitem #"+modifier;
                    }
                    else {
                        niceStatus = "Arrow '"+eventType+"' transition to node ("+consumerId+")";
                    }
                }

                String messageStr = "Error occurred in " + niceStatus;
                if (message != null && message.length()>0) {
                    StringWriter sw = new StringWriter();
                    TestDriver.deparenthesize(sw, message);
                    messageStr += " Error Message: " + sw.toString();
                }

                tr.markFailed("Process went to Error State", messageStr);
            }
        }
    }

    private
    Process
    runCommand(
        String cmdString)
    throws Exception
    {
        Process proc = null;
        String osName = System.getProperty("os.name");

        String[] dirs = cmdString.split("/");
        StringBuffer sb = new StringBuffer();
        if (dirs != null) {
            for(int i = 0; i < dirs.length-1; i++) {
                sb.append(dirs[i]);
                sb.append(File.separator);
            }
            sb.append(dirs[dirs.length-1]);
        }
        cmdString = sb.toString();

       if (osName.startsWith("Windows")) {
            String[] cmdarray = {"c:\\winnt\\system32\\cmd", "/C", "start", cmdString };
            proc = Runtime.getRuntime().exec(cmdarray);
        }
        else {
            proc = Runtime.getRuntime().exec(cmdString);
        }
        return proc;

        //Alternate way to run the command.
        //String command = "rundll32 url.dll,FileProtocolHandler " + cmdString;
        //Process proc = Runtime.getRuntime().exec(command);
        //return proc;
    }

    private
    void
    saveWsdl(
        String wsdlUrl,
        String testClientHostPort)
        throws Exception
    {
        // Get the WSDL
        URL url = new URL(wsdlUrl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setDoOutput(true);
        conn.connect();

        InputStream is = conn.getInputStream();
        BufferedReader in = new BufferedReader(new InputStreamReader(is));
        StringBuffer sb = new StringBuffer();
        for(String line = in.readLine(); line != null; line = in.readLine()) {
            sb.append(line);
            sb.append("\n");
        }
        String wsdlString = sb.toString();

        // Extract just the testClient's host name from testClientHost 
        int pos = testClientHostPort.indexOf(":");
        if (pos < 0) {
            Exception e = new Exception("':' not found in parameter testClientHost=" + testClientHostPort + ". Expected SERVERHOST:PORT");
            tr.log(e.toString());
            throw e;
        }
        
        String testClientHost = testClientHostPort.substring(0, pos) + ":5349";
        String testClientPort = testClientHostPort.substring(pos+1);
        
        // Modify host name and port from testhost:49950 or testhost:9080 to testhost:5349 
        wsdlString = update(wsdlString, "location=\"http://", "/iflowws/services/IFWebServices", testClientHost);

        // Save the wsdl file to the location where template will read it from.
        String wsdlPath = testDirPath;
        if (testClientPort.trim().equals("9080")) {
            wsdlPath += "../../../../InterstageBPMClientApplication.ear/console.war/jsp/IFWebServices.wsdl";
        }
        else {
            wsdlPath += "../../../../taskmanager/iflowjsp/jsp/IFWebServices.wsdl";
        }
        writeFile(wsdlPath, wsdlString);
    }

    private
    String
    readFile(
        String filePath)
        throws Exception
    {
        BufferedReader in = new BufferedReader(new FileReader(filePath));
        StringBuffer sb = new StringBuffer();
        for(String line = in.readLine(); line != null; line = in.readLine()) {
            sb.append(line);
            sb.append("\n");
        }
        return sb.toString();
    }

    private
    void
    writeFile(
        String filePath,
        String content)
        throws Exception
    {
        BufferedWriter out = new BufferedWriter(new FileWriter(filePath));
        out.write(content);
        out.close();
    }

    private
    void
    updateFile(
        String filePath,
        String start,
        String end,
        String newValue)
        throws Exception
    {
        String fileString = readFile(filePath);
        fileString = update(fileString, start, end, newValue);
        writeFile(filePath, fileString);
    }

    private
    String
    update(
        String originalString,
        String start,
        String end,
        String newValue)
        throws Exception
    {
        int pos1 = originalString.indexOf(start);
        if (pos1 < 0) {
            Exception e = new Exception("Starting pattern '" + start + "' not found in " + filePath + ". File has been corrupted.");
            tr.log(e.toString());
            throw e;
        }
        int pos2 = originalString.indexOf(end, pos1+start.length());
        if (pos2 < 0) {
            Exception e = new Exception("Ending pattern '" + end + "' not found after starting pattern '" + start + "' in " + filePath + ". File has been corrupted.");
            tr.log(e.toString());
            throw e;
        }
        String oldValue = originalString.substring(pos1+start.length(), pos2);

        originalString = originalString.replaceAll(start+oldValue+end, start+newValue+end);
        return originalString;
    }

    private
    void
    waitForAction(
        long waitTime)
    {
        try {
            Thread.sleep(waitTime);
        } catch (Exception ex) {
            //InteruptedException
        }
    }

    // ProcessInstanceListener method
    public
    void
    processInstanceChanged(
        ProcessInstanceEvent event)
    {
        if (event.getType() == ProcessInstanceEvent.PROCESS_INSTANCE) {
            ProcessInstance pi = (ProcessInstance)event.getSource();
            // TODO.. change this to a notify
            processState = pi.getState();

            if (processState == ProcessInstance.STATE_ERROR) {
                errorStateTime = System.currentTimeMillis();
            }
        }
    }
    // ProcessInstanceListener method
    public void stateChanged(ProcessInstanceEvent event) {}

   /**
    * TestDriver is the class that knows how to run these tests.
    * So just call it with the name of this class in there.
    */
    public
    static
    void
    main(String[] args)
    {
        String[] newArgs = new String[1];
        newArgs[0] = "com.fujitsu.iflowqa.testframe.WsiJavaActionToCmdIntf";
        TestDriver.main(newArgs);
    }
}
