/*
 * Copyright 2010 JBoss Inc
 *
 * 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.
 */
package org.drools.grid.task;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.drools.SystemEventListenerFactory;
import org.drools.eventmessaging.EventKey;
import org.drools.eventmessaging.Payload;
import org.drools.grid.ConnectorException;
import org.drools.grid.GenericNodeConnector;
import org.drools.grid.internal.Message;
import org.drools.grid.task.TaskClientMessageHandlerImpl.AddTaskMessageResponseHandler;
import org.drools.grid.task.TaskClientMessageHandlerImpl.GetContentMessageResponseHandler;
import org.drools.grid.task.TaskClientMessageHandlerImpl.GetTaskMessageResponseHandler;
import org.drools.grid.task.eventmessaging.EventMessageResponseHandler;
import org.drools.runtime.KnowledgeRuntime;
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.task.AccessType;
import org.drools.task.Content;
import org.drools.task.Group;
import org.drools.task.I18NText;
import org.drools.task.OrganizationalEntity;
import org.drools.task.PeopleAssignments;
import org.drools.task.SubTasksStrategy;
import org.drools.task.SubTasksStrategyFactory;
import org.drools.task.Task;
import org.drools.task.TaskData;
import org.drools.task.User;
import org.drools.task.event.TaskCompletedEvent;
import org.drools.task.event.TaskEvent;
import org.drools.task.event.TaskEventKey;
import org.drools.task.event.TaskFailedEvent;
import org.drools.task.event.TaskSkippedEvent;
import org.drools.task.service.Command;
import org.drools.task.service.ContentData;
import org.drools.task.service.responsehandlers.AbstractBaseResponseHandler;

public class CommandBasedServicesWSHumanTaskHandler
    implements
    WorkItemHandler {

    private String                       ipAddress = "127.0.0.1";
    private int                          port      = 9124;
    private GenericNodeConnector         connector;
    private HumanTaskServiceRemoteClient client;
    private KnowledgeRuntime             session;
    private Map<Long, Long>              idMapping = new HashMap<Long, Long>();
    private Map<Long, WorkItemManager>   managers  = new HashMap<Long, WorkItemManager>();

    public CommandBasedServicesWSHumanTaskHandler(KnowledgeRuntime session) {
        this.session = session;

    }

    public void setAddress(String ipAddress,
                           int port) {
        this.ipAddress = ipAddress;
        this.port = port;
    }

    public void connect() throws ConnectorException,
                         RemoteException {
        if ( this.connector == null ) {
            this.connector = new RemoteMinaHumanTaskConnector( "client ht",
                                                               this.ipAddress,
                                                               this.port,
                                                               SystemEventListenerFactory.getSystemEventListener() );
            int id = ((StatefulKnowledgeSession) this.session).getId();
            this.connector.connect();
            this.client = new HumanTaskServiceRemoteClient( this.connector,
                                                            id );
        }
    }

    public void executeWorkItem(WorkItem workItem,
                                WorkItemManager manager) {
        try {
            connect();
        } catch ( RemoteException ex ) {
            Logger.getLogger( CommandBasedServicesWSHumanTaskHandler.class.getName() ).log( Level.SEVERE,
                                                                                            null,
                                                                                            ex );

        } catch ( ConnectorException ex ) {
            Logger.getLogger( CommandBasedServicesWSHumanTaskHandler.class.getName() ).log( Level.SEVERE,
                                                                                            null,
                                                                                            ex );
            return;
        }
        Task task = new Task();
        String taskName = (String) workItem.getParameter( "TaskName" );
        if ( taskName != null ) {
            List<I18NText> names = new ArrayList<I18NText>();
            names.add( new I18NText( "en-UK",
                                     taskName ) );
            task.setNames( names );
        }
        String comment = (String) workItem.getParameter( "Comment" );
        if ( comment != null ) {
            List<I18NText> descriptions = new ArrayList<I18NText>();
            descriptions.add( new I18NText( "en-UK",
                                            comment ) );
            task.setDescriptions( descriptions );
            List<I18NText> subjects = new ArrayList<I18NText>();
            subjects.add( new I18NText( "en-UK",
                                        comment ) );
            task.setSubjects( subjects );
        }
        String priorityString = (String) workItem.getParameter( "Priority" );
        int priority = 0;
        if ( priorityString != null ) {
            try {
                priority = new Integer( priorityString );
            } catch ( NumberFormatException e ) {
                // do nothing
            }
        }
        task.setPriority( priority );

        TaskData taskData = new TaskData();
        taskData.setWorkItemId( workItem.getId() );
        taskData.setSkipable( !"false".equals( workItem.getParameter( "Skippable" ) ) );
        //Sub Task Data
        Long parentId = (Long) workItem.getParameter( "ParentId" );
        if ( parentId != null ) {
            taskData.setParentId( parentId );
        }

        String subTaskStrategiesCommaSeparated = (String) workItem.getParameter( "SubTaskStrategies" );
        if ( subTaskStrategiesCommaSeparated != null && !subTaskStrategiesCommaSeparated.equals( "" ) ) {
            String[] subTaskStrategies = subTaskStrategiesCommaSeparated.split( "," );
            List<SubTasksStrategy> strategies = new ArrayList<SubTasksStrategy>();
            for ( String subTaskStrategyString : subTaskStrategies ) {
                SubTasksStrategy subTaskStrategy = SubTasksStrategyFactory.newStrategy( subTaskStrategyString );
                strategies.add( subTaskStrategy );
            }
            task.setSubTaskStrategies( strategies );
        }

        PeopleAssignments assignments = new PeopleAssignments();
        List<OrganizationalEntity> potentialOwners = new ArrayList<OrganizationalEntity>();

        String actorId = (String) workItem.getParameter( "ActorId" );
        if ( actorId != null ) {

            String[] actorIds = actorId.split( "," );
            for ( String id : actorIds ) {
                User user = new User();
                user.setId( id.trim() );
                potentialOwners.add( user );
            }
            //Set the first user as creator ID??? hmmm might be wrong
            if ( potentialOwners.size() > 0 ) {
                taskData.setCreatedBy( (User) potentialOwners.get( 0 ) );
            }
        }
        String groupId = (String) workItem.getParameter( "GroupId" );
        if ( groupId != null ) {

            String[] groupIds = groupId.split( "," );
            for ( String id : groupIds ) {

                potentialOwners.add( new Group( id ) );
            }

        }

        assignments.setPotentialOwners( potentialOwners );
        List<OrganizationalEntity> businessAdministrators = new ArrayList<OrganizationalEntity>();
        businessAdministrators.add( new User( "Administrator" ) );
        assignments.setBusinessAdministrators( businessAdministrators );
        task.setPeopleAssignments( assignments );

        task.setTaskData( taskData );

        ContentData content = null;
        Object contentObject = workItem.getParameter( "Content" );
        if ( contentObject != null ) {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream out;
            try {
                out = new ObjectOutputStream( bos );
                out.writeObject( contentObject );
                out.close();
                content = new ContentData();
                content.setContent( bos.toByteArray() );
                content.setAccessType( AccessType.Inline );
            } catch ( IOException e ) {
                e.printStackTrace();
            }
        }
        TaskWorkItemAddTaskMessageResponseHandler taskResponseHandler =
                new TaskWorkItemAddTaskMessageResponseHandler( workItem.getId(),
                                                               this.managers,
                                                               this.idMapping,
                                                               manager );
        this.client.addTask( task,
                             content,
                             taskResponseHandler );
    }

    public void dispose() throws ConnectorException,
                         RemoteException {
        if ( this.connector != null ) {
            this.connector.disconnect();
        }
    }

    public void abortWorkItem(WorkItem workItem,
                              WorkItemManager manager) {
        Long taskId = this.idMapping.get( workItem.getId() );
        if ( taskId != null ) {
            synchronized ( this.idMapping ) {
                this.idMapping.remove( taskId );
            }
            synchronized ( this.managers ) {
                this.managers.remove( taskId );
            }
            this.client.skip( taskId,
                              "Administrator",
                              null );
        }
    }

    public class TaskWorkItemAddTaskMessageResponseHandler extends AbstractBaseResponseHandler
        implements
        AddTaskMessageResponseHandler {

        private Map<Long, WorkItemManager> managers;
        private Map<Long, Long>            idMapping;
        private WorkItemManager            manager;
        private long                       workItemId;

        public TaskWorkItemAddTaskMessageResponseHandler(long workItemId,
                                                         Map<Long, WorkItemManager> managers,
                                                         Map<Long, Long> idMapping,
                                                         WorkItemManager manager) {
            this.workItemId = workItemId;
            this.managers = managers;
            this.idMapping = idMapping;
            this.manager = manager;
        }

        public void execute(long taskId) {
            throw new UnsupportedOperationException( "Not supported yet." );
        }

        public void receive(Message message) {
            Command command = (Command) message.getPayload();
            Long taskId = (Long) command.getArguments().get( 0 );

            synchronized ( this.managers ) {
                this.managers.put( taskId,
                                   this.manager );
            }
            synchronized ( this.idMapping ) {
                this.idMapping.put( this.workItemId,
                                    taskId );
            }
            System.out.println( "Created task " + taskId + " for work item " + this.workItemId );

            EventKey key = new TaskEventKey( TaskCompletedEvent.class,
                                             taskId );
            TaskCompletedMessageHandler eventResponseHandler = new TaskCompletedMessageHandler( this.workItemId,
                                                                                                taskId,
                                                                                                this.managers );
            CommandBasedServicesWSHumanTaskHandler.this.client.registerForEvent( key,
                                                                                 true,
                                                                                 eventResponseHandler );
            key = new TaskEventKey( TaskFailedEvent.class,
                                    taskId );
            CommandBasedServicesWSHumanTaskHandler.this.client.registerForEvent( key,
                                                                                 true,
                                                                                 eventResponseHandler );
            key = new TaskEventKey( TaskSkippedEvent.class,
                                    taskId );
            CommandBasedServicesWSHumanTaskHandler.this.client.registerForEvent( key,
                                                                                 true,
                                                                                 eventResponseHandler );
        }
    }

    private class TaskCompletedMessageHandler extends AbstractBaseResponseHandler
        implements
        EventMessageResponseHandler {

        private long                             workItemId;
        private long                             taskId;
        private final Map<Long, WorkItemManager> managers;

        public TaskCompletedMessageHandler(long workItemId,
                                           long taskId,
                                           Map<Long, WorkItemManager> managers) {
            this.workItemId = workItemId;
            this.taskId = taskId;
            this.managers = managers;
        }

        public void receive(Message message) {
            Command cmd = (Command) message.getPayload();
            Payload payload = (Payload) cmd.getArguments().get( 0 );
            TaskEvent event = (TaskEvent) payload.get();
            if ( event.getTaskId() != this.taskId ) {
                // defensive check that should never happen, just here for testing
                setError( new IllegalStateException( "Expected task id and arrived task id do not march" ) );
                return;
            }
            if ( event instanceof TaskCompletedEvent ) {
                synchronized ( this.managers ) {
                    WorkItemManager manager = this.managers.get( this.taskId );
                    if ( manager != null ) {
                        GetCompletedTaskMessageResponseHandler getTaskResponseHandler = new GetCompletedTaskMessageResponseHandler( manager );
                        CommandBasedServicesWSHumanTaskHandler.this.client.getTask( this.taskId,
                                                                                    getTaskResponseHandler );
                    }
                }
            } else {
                synchronized ( this.managers ) {
                    WorkItemManager manager = this.managers.get( this.taskId );
                    if ( manager != null ) {
                        manager.abortWorkItem( this.workItemId );
                    }
                }
            }
        }

        public void execute(Payload payload) {
            throw new UnsupportedOperationException( "Not supported yet." );
        }
    }

    private class GetCompletedTaskMessageResponseHandler extends AbstractBaseResponseHandler
        implements
        GetTaskMessageResponseHandler {

        private WorkItemManager manager;

        public GetCompletedTaskMessageResponseHandler(WorkItemManager manager) {
            this.manager = manager;
        }

        public void receive(Message message) {
            Command cmd = (Command) message.getPayload();
            Task task = (Task) cmd.getArguments().get( 0 );
            long workItemId = task.getTaskData().getWorkItemId();
            String userId = task.getTaskData().getActualOwner().getId();
            Map<String, Object> results = new HashMap<String, Object>();
            results.put( "ActorId",
                         userId );
            long contentId = task.getTaskData().getOutputContentId();
            if ( contentId != -1 ) {
                GetResultContentMessageResponseHandler getContentResponseHandler =
                        new GetResultContentMessageResponseHandler( this.manager,
                                                                    task,
                                                                    results );
                CommandBasedServicesWSHumanTaskHandler.this.client.getContent( contentId,
                                                                               getContentResponseHandler );
            } else {
                this.manager.completeWorkItem( workItemId,
                                               results );
            }
        }

        public void execute(Task task) {
            throw new UnsupportedOperationException( "Not supported yet." );
        }
    }

    private class GetResultContentMessageResponseHandler extends AbstractBaseResponseHandler
        implements
        GetContentMessageResponseHandler {

        private Task                  task;
        private final WorkItemManager manager;
        private Map<String, Object>   results;

        public GetResultContentMessageResponseHandler(WorkItemManager manager,
                                                      Task task,
                                                      Map<String, Object> results) {
            this.manager = manager;
            this.task = task;
            this.results = results;
        }

        public void receive(Message message) {
            Command cmd = (Command) message.getPayload();
            Content content = (Content) cmd.getArguments().get( 0 );
            ByteArrayInputStream bis = new ByteArrayInputStream( content.getContent() );
            ObjectInputStream in;
            try {
                in = new ObjectInputStream( bis );
                Object result = in.readObject();
                in.close();
                this.results.put( "Result",
                                  result );
                if ( result instanceof Map ) {
                    Map< ? , ? > map = (Map) result;
                    for ( Map.Entry< ? , ? > entry : map.entrySet() ) {
                        if ( entry.getKey() instanceof String ) {
                            this.results.put( (String) entry.getKey(),
                                              entry.getValue() );
                        }
                    }
                }
                this.manager.completeWorkItem( this.task.getTaskData().getWorkItemId(),
                                               this.results );
            } catch ( IOException e ) {
                e.printStackTrace();
            } catch ( ClassNotFoundException e ) {
                e.printStackTrace();
            }
        }

        public void execute(Content content) {
            throw new UnsupportedOperationException( "Not supported yet." );
        }
    }
}
