/*
 * Copyright 2017 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.
 * 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.jbpm.marshalling.impl;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.drools.core.common.InternalWorkingMemory;
import org.drools.core.impl.InternalKnowledgeBase;
import org.drools.core.marshalling.impl.MarshallerReaderContext;
import org.drools.core.marshalling.impl.MarshallerWriteContext;
import org.drools.core.marshalling.impl.PersisterEnums;
import org.drools.core.util.StringUtils;
import org.jbpm.process.core.Context;
import org.jbpm.process.core.context.exclusive.ExclusiveGroup;
import org.jbpm.process.core.context.swimlane.SwimlaneContext;
import org.jbpm.process.core.context.variable.VariableScope;
import org.jbpm.process.instance.ContextInstance;
import org.jbpm.process.instance.context.exclusive.ExclusiveGroupInstance;
import org.jbpm.process.instance.context.swimlane.SwimlaneContextInstance;
import org.jbpm.process.instance.context.variable.VariableScopeInstance;
import org.jbpm.workflow.instance.impl.NodeInstanceImpl;
import org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl;
import org.jbpm.workflow.instance.node.*;
import org.kie.api.definition.process.Process;
import org.kie.api.marshalling.ObjectMarshallingStrategy;
import org.kie.api.runtime.process.NodeInstance;
import org.kie.api.runtime.process.NodeInstanceContainer;
import org.kie.api.runtime.process.ProcessInstance;
import org.kie.api.runtime.process.WorkflowProcessInstance;

/**
 * Default implementation of a process instance marshaller.
 * 
 */
public abstract class AbstractProcessInstanceMarshaller implements
        ProcessInstanceMarshaller {

    // Output methods
    public Object writeProcessInstance(MarshallerWriteContext context,
            ProcessInstance processInstance) throws IOException {        
        WorkflowProcessInstanceImpl workFlow = (WorkflowProcessInstanceImpl) processInstance;
        ObjectOutputStream stream = context.stream;
        stream.writeUTF(workFlow.getId());
        stream.writeUTF(workFlow.getProcessId());
        stream.writeInt(workFlow.getState());        

        SwimlaneContextInstance swimlaneContextInstance = (SwimlaneContextInstance) workFlow.getContextInstance(SwimlaneContext.SWIMLANE_SCOPE);
        if (swimlaneContextInstance != null) {
            Map<String, String> swimlaneActors = swimlaneContextInstance.getSwimlaneActors();
            stream.writeInt(swimlaneActors.size());
            for (Map.Entry<String, String> entry : swimlaneActors.entrySet()) {
                stream.writeUTF(entry.getKey());
                stream.writeUTF(entry.getValue());
            }
        } else {
            stream.writeInt(0);
        }
        
        List<NodeInstance> nodeInstances = new ArrayList<NodeInstance>(workFlow.getNodeInstances());
        Collections.sort(nodeInstances,
                new Comparator<NodeInstance>() {

                    public int compare(NodeInstance o1,
                            NodeInstance o2) {
                        return (int) (o1.getId().compareTo(o2.getId()));
                    }
                });
        for (NodeInstance nodeInstance : nodeInstances) {
            stream.writeShort(PersisterEnums.NODE_INSTANCE);
            writeNodeInstance(context,
                    nodeInstance);
        }
        stream.writeShort(PersisterEnums.END);              
        
        List<ContextInstance> exclusiveGroupInstances =
        	workFlow.getContextInstances(ExclusiveGroup.EXCLUSIVE_GROUP);
        if (exclusiveGroupInstances == null) {
        	stream.writeInt(0);
        } else {
        	stream.writeInt(exclusiveGroupInstances.size());
        	for (ContextInstance contextInstance: exclusiveGroupInstances) {
        		ExclusiveGroupInstance exclusiveGroupInstance = (ExclusiveGroupInstance) contextInstance;
        		Collection<NodeInstance> groupNodeInstances = exclusiveGroupInstance.getNodeInstances();
        		stream.writeInt(groupNodeInstances.size());
        		for (NodeInstance nodeInstance: groupNodeInstances) {
        			stream.writeUTF(nodeInstance.getId());
        		}
        	}
        }
        VariableScopeInstance variableScopeInstance = (VariableScopeInstance) workFlow.getContextInstance(VariableScope.VARIABLE_SCOPE);
        Map<String, Object> variables = variableScopeInstance.getVariables();
        List<String> keys = new ArrayList<String>(variables.keySet());
        Collection<Object> values = variables.values();
        
        Collections.sort(keys,
                new Comparator<String>() {

                    public int compare(String o1,
                            String o2) {
                        return o1.compareTo(o2);
                    }
                });
        // Process Variables
                // - Number of non null Variables = nonnullvariables.size()
                // For Each Variable
                    // - Variable Key
                    // - Marshalling Strategy Index
                    // - Marshalled Object
        
        Collection<Object> notNullValues = new ArrayList<Object>();
        for(Object value: values){
            if(value != null){
                notNullValues.add(value);
            }
        }
                
        stream.writeInt(notNullValues.size());
        for (String key : keys) {
            Object object = variables.get(key); 
            if(object != null){
                stream.writeUTF(key);
                // New marshalling algorithm when using strategies
                int useNewMarshallingStrategyAlgorithm = -2;
                stream.writeInt(useNewMarshallingStrategyAlgorithm);
                // Choose first strategy that accepts the object (what was always done)
                ObjectMarshallingStrategy strategy = context.objectMarshallingStrategyStore.getStrategyObject(object);
                stream.writeUTF(strategy.getClass().getName());
                strategy.write(stream, object);
            }
            
        }    
        return null;

    }

    public Object writeNodeInstance(MarshallerWriteContext context,
            NodeInstance nodeInstance) throws IOException {
        ObjectOutputStream stream = context.stream;
        stream.writeUTF(nodeInstance.getId());
        stream.writeLong(nodeInstance.getNodeId());
        writeNodeInstanceContent(stream, nodeInstance, context);
        return null;
    }

    protected void writeNodeInstanceContent(ObjectOutputStream stream,
            NodeInstance nodeInstance, MarshallerWriteContext context)
            throws IOException {
        if (nodeInstance instanceof RuleSetNodeInstance) {
            stream.writeShort(PersisterEnums.RULE_SET_NODE_INSTANCE);
            List<String> timerInstances =
                ((RuleSetNodeInstance) nodeInstance).getTimerInstances();
	        if (timerInstances != null) {
	            stream.writeInt(timerInstances.size());
	            for (String id : timerInstances) {
	                stream.writeUTF(id);
	            }
	        } else {
	            stream.writeInt(0);
	        }
        } else if (nodeInstance instanceof HumanTaskNodeInstance) {
            stream.writeShort(PersisterEnums.HUMAN_TASK_NODE_INSTANCE);
            stream.writeUTF(((HumanTaskNodeInstance) nodeInstance).getWorkItemId());
            List<String> timerInstances =
                ((HumanTaskNodeInstance) nodeInstance).getTimerInstances();
	        if (timerInstances != null) {
	            stream.writeInt(timerInstances.size());
	            for (String id : timerInstances) {
	                stream.writeUTF(id);
	            }
	        } else {
	            stream.writeInt(0);
	        }
        } else if (nodeInstance instanceof WorkItemNodeInstance) {
            stream.writeShort(PersisterEnums.WORK_ITEM_NODE_INSTANCE);
            stream.writeUTF(((WorkItemNodeInstance) nodeInstance).getWorkItemId());
            List<String> timerInstances =
                ((WorkItemNodeInstance) nodeInstance).getTimerInstances();
	        if (timerInstances != null) {
	            stream.writeInt(timerInstances.size());
	            for (String id : timerInstances) {
	                stream.writeUTF(id);
	            }
	        } else {
	            stream.writeInt(0);
	        }
        } else if (nodeInstance instanceof SubProcessNodeInstance) {
            stream.writeShort(PersisterEnums.SUB_PROCESS_NODE_INSTANCE);
            stream.writeUTF(((SubProcessNodeInstance) nodeInstance).getProcessInstanceId());
            List<String> timerInstances =
                ((SubProcessNodeInstance) nodeInstance).getTimerInstances();
	        if (timerInstances != null) {
	            stream.writeInt(timerInstances.size());
	            for (String id : timerInstances) {
	                stream.writeUTF(id);
	            }
	        } else {
	            stream.writeInt(0);
	        }
        } else if (nodeInstance instanceof MilestoneNodeInstance) {
            stream.writeShort(PersisterEnums.MILESTONE_NODE_INSTANCE);
            List<String> timerInstances =
                    ((MilestoneNodeInstance) nodeInstance).getTimerInstances();
            if (timerInstances != null) {
                stream.writeInt(timerInstances.size());
                for (String id : timerInstances) {
                    stream.writeUTF(id);
                }
            } else {
                stream.writeInt(0);
            }
        } else if (nodeInstance instanceof EventNodeInstance) {
        	stream.writeShort(PersisterEnums.EVENT_NODE_INSTANCE);
    	} else if (nodeInstance instanceof TimerNodeInstance) {
            stream.writeShort(PersisterEnums.TIMER_NODE_INSTANCE);
            stream.writeUTF(((TimerNodeInstance) nodeInstance).getTimerId());
        } else if (nodeInstance instanceof JoinInstance) {
            stream.writeShort(PersisterEnums.JOIN_NODE_INSTANCE);
            Map<Long, Integer> triggers = ((JoinInstance) nodeInstance).getTriggers();
            stream.writeInt(triggers.size());
            List<Long> keys = new ArrayList<Long>(triggers.keySet());
            Collections.sort(keys,
                    new Comparator<Long>() {

                        public int compare(Long o1,
                                Long o2) {
                            return o1.compareTo(o2);
                        }
                    });
            for (Long key : keys) {
                stream.writeLong(key);
                stream.writeInt(triggers.get(key));
            }
        } else if (nodeInstance instanceof StateNodeInstance) {
            stream.writeShort(PersisterEnums.STATE_NODE_INSTANCE);
            List<String> timerInstances =
                    ((StateNodeInstance) nodeInstance).getTimerInstances();
            if (timerInstances != null) {
                stream.writeInt(timerInstances.size());
                for (String id : timerInstances) {
                    stream.writeUTF(id);
                }
            } else {
                stream.writeInt(0);
            }
        } else if (nodeInstance instanceof CompositeContextNodeInstance) {
        	if (nodeInstance instanceof DynamicNodeInstance) {
                stream.writeShort(PersisterEnums.DYNAMIC_NODE_INSTANCE);
        	} else {
        		stream.writeShort(PersisterEnums.COMPOSITE_NODE_INSTANCE);
        	}
            CompositeContextNodeInstance compositeNodeInstance = (CompositeContextNodeInstance) nodeInstance;
            List<String> timerInstances =
                ((CompositeContextNodeInstance) nodeInstance).getTimerInstances();
            if (timerInstances != null) {
                stream.writeInt(timerInstances.size());
                for (String id : timerInstances) {
                    stream.writeUTF(id);
                }
            } else {
                stream.writeInt(0);
            }
            VariableScopeInstance variableScopeInstance = (VariableScopeInstance) compositeNodeInstance.getContextInstance(VariableScope.VARIABLE_SCOPE);
            if (variableScopeInstance == null) {
            	stream.writeInt(0);
            } else {
	            Map<String, Object> variables = variableScopeInstance.getVariables();
	            List<String> keys = new ArrayList<String>(variables.keySet());
	            Collections.sort(keys,
	                    new Comparator<String>() {
	                        public int compare(String o1,
	                                String o2) {
	                            return o1.compareTo(o2);
	                        }
	                    });
	            stream.writeInt(keys.size());
	            for (String key : keys) {
	                stream.writeUTF(key);
	                stream.writeObject(variables.get(key));
	            }
            }
            List<NodeInstance> nodeInstances = new ArrayList<NodeInstance>(compositeNodeInstance.getNodeInstances());
            Collections.sort(nodeInstances,
                    new Comparator<NodeInstance>() {

                        public int compare(NodeInstance o1,
                                NodeInstance o2) {
                            return (int) (o1.getId().compareTo(o2.getId()));
                        }
                    });
            for (NodeInstance subNodeInstance : nodeInstances) {
                stream.writeShort(PersisterEnums.NODE_INSTANCE);
                writeNodeInstance(context,
                        subNodeInstance);
            }
            stream.writeShort(PersisterEnums.END);
            List<ContextInstance> exclusiveGroupInstances =
            	compositeNodeInstance.getContextInstances(ExclusiveGroup.EXCLUSIVE_GROUP);
            if (exclusiveGroupInstances == null) {
            	stream.writeInt(0);
            } else {
            	stream.writeInt(exclusiveGroupInstances.size());
            	for (ContextInstance contextInstance: exclusiveGroupInstances) {
            		ExclusiveGroupInstance exclusiveGroupInstance = (ExclusiveGroupInstance) contextInstance;
            		Collection<NodeInstance> groupNodeInstances = exclusiveGroupInstance.getNodeInstances();
            		stream.writeInt(groupNodeInstances.size());
            		for (NodeInstance groupNodeInstance: groupNodeInstances) {
            			stream.writeUTF(groupNodeInstance.getId());
            		}
            	}
            }
        } else if (nodeInstance instanceof ForEachNodeInstance) {
            stream.writeShort(PersisterEnums.FOR_EACH_NODE_INSTANCE);
            ForEachNodeInstance forEachNodeInstance = (ForEachNodeInstance) nodeInstance;
            List<NodeInstance> nodeInstances = new ArrayList<NodeInstance>(forEachNodeInstance.getNodeInstances());
            Collections.sort(nodeInstances,
                    new Comparator<NodeInstance>() {
                        public int compare(NodeInstance o1,
                                NodeInstance o2) {
                            return (int) (o1.getId().compareTo(o2.getId()));
                        }
                    });
            for (NodeInstance subNodeInstance : nodeInstances) {
                if (subNodeInstance instanceof CompositeContextNodeInstance) {
                    stream.writeShort(PersisterEnums.NODE_INSTANCE);
                    writeNodeInstance(context,
                            subNodeInstance);
                }
            }
            stream.writeShort(PersisterEnums.END);
        } else {
            throw new IllegalArgumentException("Unknown node instance type: " + nodeInstance);
        }
    }

    // Input methods
    public ProcessInstance readProcessInstance(MarshallerReaderContext context) throws IOException {
        ObjectInputStream stream = context.stream;
        InternalKnowledgeBase kBase = context.kBase;
        InternalWorkingMemory wm = context.wm;

        WorkflowProcessInstanceImpl processInstance = createProcessInstance();
        processInstance.setId(stream.readUTF());
        String processId = stream.readUTF();
        processInstance.setProcessId(processId);
        Process process = kBase.getProcess(processId);
        if (kBase != null) {
            processInstance.setProcess(process);
        }
        processInstance.setState(stream.readInt());
        long nodeInstanceCounter = stream.readLong();
        processInstance.setKnowledgeRuntime(wm.getKnowledgeRuntime());

        int nbSwimlanes = stream.readInt();
        if (nbSwimlanes > 0) {
            Context swimlaneContext = ((org.jbpm.process.core.Process) process).getDefaultContext(SwimlaneContext.SWIMLANE_SCOPE);
            SwimlaneContextInstance swimlaneContextInstance = (SwimlaneContextInstance) processInstance.getContextInstance(swimlaneContext);
            for (int i = 0; i < nbSwimlanes; i++) {
                String name = stream.readUTF();
                String value = stream.readUTF();
                swimlaneContextInstance.setActorId(name, value);
            }
        }

        while (stream.readShort() == PersisterEnums.NODE_INSTANCE) {
            readNodeInstance(context, processInstance, processInstance);
        }

        int exclusiveGroupInstances = stream.readInt();
    	for (int i = 0; i < exclusiveGroupInstances; i++) {
            ExclusiveGroupInstance exclusiveGroupInstance = new ExclusiveGroupInstance();
            processInstance.addContextInstance(ExclusiveGroup.EXCLUSIVE_GROUP, exclusiveGroupInstance);
            int nodeInstances = stream.readInt();
            for (int j = 0; j < nodeInstances; j++) {
                String nodeInstanceId = stream.readUTF();
                NodeInstance nodeInstance = processInstance.getNodeInstance(nodeInstanceId);
                if (nodeInstance == null) {
                	throw new IllegalArgumentException("Could not find node instance when deserializing exclusive group instance: " + nodeInstanceId);
                }
                exclusiveGroupInstance.addNodeInstance(nodeInstance);
            }
    	}

        // Process Variables
        // - Number of Variables = keys.size()
        // For Each Variable
            // - Variable Key
            // - Marshalling Strategy Index
            // - Marshalled Object
		int nbVariables = stream.readInt();
		if (nbVariables > 0) {
			Context variableScope = ((org.jbpm.process.core.Process) process)
					.getDefaultContext(VariableScope.VARIABLE_SCOPE);
			VariableScopeInstance variableScopeInstance = (VariableScopeInstance) processInstance
					.getContextInstance(variableScope);
			for (int i = 0; i < nbVariables; i++) {
				String name = stream.readUTF();
				try {
			        ObjectMarshallingStrategy strategy = null;
					int index = stream.readInt();
			        // This is the old way of de/serializing strategy objects
			        if ( index >= 0 ) {
			            strategy = context.resolverStrategyFactory.getStrategy( index );
			        }
			        // This is the new way 
			        else if( index == -2 ) { 
			            String strategyClassName = context.stream.readUTF();
			            if ( ! StringUtils.isEmpty(strategyClassName) ) { 
			                strategy = context.resolverStrategyFactory.getStrategyObject(strategyClassName);
			                if( strategy == null ) { 
			                    throw new IllegalStateException( "No strategy of type " + strategyClassName + " available." );
			                }
			            }
			        }
			        // If either way retrieves a strategy, use it
			        Object value = null;
			        if( strategy != null ) { 
			            value = strategy.read( stream );
			        }
					variableScopeInstance.internalSetVariable(name, value);
				} catch (ClassNotFoundException e) {
					throw new IllegalArgumentException(
							"Could not reload variable " + name);
				}
			}
		}
        if (wm != null) {
            processInstance.reconnect();
        }
        return processInstance;
    }

    protected abstract WorkflowProcessInstanceImpl createProcessInstance();

    public NodeInstance readNodeInstance(MarshallerReaderContext context,
            NodeInstanceContainer nodeInstanceContainer,
            WorkflowProcessInstance processInstance) throws IOException {
        ObjectInputStream stream = context.stream;
        String id = stream.readUTF();
        long nodeId = stream.readLong();
        int nodeType = stream.readShort();
        NodeInstanceImpl nodeInstance = readNodeInstanceContent(nodeType,
                stream, context, processInstance);

        nodeInstance.setNodeId(nodeId);
        nodeInstance.setNodeInstanceContainer(nodeInstanceContainer);
        nodeInstance.setProcessInstance((org.jbpm.workflow.instance.WorkflowProcessInstance) processInstance);
        nodeInstance.setId(id);

        switch (nodeType) {
            case PersisterEnums.COMPOSITE_NODE_INSTANCE:
            case PersisterEnums.DYNAMIC_NODE_INSTANCE:
                int nbVariables = stream.readInt();
                if (nbVariables > 0) {
                    Context variableScope = ((org.jbpm.process.core.Process) ((org.jbpm.process.instance.ProcessInstance)
                		processInstance).getProcess()).getDefaultContext(VariableScope.VARIABLE_SCOPE);
                    VariableScopeInstance variableScopeInstance = (VariableScopeInstance) ((CompositeContextNodeInstance) nodeInstance).getContextInstance(variableScope);
                    for (int i = 0; i < nbVariables; i++) {
                        String name = stream.readUTF();
                        try {
                            Object value = stream.readObject();
                            variableScopeInstance.internalSetVariable(name, value);
                        } catch (ClassNotFoundException e) {
                            throw new IllegalArgumentException("Could not reload variable " + name);
                        }
                    }
                }
                while (stream.readShort() == PersisterEnums.NODE_INSTANCE) {
                    readNodeInstance(context,
                            (CompositeContextNodeInstance) nodeInstance,
                            processInstance);
                }
                
                int exclusiveGroupInstances = stream.readInt();
            	for (int i = 0; i < exclusiveGroupInstances; i++) {
                    ExclusiveGroupInstance exclusiveGroupInstance = new ExclusiveGroupInstance();
                    ((org.jbpm.process.instance.ProcessInstance) processInstance).addContextInstance(ExclusiveGroup.EXCLUSIVE_GROUP, exclusiveGroupInstance);
                    int nodeInstances = stream.readInt();
                    for (int j = 0; j < nodeInstances; j++) {
                        String nodeInstanceId = stream.readUTF();
                        NodeInstance groupNodeInstance = processInstance.getNodeInstance(nodeInstanceId);
                        if (groupNodeInstance == null) {
                        	throw new IllegalArgumentException("Could not find node instance when deserializing exclusive group instance: " + nodeInstanceId);
                        }
                        exclusiveGroupInstance.addNodeInstance(groupNodeInstance);
                    }
            	}

                break;
            case PersisterEnums.FOR_EACH_NODE_INSTANCE:
                while (stream.readShort() == PersisterEnums.NODE_INSTANCE) {
                    readNodeInstance(context,
                            (ForEachNodeInstance) nodeInstance,
                            processInstance);
                }
                break;
            default:
            // do nothing
        }

        return nodeInstance;
    }

    protected NodeInstanceImpl readNodeInstanceContent(int nodeType,
            ObjectInputStream stream, MarshallerReaderContext context,
            WorkflowProcessInstance processInstance) throws IOException {
        NodeInstanceImpl nodeInstance = null;
        switch (nodeType) {
            case PersisterEnums.RULE_SET_NODE_INSTANCE:
                nodeInstance = new RuleSetNodeInstance();
                int nbTimerInstances = stream.readInt();
                if (nbTimerInstances > 0) {
                    List<String> timerInstances = new ArrayList<>();
                    for (int i = 0; i < nbTimerInstances; i++) {
                        timerInstances.add(stream.readUTF());
                    }
                    ((RuleSetNodeInstance) nodeInstance).internalSetTimerInstances(timerInstances);
                }
                break;
            case PersisterEnums.HUMAN_TASK_NODE_INSTANCE:
                nodeInstance = new HumanTaskNodeInstance();
                ((HumanTaskNodeInstance) nodeInstance).internalSetWorkItemId(stream.readUTF());
                nbTimerInstances = stream.readInt();
                if (nbTimerInstances > 0) {
                    List<String> timerInstances = new ArrayList<>();
                    for (int i = 0; i < nbTimerInstances; i++) {
                        timerInstances.add(stream.readUTF());
                    }
                    ((HumanTaskNodeInstance) nodeInstance).internalSetTimerInstances(timerInstances);
                }
                break;
            case PersisterEnums.WORK_ITEM_NODE_INSTANCE:
                nodeInstance = new WorkItemNodeInstance();
                ((WorkItemNodeInstance) nodeInstance).internalSetWorkItemId(stream.readUTF());
                nbTimerInstances = stream.readInt();
                if (nbTimerInstances > 0) {
                    List<String> timerInstances = new ArrayList<>();
                    for (int i = 0; i < nbTimerInstances; i++) {
                        timerInstances.add(stream.readUTF());
                    }
                    ((WorkItemNodeInstance) nodeInstance).internalSetTimerInstances(timerInstances);
                }
                break;
            case PersisterEnums.SUB_PROCESS_NODE_INSTANCE:
                nodeInstance = new SubProcessNodeInstance();
                ((SubProcessNodeInstance) nodeInstance).internalSetProcessInstanceId(stream.readUTF());
                nbTimerInstances = stream.readInt();
                if (nbTimerInstances > 0) {
                    List<String> timerInstances = new ArrayList<>();
                    for (int i = 0; i < nbTimerInstances; i++) {
                        timerInstances.add(stream.readUTF());
                    }
                    ((SubProcessNodeInstance) nodeInstance).internalSetTimerInstances(timerInstances);
                }
                break;
            case PersisterEnums.MILESTONE_NODE_INSTANCE:
                nodeInstance = new MilestoneNodeInstance();
                nbTimerInstances = stream.readInt();
                if (nbTimerInstances > 0) {
                    List<String> timerInstances = new ArrayList<>();
                    for (int i = 0; i < nbTimerInstances; i++) {
                        timerInstances.add(stream.readUTF());
                    }
                    ((MilestoneNodeInstance) nodeInstance).internalSetTimerInstances(timerInstances);
                }
                break;
            case PersisterEnums.TIMER_NODE_INSTANCE:
                nodeInstance = new TimerNodeInstance();
                ((TimerNodeInstance) nodeInstance).internalSetTimerId(stream.readUTF());
                break;
            case PersisterEnums.EVENT_NODE_INSTANCE:
                nodeInstance = new EventNodeInstance();
                break;
            case PersisterEnums.JOIN_NODE_INSTANCE:
                nodeInstance = new JoinInstance();
                int number = stream.readInt();
                if (number > 0) {
                    Map<Long, Integer> triggers = new HashMap<Long, Integer>();
                    for (int i = 0; i < number; i++) {
                        long l = stream.readLong();
                        int count = stream.readInt();
                        triggers.put(l,
                                count);
                    }
                    ((JoinInstance) nodeInstance).internalSetTriggers(triggers);
                }
                break;
            case PersisterEnums.COMPOSITE_NODE_INSTANCE:
                nodeInstance = new CompositeContextNodeInstance();
                nbTimerInstances = stream.readInt();
                if (nbTimerInstances > 0) {
                    List<String> timerInstances = new ArrayList<>();
                    for (int i = 0; i < nbTimerInstances; i++) {
                        timerInstances.add(stream.readUTF());
                    }
                    ((CompositeContextNodeInstance) nodeInstance).internalSetTimerInstances(timerInstances);
                }
                break;
            case PersisterEnums.FOR_EACH_NODE_INSTANCE:
                nodeInstance = new ForEachNodeInstance();
                break;
            case PersisterEnums.DYNAMIC_NODE_INSTANCE:
                nodeInstance = new DynamicNodeInstance();
                nbTimerInstances = stream.readInt();
                if (nbTimerInstances > 0) {
                    List<String> timerInstances = new ArrayList<>();
                    for (int i = 0; i < nbTimerInstances; i++) {
                        timerInstances.add(stream.readUTF());
                    }
                    ((CompositeContextNodeInstance) nodeInstance).internalSetTimerInstances(timerInstances);
                }
                break;
            case PersisterEnums.STATE_NODE_INSTANCE:
                nodeInstance = new StateNodeInstance();
                nbTimerInstances = stream.readInt();
                if (nbTimerInstances > 0) {
                    List<String> timerInstances = new ArrayList<>();
                    for (int i = 0; i < nbTimerInstances; i++) {
                        timerInstances.add(stream.readUTF());
                    }
                    ((CompositeContextNodeInstance) nodeInstance).internalSetTimerInstances(timerInstances);
                }
                break;
            default:
                throw new IllegalArgumentException("Unknown node type: " + nodeType);
        }
        return nodeInstance;

    }
}
