/*
 * Copyright 2015 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * 
 *      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.kie.remote.services.jaxb;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElements;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSchemaType;

import org.drools.core.common.DefaultFactHandle;
import org.jbpm.process.audit.NodeInstanceLog;
import org.jbpm.process.audit.ProcessInstanceLog;
import org.jbpm.process.audit.VariableInstanceLog;
import org.jbpm.process.audit.event.AuditEvent;
import org.jbpm.services.task.commands.GetContentMapForUserCommand;
import org.jbpm.services.task.commands.GetTaskByWorkItemIdCommand;
import org.jbpm.services.task.commands.GetTaskContentCommand;
import org.jbpm.services.task.impl.model.xml.JaxbContent;
import org.jbpm.services.task.impl.model.xml.JaxbTask;
import org.kie.api.command.Command;
import org.kie.api.runtime.process.ProcessInstance;
import org.kie.api.runtime.process.WorkItem;
import org.kie.api.task.model.Comment;
import org.kie.api.task.model.Task;
import org.kie.api.task.model.TaskSummary;
import org.kie.services.client.serialization.jaxb.impl.JaxbCommandResponse;
import org.kie.services.client.serialization.jaxb.impl.JaxbLongListResponse;
import org.kie.services.client.serialization.jaxb.impl.JaxbOtherResponse;
import org.kie.services.client.serialization.jaxb.impl.JaxbPrimitiveResponse;
import org.kie.services.client.serialization.jaxb.impl.JaxbRequestStatus;
import org.kie.services.client.serialization.jaxb.impl.JaxbStringListResponse;
import org.kie.services.client.serialization.jaxb.impl.JaxbVariablesResponse;
import org.kie.services.client.serialization.jaxb.impl.audit.JaxbHistoryLogList;
import org.kie.services.client.serialization.jaxb.impl.audit.JaxbNodeInstanceLog;
import org.kie.services.client.serialization.jaxb.impl.audit.JaxbProcessInstanceLog;
import org.kie.services.client.serialization.jaxb.impl.audit.JaxbVariableInstanceLog;
import org.kie.services.client.serialization.jaxb.impl.process.JaxbProcessInstanceListResponse;
import org.kie.services.client.serialization.jaxb.impl.process.JaxbProcessInstanceResponse;
import org.kie.services.client.serialization.jaxb.impl.process.JaxbWorkItemResponse;
import org.kie.services.client.serialization.jaxb.impl.task.JaxbTaskContentResponse;
import org.kie.services.client.serialization.jaxb.rest.JaxbExceptionResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@XmlRootElement(name = "command-response")
@XmlAccessorType(XmlAccessType.FIELD)
@SuppressWarnings("rawtypes")
public class JaxbCommandsResponse {

    private static final transient Logger logger = LoggerFactory.getLogger(JaxbCommandsResponse.class);

    @XmlElement(name = "deployment-id")
    @XmlSchemaType(name = "string")
    private String deploymentId;

    @XmlElement(name = "process-instance-id")
    @XmlSchemaType(name = "long")
    private Long processInstanceId;

    @XmlElement(name = "ver")
    @XmlSchemaType(name = "string")
    private String version;

    @XmlElements({ 
            @XmlElement(name = "exception", type = JaxbExceptionResponse.class),
            @XmlElement(name = "long-list", type = JaxbLongListResponse.class),
            @XmlElement(name = "string-list", type = JaxbStringListResponse.class),
            @XmlElement(name = "primitive", type = JaxbPrimitiveResponse.class),
            @XmlElement(name = "process-instance", type = JaxbProcessInstanceResponse.class),
            @XmlElement(name = "process-instance-list", type = JaxbProcessInstanceListResponse.class),
            @XmlElement(name = "task-response", type = JaxbTaskResponse.class),
            @XmlElement(name = "content-response", type = JaxbContentResponse.class ),
            @XmlElement(name = "task-content-response", type = JaxbTaskContentResponse.class ),
            @XmlElement(name = "task-comment-response", type = JaxbTaskCommentResponse.class ),
            @XmlElement(name = "task-comment-list-response", type = JaxbTaskCommentListResponse.class ),
            @XmlElement(name = "task-summary-list", type = JaxbTaskSummaryListResponse.class),
            @XmlElement(name = "work-item", type = JaxbWorkItemResponse.class),
            @XmlElement(name = "variables", type = JaxbVariablesResponse.class),
            @XmlElement(name = "other", type = JaxbOtherResponse.class),
            @XmlElement(name = "history-log-list", type = JaxbHistoryLogList.class),
            @XmlElement(name = "proc-inst-log", type = JaxbProcessInstanceLog.class),
            @XmlElement(name = "node-inst-log", type = JaxbNodeInstanceLog.class),
            @XmlElement(name = "var-inst-log", type = JaxbVariableInstanceLog.class)
            })
    private List<JaxbCommandResponse<?>> responses;

    public JaxbCommandsResponse() {
        // Default constructor
    }

    public JaxbCommandsResponse(JaxbCommandsRequest request) {
        super();
        this.deploymentId = request.getDeploymentId();
        this.processInstanceId = request.getProcessInstanceId();
    }

    public String getDeploymentId() {
        return deploymentId;
    }

    public void setDeploymentId(String deploymentId) {
        this.deploymentId = deploymentId;
    }

    public Long getProcessInstanceId() {
        return processInstanceId;
    }

    public void setProcessInstanceId(Long processInstanceId) {
        this.processInstanceId = processInstanceId;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    private void lazyInitResponseList() { 
        if( this.responses == null ) { 
            this.responses = new ArrayList<JaxbCommandResponse<?>>();
        }
    }

    public List<JaxbCommandResponse<?>> getResponses() {
        lazyInitResponseList();
        return responses;
    }

    public void setResponses(List<JaxbCommandResponse<?>> responses) {
        this.responses = responses;
    }

    public void addException(Exception exception, int i, Command<?> cmd, JaxbRequestStatus status) {
        lazyInitResponseList();
        this.responses.add(new JaxbExceptionResponse(exception, i, cmd, status));
    }

    @SuppressWarnings("unchecked")
    public void addResult(Object result, int i, Command<?> cmd) {
        lazyInitResponseList();
        boolean unknownResultType = false;
        
        String className = result.getClass().getName();
        if (result instanceof ProcessInstance) {
            this.responses.add(new JaxbProcessInstanceResponse((ProcessInstance) result, i, cmd));
        } else if (result instanceof JaxbTask
                || cmd instanceof GetTaskByWorkItemIdCommand) {
            this.responses.add(new JaxbTaskResponse((Task)result, i, cmd));
        } else if (result instanceof JaxbContent) {
            if (((JaxbContent) result).getId() == -1) {
                this.responses.add(new JaxbTaskContentResponse(((JaxbContent) result).getContentMap(), i, cmd));
            } else {
                this.responses.add(new JaxbContentResponse((JaxbContent) result, i, cmd));
            }
        } else if (List.class.isInstance(result)) { 
            // Necessary to determine return type of empty lists
            Class listType = getListType(cmd);
            if( listType == null ) { 
                unknownResultType = true;
            } else if( listType.equals(TaskSummary.class) ) { 
                this.responses.add(new JaxbTaskSummaryListResponse((List<TaskSummary>) result, i, cmd));
            } else if( listType.equals(Long.class) ) {
                this.responses.add(new JaxbLongListResponse((List<Long>)result, i, cmd));
            } else if( listType.equals(String.class) ) {
                this.responses.add(new JaxbStringListResponse((List<String>)result, i, cmd));
            } else if( listType.equals(ProcessInstance.class) ) {
               this.responses.add(new JaxbProcessInstanceListResponse((List<ProcessInstance>) result, i, cmd));
            } else if( listType.equals(ProcessInstanceLog.class) 
                    || listType.equals(NodeInstanceLog.class)
                    || listType.equals(VariableInstanceLog.class) ) {
                this.responses.add(new JaxbHistoryLogList((List<AuditEvent>) result));
            } else if( listType.equals(Comment.class) ) { 
                this.responses.add(new JaxbTaskCommentListResponse((List<Comment>) result));
            } else { 
                throw new IllegalStateException(listType.getSimpleName() + " should be handled but is not in " + this.getClass().getSimpleName() + "!" );
            }
        } else if (result.getClass().isPrimitive() 
        		|| Boolean.class.getName().equals(className)
        		|| Byte.class.getName().equals(className)
        		|| Short.class.getName().equals(className)
        		|| Integer.class.getName().equals(className)
        		|| Character.class.getName().equals(className)
        		|| Long.class.getName().equals(className)
        		|| Float.class.getName().equals(className)
        		|| Double.class.getName().equals(className) ) {
            this.responses.add(new JaxbPrimitiveResponse(result, i, cmd));
        } else if( result instanceof WorkItem ) { 
           this.responses.add(new JaxbWorkItemResponse((WorkItem) result, i, cmd));
        } else if( result instanceof ProcessInstanceLog ) { 
            this.responses.add(new JaxbProcessInstanceLog((ProcessInstanceLog) result));
        } else if( result instanceof NodeInstanceLog ) { 
            this.responses.add(new JaxbNodeInstanceLog((NodeInstanceLog) result));
        } else if( result instanceof VariableInstanceLog ) { 
            this.responses.add(new JaxbVariableInstanceLog((VariableInstanceLog) result));
        } else if( result instanceof DefaultFactHandle ) { 
           this.responses.add(new JaxbOtherResponse(result, i, cmd));
        } else if( cmd instanceof GetTaskContentCommand || cmd instanceof GetContentMapForUserCommand ) { 
           this.responses.add(new JaxbTaskContentResponse((Map<String, Object>) result, i, cmd));
        } else if( Comment.class.isAssignableFrom(result.getClass()) ) { 
           this.responses.add(new JaxbTaskCommentResponse((Comment) result, i, cmd));
        }
        // Other
        else if( result instanceof JaxbExceptionResponse ) { 
           this.responses.add((JaxbExceptionResponse) result);
        } else {
            unknownResultType = true;
        }
        
        if (unknownResultType) {
            logger.info( "Unknown result type " + result.getClass().getCanonicalName()
                    + " from command " + cmd.getClass().getCanonicalName() + ".");
        }
    }

    static Class getListType( Command cmd ) {
        Class cmdClass = cmd.getClass();
        Type genSuper = cmdClass.getGenericSuperclass();
        if( genSuper != null ) {
            if( genSuper instanceof ParameterizedType ) {
                return getClassFromParameterizedListCmd(genSuper, cmdClass);
            }
        }
        Type [] genInts = cmdClass.getGenericInterfaces();
        if( genInts.length > 0 ) { 
           if( genInts[0] instanceof ParameterizedType ) { 
                return getClassFromParameterizedListCmd(genInts[0], cmdClass);
           }
        }
        throw new IllegalStateException("No list type could be found for " + cmd.getClass().getSimpleName() );
    }
    
    private static Class getClassFromParameterizedListCmd(Type genericIntOrSuper, Class cmdClass) { 
        Type[] listTypes = ((ParameterizedType) genericIntOrSuper).getActualTypeArguments();
        if( listTypes.length > 0 ) {
            if( listTypes[0] instanceof ParameterizedType ) {
                Type rawType = ((ParameterizedType) listTypes[0]).getRawType();
                if( Collection.class.isAssignableFrom((Class) rawType) ) { 
                    Type[] returnTypeParamTypes = ((ParameterizedType) listTypes[0]).getActualTypeArguments();
                    if( returnTypeParamTypes.length > 0 ) {
                        return (Class) returnTypeParamTypes[0];
                    }
                } 
            } 
        }
        throw new IllegalStateException("No list type could be found for " + cmdClass.getSimpleName() );
    }
}
