/*
 *  Copyright 2010 salaboy.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package org.drools.services;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.rmi.RemoteException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.apache.commons.collections.map.HashedMap;
import org.apache.mina.transport.socket.SocketAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.drools.KnowledgeBase;
import org.drools.KnowledgeBaseFactoryService;
import org.drools.SystemEventListenerFactory;
import org.drools.builder.KnowledgeBuilder;
import org.drools.builder.KnowledgeBuilderFactoryService;
import org.drools.grid.ConnectorException;
import org.drools.grid.ExecutionNode;
import org.drools.grid.internal.GenericMessageHandlerImpl;
import org.drools.grid.internal.NodeData;
import org.drools.grid.remote.mina.MinaAcceptor;
import org.drools.grid.remote.mina.MinaIoHandler;
import org.drools.grid.services.ExecutionEnvironment;
import org.drools.grid.services.GridTopology;
import org.drools.grid.services.TaskServerInstance;
import org.drools.grid.services.configuration.ExecutionEnvironmentConfiguration;
import org.drools.grid.services.configuration.GridTopologyConfiguration;
import org.drools.grid.services.configuration.MinaProvider;
import org.drools.grid.services.configuration.TaskServerInstanceConfiguration;
import org.drools.grid.services.factory.GridTopologyFactory;
import org.drools.grid.task.CommandBasedServicesWSHumanTaskHandler;
import org.drools.grid.task.HumanTaskService;
import org.drools.grid.task.TaskServerMessageHandlerImpl;
import org.drools.grid.task.responseHandlers.BlockingTaskOperationMessageResponseHandler;
import org.drools.grid.task.responseHandlers.BlockingTaskSummaryMessageResponseHandler;
import org.drools.io.impl.ClassPathResource;
import org.drools.process.instance.impl.WorkItemImpl;
import org.drools.runtime.StatefulKnowledgeSession;
import org.drools.runtime.process.WorkItem;
import org.drools.runtime.process.WorkItemHandler;
import org.drools.runtime.process.WorkItemManager;
import org.drools.services.task.MockUserInfo;
import org.drools.task.Group;
import org.drools.task.Status;
import org.drools.task.User;
import org.drools.task.query.TaskSummary;
import org.drools.task.service.TaskService;
import org.drools.task.service.TaskServiceSession;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mvel2.MVEL;
import org.mvel2.ParserContext;
import org.mvel2.compiler.ExpressionCompiler;

public class RegisterTaskTest {

    private GridTopology                             grid;
    private MinaAcceptor                             serverTask;
    private MinaAcceptor                             serverNode;
    private HumanTaskService                         client;
    private EntityManagerFactory                     emf;
    private TaskService                              taskService;
    private TaskServiceSession                       taskSession;
    protected Map<String, User>                      users;
    protected Map<String, Group>                     groups;
    protected static final int                       DEFAULT_WAIT_TIME            = 5000;
    protected static final int                       MANAGER_COMPLETION_WAIT_TIME = DEFAULT_WAIT_TIME;
    protected static final int                       MANAGER_ABORT_WAIT_TIME      = DEFAULT_WAIT_TIME;
    protected CommandBasedServicesWSHumanTaskHandler handler;

    public RegisterTaskTest() {
    }

    @BeforeClass
    public static void setUpClass() throws Exception {
    }

    @AfterClass
    public static void tearDownClass() throws Exception {
    }

    @Before
    public void setUp() throws InterruptedException,
                       IOException {

        //Task Related Stuff
        // Use persistence.xml configuration
        this.emf = Persistence.createEntityManagerFactory( "org.drools.task" );

        this.taskService = new TaskService( this.emf,
                                            SystemEventListenerFactory.getSystemEventListener() );
        this.taskSession = this.taskService.createSession();
        MockUserInfo userInfo = new MockUserInfo();
        this.taskService.setUserinfo( userInfo );
        Map vars = new HashedMap();

        Reader reader = null;

        try {
            reader = new InputStreamReader( new ClassPathResource( "org/drools/task/LoadUsers.mvel" ).getInputStream() );
            this.users = (Map<String, User>) eval( reader,
                                                   vars );
            for ( User user : this.users.values() ) {
                this.taskSession.addUser( user );
            }
        } finally {
            if ( reader != null ) {
                reader.close();
            }
            reader = null;
        }

        try {
            reader = new InputStreamReader( new ClassPathResource( "org/drools/task/LoadGroups.mvel" ).getInputStream() );
            this.groups = (Map<String, Group>) eval( reader,
                                                     vars );
            for ( Group group : this.groups.values() ) {
                this.taskSession.addGroup( group );
            }
        } finally {
            if ( reader != null ) {
                reader.close();
            }
        }

        // Human task Server configuration
        SocketAddress htAddress = new InetSocketAddress( "127.0.0.1",
                                                         9123 );
        SocketAcceptor htAcceptor = new NioSocketAcceptor();

        htAcceptor.setHandler( new MinaIoHandler( SystemEventListenerFactory.getSystemEventListener(),
                                                  new TaskServerMessageHandlerImpl( this.taskService,
                                                                                    SystemEventListenerFactory.getSystemEventListener() ) ) );
        this.serverTask = new MinaAcceptor( htAcceptor,
                                            htAddress );
        this.serverTask.start();
        Thread.sleep( 5000 );
        // End Execution Server

        //Execution Node related stuff

        System.out.println( "Server 1 Starting!" );
        // the servers should be started in a different machine (jvm or physical) or in another thread
        SocketAddress address = new InetSocketAddress( "127.0.0.1",
                                                       9124 );
        NodeData nodeData = new NodeData();
        // setup Server
        SocketAcceptor acceptor = new NioSocketAcceptor();
        acceptor.setHandler( new MinaIoHandler( SystemEventListenerFactory.getSystemEventListener(),
                                                new GenericMessageHandlerImpl( nodeData,
                                                                               SystemEventListenerFactory.getSystemEventListener() ) ) );
        this.serverNode = new MinaAcceptor( acceptor,
                                            address );
        this.serverNode.start();
        System.out.println( "Server 1 Started! at = " + address.toString() );

        Thread.sleep( 5000 );

    }

    @After
    public void tearDown() throws InterruptedException,
                          ConnectorException,
                          RemoteException {
        this.client.disconnect();

        this.grid.dispose();

        this.handler.dispose();

        Assert.assertEquals( 0,
                             this.serverNode.getCurrentSessions() );
        this.serverNode.stop();
        System.out.println( "Execution Server Stopped!" );

        Assert.assertEquals( 0,
                             this.serverTask.getCurrentSessions() );
        this.serverTask.stop();
        System.out.println( "Task Server Stopped!" );

        this.taskSession.dispose();
        this.emf.close();

    }

    @Test
    public void MinaTaskTest() throws InterruptedException,
                              ConnectorException,
                              RemoteException {

        GridTopologyConfiguration gridTopologyConfiguration = new GridTopologyConfiguration( "MyTopology" );
        gridTopologyConfiguration.addTaskServerInstance( new TaskServerInstanceConfiguration( "MyMinaTask",
                                                                                              new MinaProvider( "127.0.0.1",
                                                                                                                9123 ) ) );
        gridTopologyConfiguration.addExecutionEnvironment( new ExecutionEnvironmentConfiguration( "MyMinaExecutionEnv1",
                                                                                                  new MinaProvider( "127.0.0.1",
                                                                                                                    9124 ) ) );

        this.grid = GridTopologyFactory.build( gridTopologyConfiguration );

        Assert.assertNotNull( this.grid );

        TaskServerInstance taskServer = this.grid.getTaskServerInstance( "MyMinaTask" );

        Assert.assertNotNull( taskServer );

        this.client = taskServer.getHumanTaskNode().get( HumanTaskService.class );
        Assert.assertNotNull( this.client );

        //Create a task to test the HT client. For that we need to have a ksession with a workitem that creates it
        ExecutionEnvironment ee = this.grid.getExecutionEnvironment( "MyMinaExecutionEnv1" );
        Assert.assertNotNull( ee );

        // Give me an ExecutionNode in the selected environment
        // For the Mina we have just one Execution Node per server instance
        ExecutionNode node = ee.getExecutionNode();

        Assert.assertNotNull( node );
        KnowledgeBuilder kbuilder =
                node.get( KnowledgeBuilderFactoryService.class ).newKnowledgeBuilder();

        KnowledgeBase kbase =
                node.get( KnowledgeBaseFactoryService.class ).newKnowledgeBase();
        Assert.assertNotNull( kbase );

        kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() );

        StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
        Assert.assertNotNull( ksession );

        this.handler = new CommandBasedServicesWSHumanTaskHandler( ksession );
        this.handler.setAddress( "127.0.0.1",
                                 9123 );

        TestWorkItemManager manager = new TestWorkItemManager();
        WorkItemImpl workItem = new WorkItemImpl();
        workItem.setName( "Human Task" );
        workItem.setParameter( "TaskName",
                               "TaskName" );
        workItem.setParameter( "Comment",
                               "Comment" );
        workItem.setParameter( "Priority",
                               "10" );
        workItem.setParameter( "ActorId",
                               "Darth Vader" );
        this.handler.executeWorkItem( workItem,
                                      manager );

        Thread.sleep( 500 );

        BlockingTaskSummaryMessageResponseHandler responseHandler = new BlockingTaskSummaryMessageResponseHandler();
        this.client.getTasksAssignedAsPotentialOwner( "Darth Vader",
                                                      "en-UK",
                                                      responseHandler );
        responseHandler.waitTillDone( DEFAULT_WAIT_TIME );
        List<TaskSummary> tasks = responseHandler.getResults();
        Assert.assertEquals( 1,
                             tasks.size() );
        TaskSummary task = tasks.get( 0 );
        Assert.assertEquals( "TaskName",
                             task.getName() );
        Assert.assertEquals( 10,
                             task.getPriority() );
        Assert.assertEquals( "Comment",
                             task.getDescription() );
        Assert.assertEquals( Status.Reserved,
                             task.getStatus() );
        Assert.assertEquals( "Darth Vader",
                             task.getActualOwner().getId() );

        System.out.println( "Starting task " + task.getId() );
        BlockingTaskOperationMessageResponseHandler operationResponseHandler = new BlockingTaskOperationMessageResponseHandler();
        this.client.start( task.getId(),
                           "Darth Vader",
                           operationResponseHandler );
        operationResponseHandler.waitTillDone( DEFAULT_WAIT_TIME );
        System.out.println( "Started task " + task.getId() );

        System.out.println( "Completing task " + task.getId() );
        operationResponseHandler = new BlockingTaskOperationMessageResponseHandler();
        this.client.complete( task.getId(),
                              "Darth Vader",
                              null,
                              operationResponseHandler );
        operationResponseHandler.waitTillDone( 15000 );
        System.out.println( "Completed task " + task.getId() );
        Assert.assertTrue( manager.waitTillCompleted( DEFAULT_WAIT_TIME ) );
        Thread.sleep( 500 );

        taskServer.getConnector().disconnect();
    }

    private Object eval(Reader reader,
                        Map<String, Object> vars) {
        try {
            return eval( toString( reader ),
                         vars );
        } catch ( IOException e ) {
            throw new RuntimeException( "Exception Thrown",
                                        e );
        }
    }

    private String toString(Reader reader) throws IOException {
        int charValue = 0;
        StringBuffer sb = new StringBuffer( 1024 );
        while ( (charValue = reader.read()) != -1 ) {
            // result = result + (char) charValue;
            sb.append( (char) charValue );
        }
        return sb.toString();
    }

    private Object eval(String str,
                        Map<String, Object> vars) {
        ExpressionCompiler compiler = new ExpressionCompiler( str.trim() );

        ParserContext context = new ParserContext();
        context.addPackageImport( "org.drools.task" );
        context.addPackageImport( "org.drools.task.service" );
        context.addPackageImport( "org.drools.task.query" );
        context.addPackageImport( "java.util" );

        vars.put( "now",
                  new Date() );
        return MVEL.executeExpression( compiler.compile( context ),
                                       vars );
    }
}

class TestWorkItemManager
    implements
    WorkItemManager {

    private volatile boolean             completed;
    private volatile boolean             aborted;
    private volatile Map<String, Object> results;

    public synchronized boolean waitTillCompleted(long time) {
        if ( !isCompleted() ) {
            try {
                wait( time );
            } catch ( InterruptedException e ) {
                // swallow and return state of completed
            }
        }

        return isCompleted();
    }

    public synchronized boolean waitTillAborted(long time) {
        if ( !isAborted() ) {
            try {
                wait( time );
            } catch ( InterruptedException e ) {
                // swallow and return state of aborted
            }
        }

        return isAborted();
    }

    public void abortWorkItem(long id) {
        setAborted( true );
    }

    public synchronized boolean isAborted() {
        return this.aborted;
    }

    private synchronized void setAborted(boolean aborted) {
        this.aborted = aborted;
        notifyAll();
    }

    public void completeWorkItem(long id,
                                 Map<String, Object> results) {
        this.results = results;
        setCompleted( true );
    }

    private synchronized void setCompleted(boolean completed) {
        this.completed = completed;
        notifyAll();
    }

    public synchronized boolean isCompleted() {
        return this.completed;
    }

    public WorkItem getWorkItem(long id) {
        return null;
    }

    public Set<WorkItem> getWorkItems() {
        return null;
    }

    public Map<String, Object> getResults() {
        return this.results;
    }

    public void internalAbortWorkItem(long id) {
    }

    public void internalAddWorkItem(WorkItem workItem) {
    }

    public void internalExecuteWorkItem(WorkItem workItem) {
    }

    public void registerWorkItemHandler(String workItemName,
                                        WorkItemHandler handler) {
    }
}
