package com.fujitsu.iflowqa.testframe;

// iflow classes.
import java.io.FileInputStream;
import java.util.Properties;
import java.util.Random;
import java.util.ResourceBundle;

import javax.jms.Session;
import javax.jms.TemporaryTopic;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.jms.TopicSubscriber;
import javax.naming.Context;
import javax.naming.InitialContext;

import com.fujitsu.iflow.model.workflow.DataItem;
import com.fujitsu.iflow.model.workflow.Filter;
import com.fujitsu.iflow.model.workflow.Plan;
import com.fujitsu.iflow.model.workflow.ProcessInstance;
import com.fujitsu.iflow.model.workflow.WFAdminSession;
import com.fujitsu.iflow.model.workflow.WFObjectFactory;
import com.fujitsu.iflow.model.workflow.WFObjectList;
import com.fujitsu.iflow.model.workflow.WorkItem;

public class JMSTest implements TestSet, javax.jms.MessageListener
{
    private final static long SLEEP_TIME = 10000;
    private static final long   MESSAGE_LIFESPAN = 1*60*1000;

    private static final String NAMING_PROVIDER = "JmsNamingProvider";
    private static final String NAMING_PROVIDER_URL = "JmsNamingProviderURL";
    private static final String JMS_FACTORY = "JmsFactory";
    private static final String JMS_CMD_TOPIC = "JmsCommandTopic";

    private ResourceBundle sysSettings = null ;
    private TemporaryTopic tp = null;
    private TopicSubscriber subscriber = null;
    private TopicConnection connect = null;
    private TopicSession pubSession = null;
    private TopicSession subSession = null;

    private TopicPublisher cmdPublisher = null;
    private String JmsFactory = null;
    private String JmsCommandTopic = null;

    private TestRecorder tRecorder;
    private Properties serverProperties;
    private WFAdminSession wfSession;
    private String RESULT_UDA = "TestResult";
    private String RESULT_PASSED = "Passed";
    private String RESULT_FAILED = "Failed";
    private long planId = -1;
    private long processId = -1;


    /*
     * Zero argument constructor.
     */
    public JMSTest() {
    }

    /*
     * This method runs the rest.
     */
    public void runTests(TestRecorder tRecorder) throws Exception
    {
        this.tRecorder = tRecorder;
        debug("runTests()");

        try {
            logIn();
            // the fist argument from the tRecorder will be the xpdl file name.
            String[] commandLineArgs = tRecorder.getCommandLineArgs();
            planId = createPlanFromXPDL(commandLineArgs[1]);
            processId = createProcess(planId);
            testJMSInterface();
            handleWorkItems(processId);
            checkResultUDA(processId);
            logOut();
        } catch (Exception e) {
            logException("Exception caught in main test level", e);
        }
    }

    /*
     * This method creates the template from the XPDL
     */
    public
    long
    createPlanFromXPDL(
        String fileName)
        throws Exception
    {
        debug("createPlanFromXPDL()");
        FileInputStream fis = null;
        long id = -1;
        try {
            Plan plan = WFObjectFactory.getPlan();
            plan.setWFSession(wfSession);
            fis = new  FileInputStream(fileName);
            plan.convertFromXPDL(fis);
            Plan newPlan = plan.createProcessDef();
            tRecorder.markPassed("Plan.createPlanFromXPDL()");
            id = newPlan.getId();
        }
        catch (Exception e) {
            logException("createPlanFromXPDL() - Possibly because of Plan.convertFromXPDL()" , e);
        }
        finally {
            if (fis != null)
                fis.close();
        }
        return id;
    }

    /*
     * This method creates the Process for the given template.
     */
    public
    long
    createProcess(
        long planId)
        throws Exception
    {
        debug("createProcess()");
        long id = -1;
        try {
            Plan plan = WFObjectFactory.getPlan(planId, wfSession);
            //tRecorder.markPassed("WFObjectFactory.getPlan()");
            ProcessInstance pi = plan.createProcessInstance();
            //tRecorder.markPassed("Plan.createProcessInstance()");
            pi.start();
            tRecorder.markPassed("ProcessInstance.start()");
            Thread.sleep(SLEEP_TIME); // 10 Seconds.
            id  = pi.getId();
        }
        catch (Exception e) {
            logException("createProcess() - Possibly because of Plan.createProcessInstance()", e);
        }
        return id;
    }

    /*
     * Setup the JMS Connection/ Topic/ Session.
     */
    private
    void
    testJMSInterface()
        throws Exception
    {
        debug("testJMSInterface()");
        try {
            ResourceBundle sysSettings = ResourceBundle.getBundle("iFlowClient");
            Properties JmsProperties = new Properties();
            JmsProperties.put(Context.INITIAL_CONTEXT_FACTORY, sysSettings.getString(NAMING_PROVIDER));
            JmsProperties.put(Context.PROVIDER_URL, sysSettings.getString(NAMING_PROVIDER_URL));
            JmsFactory = sysSettings.getString(JMS_FACTORY);
            JmsCommandTopic = sysSettings.getString(JMS_CMD_TOPIC);

            //Getting Initial Context
            Context intCtx = new InitialContext(JmsProperties);

            //Getting Topic Connection Factory
            TopicConnectionFactory  factory = (TopicConnectionFactory)intCtx.lookup(JmsFactory);

            //Getting Command Topic
            Topic cmdTopic = (Topic)intCtx.lookup(JmsCommandTopic);

            intCtx.close();

            intCtx = null;

            //Getting a TopicConnection
            connect = factory.createTopicConnection ();

            //Getting a TopicSession
            pubSession = connect.createTopicSession(false,Session.AUTO_ACKNOWLEDGE);

            cmdPublisher = pubSession.createPublisher(cmdTopic);

            //Getting a TopicSession
            //TopicSession
            subSession = connect.createTopicSession(false,Session.AUTO_ACKNOWLEDGE);

            //Getting a TopicSubscriber with filter
            //TopicSubscriber
            tp = subSession.createTemporaryTopic();
            subscriber = subSession.createSubscriber(tp);
            //Set this object as a Jms Message Listener
            subscriber.setMessageListener(this);

            //Start Delivery of Message
            connect.start();

            // send the message to create the process.
            sendMessage();
        } catch (Exception e) {
            logException("testJMS() - Unable create setup the JMS Topic/Session/Connection for the test", e);
        }

        finally {
            try {
                connect.close();
            } catch (Exception e) {
                logException("testJMS() - Unable to close the JMS connection", e);
           }
        }
    }


    /*
     * Posts the message with the command to the specified topic for iFlow
     *
     * @param command The command to be executed in XML form.
     */
    private
    void
    sendMessage()
        throws Exception
    {

        debug("sendMessage()");

        try
        {
            String xcom = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><iFlowJMSCommand cmdId=\"5\"><user>" +
                "<name>" + tRecorder.getRequiredProperty("username") +
                "</name><password>" + tRecorder.getRequiredProperty("password") + "</password></user>" +
                "<workitemScope>" +
                "<getMyWorkitems/>" +
                "</workitemScope></iFlowJMSCommand>";
            TextMessage msg = pubSession.createTextMessage();
            msg.setText(xcom);
            msg.setJMSReplyTo(tp);
            cmdPublisher.publish(msg);
            synchronized(this){
                wait();
            }
        }
        catch (Exception e) {
            logException("sendMessage() - Possibly unable to publish the JMS message", e);
        }
    }

    /*
     * Receives the reply posted by the server with the results of the command execution.
     *
     * @param msg The received message.
     */
    public
    void
    onMessage(
        javax.jms.Message msg)
    {

        debug("onMessage()");

        try
        {
            TextMessage textMessage = (TextMessage)msg;
            String strMsg = textMessage.getText();
            synchronized(this){
                notifyAll();
            }
            String workitemIdStr = strMsg.substring(strMsg.indexOf("<workItemId>")+12,strMsg.indexOf("</workItemId>"));
            if (strMsg.indexOf("<processId>")!=-1) {
                tRecorder.markPassed("WorkItem with id " + workitemIdStr + " was sucessfully retreived.");
            }
            else {
                tRecorder.markFailed("Unable to get 'MyWorkItems'", "Possible because of no process existe");
            }
        }
        catch (Exception e) {
            // cant throw exception here.
            e.printStackTrace();
        }
    }


    /*
     * This mehtod handles all the workitems for the give user and on the given process.
     */
    private
    void
    handleWorkItems(
        long procInstId)
        throws Exception
    {
        debug("handleWorkItems()");
        try {
            while (true) {
                WFObjectList wfol = WFObjectFactory.getWFObjectList(wfSession);
                wfol.addFilter(WFObjectList.LISTFIELD_PROCESSINSTANCE_ID , "=", "'"+ procInstId +"'");
                wfol.openBatchedList(Filter.MyActiveWorkItems);
                //tRecorder.markPassed("WFObjectList.openBatchedList()");
                Object[] workItemList = wfol.getNextBatch(0, 99);

                if ( (workItemList == null) || (workItemList.length == 0) ) {
                    debug("No more Wotkitems on this process for this user to make choice on.");
                    return ;
                }

                Random randNo = new Random();
                WorkItem workItem = (WorkItem) workItemList[randNo.nextInt(workItemList.length )];
                String[] choices = workItem.getChoices();
                //tRecorder.markPassed("WorkItem.getChoices()");
                workItem.makeChoice(choices[randNo.nextInt(choices.length)]); // always select the first choice.
                //tRecorder.markPassed("WorkItem.makeChoice()");
                Thread.sleep(SLEEP_TIME);
            }
        }
        catch (Exception e) {
            logException("handleWorkItems() - Possibly because of WorkItem.makeChoice()", e);
        }
    }

    /*
     * This mehtod handles all the workitems for the give user and on the given process.
     */
    private
    void
    checkResultUDA(
        long procInstId)
        throws Exception
    {
        debug("checkResultUDA()");
        try {
            ProcessInstance pi = WFObjectFactory.getProcessInstance(procInstId, wfSession);
            DataItem di = null;
            try {
                di = pi.getDataItem(RESULT_UDA);
            } catch (Exception e) {
                tRecorder.markFailed("Unable to get UDA named '"+ RESULT_UDA +"'", "May be the UDA is missing");
                return;
            }

            if (di.getValue().equalsIgnoreCase(RESULT_PASSED)) {
                tRecorder.markPassed("Test case passed");
            }
            else {
                tRecorder.markFailed("Test case failed", "The Result UDA value is wrong");
            }
        } catch (Exception e) {
            logException("checkResultUDA() - Possibly because of ProcessInstance.getDataItem()", e);
        }
    }


    /*
     * Login
     */
    public
    void
    logIn()
        throws Exception
    {
        debug("logIn(server=" + tRecorder.getRequiredProperty("servername") +
                  ", user=" + tRecorder.getRequiredProperty("username") + ")");
        try {
            String transPortType = tRecorder.getRequiredProperty("TWFTransportType");
            wfSession = WFObjectFactory.getWFAdminSession();
            //wfSession.initForApplication(null, tRecorder.props);
            wfSession.initForApplication();
            wfSession.logIn(tRecorder.getRequiredProperty("servername") +"Flow",
                            tRecorder.getRequiredProperty("username"),
                            tRecorder.getRequiredProperty("password"));
        }
        catch (Exception e) {
            logException("Failed to Login", e);
        }
    }

    /*
     * LogOut
     */
    public
    void
    logOut()
        throws Exception
    {
        debug("logOut()");
        try {
            wfSession.logOut();
        }
        catch(Exception e) {
            logException("Failed to Logout ", e);
        }
    }

    /*
     * Logs the trace.
     */
    public
    void
    debug(String msg)
    {
        tRecorder.log(msg);
    }

    /*
     *  Logs the exception.
     */
    public
    void
    logException(
        String message,
        Exception e)
        throws Exception
    {
        tRecorder.log(message);
        throw new ExceptionWrapper(message, e);
    }

}

