package org.drools.persistence.processinstance;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.PreUpdate;
import javax.persistence.Transient;
import javax.persistence.Version;

import org.drools.WorkingMemory;
import org.drools.common.InternalRuleBase;
import org.drools.common.InternalWorkingMemory;
import org.drools.marshalling.impl.MarshallerReaderContext;
import org.drools.marshalling.impl.MarshallerWriteContext;
import org.drools.marshalling.impl.ProcessInstanceMarshaller;
import org.drools.marshalling.impl.ProcessMarshallerRegistry;
import org.drools.process.instance.impl.ProcessInstanceImpl;
import org.drools.runtime.process.ProcessInstance;
import org.hibernate.annotations.CollectionOfElements;

@Entity
public class ProcessInstanceInfo {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private  Long processInstanceId;
    
    @Version
    @Column(name = "OPTLOCK")
    private int         version;
    
    private String processId;
    private Date startDate;
    private Date lastReadDate;
    private Date lastModificationDate;
    private int state;
    // TODO How do I mark a process instance info as dirty when the process
    // instance
    // has changed (so that byte array is regenerated and saved) ?
    private @Lob
    byte[] processInstanceByteArray;
    @CollectionOfElements
    private Set<String> eventTypes = new HashSet<String>();
    private @Transient
    ProcessInstance processInstance;

    ProcessInstanceInfo() {
    }

    public ProcessInstanceInfo(ProcessInstance processInstance) {
        this.processInstance = processInstance;
        this.processId = processInstance.getProcessId();
        startDate = new Date();
    }

    public long getId() {
        return processInstanceId;
    }

    public String getProcessId() {
        return processId;
    }

    public Date getStartDate() {
        return startDate;
    }

    public Date getLastModificationDate() {
        return lastModificationDate;
    }

    public Date getLastReadDate() {
        return lastReadDate;
    }

    public void updateLastReadDate() {
        lastReadDate = new Date();
    }

    public int getState() {
        return state;
    }

    public ProcessInstance getProcessInstance(WorkingMemory workingMemory) {
        if (processInstance == null) {
            try {
                ByteArrayInputStream bais = new ByteArrayInputStream(
                        processInstanceByteArray);
                MarshallerReaderContext context = new MarshallerReaderContext(
                        bais, (InternalRuleBase) workingMemory.getRuleBase(), null, null);
                context.wm = (InternalWorkingMemory) workingMemory;
                ProcessInstanceMarshaller marshaller = getMarshallerFromContext(context);
                processInstance = marshaller.readProcessInstance(context);
                context.close();
            } catch (IOException e) {
                e.printStackTrace();
                throw new IllegalArgumentException(
                        "IOException while loading process instance: "
                                + e.getMessage());
            }
        }
        return processInstance;
    }

    private ProcessInstanceMarshaller getMarshallerFromContext(
            MarshallerReaderContext context) throws IOException {
        ObjectInputStream stream = context.stream;
        String processInstanceType = stream.readUTF();
        return ProcessMarshallerRegistry.INSTANCE
                .getMarshaller(processInstanceType);
    }

    private void saveProcessInstanceType(MarshallerWriteContext context,
            ProcessInstance processInstance, String processInstanceType)
            throws IOException {
        ObjectOutputStream stream = context.stream;
        // saves the processInstance type first
        stream.writeUTF(processInstanceType);
    }

    @PreUpdate
    public void update() {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            MarshallerWriteContext context = new MarshallerWriteContext(baos,
                    null, null, null, null);
            String processType = ((ProcessInstanceImpl) processInstance).getProcess()
                    .getType();
            saveProcessInstanceType(context, processInstance, processType);
            ProcessInstanceMarshaller marshaller = ProcessMarshallerRegistry.INSTANCE.getMarshaller(processType);
            marshaller.writeProcessInstance(
                    context, processInstance);
            context.close();
        } catch (IOException e) {
            throw new IllegalArgumentException(
                    "IOException while storing process instance "
                            + processInstance.getId() + ": " + e.getMessage());
        }
        byte[] newByteArray = baos.toByteArray();
        if (!Arrays.equals(newByteArray, processInstanceByteArray)) {
            this.state = processInstance.getState();
            this.lastModificationDate = new Date();
            this.processInstanceByteArray = newByteArray;
            this.eventTypes.clear();
            for (String type : processInstance.getEventTypes()) {
                eventTypes.add(type);
            }
        }
    }
}
