/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 com.alibaba.compileflow.idea.graph.codec;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import com.alibaba.compileflow.engine.definition.common.var.IVar;
import com.alibaba.compileflow.engine.definition.tbbpm.AutoTaskNode;
import com.alibaba.compileflow.engine.definition.tbbpm.BreakNode;
import com.alibaba.compileflow.engine.definition.tbbpm.ContinueNode;
import com.alibaba.compileflow.engine.definition.tbbpm.DecisionNode;
import com.alibaba.compileflow.engine.definition.tbbpm.EndNode;
import com.alibaba.compileflow.engine.definition.tbbpm.FlowNode;
import com.alibaba.compileflow.engine.definition.tbbpm.LoopProcessNode;
import com.alibaba.compileflow.engine.definition.tbbpm.NoteNode;
import com.alibaba.compileflow.engine.definition.tbbpm.ScriptTaskNode;
import com.alibaba.compileflow.engine.definition.tbbpm.StartNode;
import com.alibaba.compileflow.engine.definition.tbbpm.SubBpmNode;
import com.alibaba.compileflow.engine.definition.tbbpm.Transition;
import com.alibaba.compileflow.engine.definition.tbbpm.WaitEventTaskNode;
import com.alibaba.compileflow.engine.definition.tbbpm.WaitTaskNode;
import com.alibaba.compileflow.idea.graph.model.AutoTaskNodeModel;
import com.alibaba.compileflow.idea.graph.model.BaseNodeModel;
import com.alibaba.compileflow.idea.graph.model.BreakNodeModel;
import com.alibaba.compileflow.idea.graph.model.ContinueNodeModel;
import com.alibaba.compileflow.idea.graph.model.DecisionNodeModel;
import com.alibaba.compileflow.idea.graph.model.EndNodeModel;
import com.alibaba.compileflow.idea.graph.model.LoopProcessNodeModel;
import com.alibaba.compileflow.idea.graph.model.NoteNodeModel;
import com.alibaba.compileflow.idea.graph.model.ScriptTaskNodeModel;
import com.alibaba.compileflow.idea.graph.model.StartNodeModel;
import com.alibaba.compileflow.idea.graph.model.SubBpmNodeModel;
import com.alibaba.compileflow.idea.graph.model.WaitEventModel;
import com.alibaba.compileflow.idea.graph.model.WaitTaskNodeModel;

/**
 * @author xuan
 * @since 2020/5/19
 */
public class NodeConvert {

    public static FlowNode toNode(BaseNodeModel model) {

        if (null == model) {
            return null;
        }

        if (model instanceof AutoTaskNodeModel) {

            AutoTaskNodeModel autoTaskNodeModel = (AutoTaskNodeModel)model;
            AutoTaskNode autoTaskNode = new AutoTaskNode();
            //
            fillToNode(autoTaskNode, autoTaskNodeModel);
            autoTaskNode.setAction(ActionConvert.toAction(autoTaskNodeModel.getAction()));
            return autoTaskNode;

        } else if (model instanceof DecisionNodeModel) {

            DecisionNodeModel decisionNodeModel = (DecisionNodeModel)model;
            DecisionNode decisionNode = new DecisionNode();
            //
            fillToNode(decisionNode, decisionNodeModel);
            decisionNode.setAction(ActionConvert.toAction(decisionNodeModel.getAction()));
            return decisionNode;

        } else if (model instanceof EndNodeModel) {

            EndNodeModel endNodeModel = (EndNodeModel)model;
            EndNode endNode = new EndNode();
            //
            fillToNode(endNode, endNodeModel);
            return endNode;
        } else if (model instanceof LoopProcessNodeModel) {

            LoopProcessNodeModel loopProcessNodeModel = (LoopProcessNodeModel)model;
            LoopProcessNode loopProcessNode = new LoopProcessNode();
            //
            fillToNode(loopProcessNode, loopProcessNodeModel);
            loopProcessNode.setCollectionVarName(loopProcessNodeModel.getCollectionVarName());
            loopProcessNode.setVariableName(loopProcessNodeModel.getVariableName());
            loopProcessNode.setIndexVarName(loopProcessNodeModel.getIndexVarName());
            loopProcessNode.setVariableClass(loopProcessNodeModel.getVariableClass());
            loopProcessNode.setStartNodeId(loopProcessNodeModel.getStartNodeId());
            loopProcessNode.setEndNodeId(loopProcessNodeModel.getEndNodeId());
            if (null != loopProcessNodeModel.getAllNodes()) {
                loopProcessNodeModel.getAllNodes().forEach(
                    subModel -> loopProcessNode.addNode(NodeConvert.toNode(subModel)));
            }
            return loopProcessNode;

        } else if (model instanceof NoteNodeModel) {

            NoteNodeModel noteNodeModel = (NoteNodeModel)model;
            NoteNode noteNode = new NoteNode();
            //
            fillToNode(noteNode, noteNodeModel);
            noteNode.setComment(noteNodeModel.getComment());
            noteNode.setVisible("true".equals(noteNodeModel.getVisible()));
            return noteNode;

        } else if (model instanceof ScriptTaskNodeModel) {

            ScriptTaskNodeModel scriptTaskNodeModel = (ScriptTaskNodeModel)model;
            ScriptTaskNode scriptTaskNode = new ScriptTaskNode();
            //
            fillToNode(scriptTaskNode, scriptTaskNodeModel);
            scriptTaskNode.setAction(ActionConvert.toAction(scriptTaskNodeModel.getAction()));
            return scriptTaskNode;

        } else if (model instanceof StartNodeModel) {
            StartNodeModel startNodeModel = (StartNodeModel)model;
            StartNode startNode = new StartNode();
            //
            fillToNode(startNode, startNodeModel);
            return startNode;

        } else if (model instanceof SubBpmNodeModel) {

            SubBpmNodeModel subBpmNodeModel = (SubBpmNodeModel)model;
            SubBpmNode subBpmNode = new SubBpmNode();
            //
            fillToNode(subBpmNode, subBpmNodeModel);
            subBpmNode.setType(subBpmNodeModel.getType());
            subBpmNode.setSubBpmCode(subBpmNodeModel.getSubBpmCode());
            subBpmNode.setWaitForCompletion(subBpmNodeModel.isWaitForCompletion());
            List<IVar> varList = VarConvert.toParameterDefineList(subBpmNodeModel.getVars());
            if (null != varList) {
                varList.forEach(subBpmNode::addVar);
            }
            return subBpmNode;

        } else if (model instanceof WaitTaskNodeModel) {

            WaitTaskNodeModel waitTaskNodeModel = (WaitTaskNodeModel)model;
            WaitTaskNode waitTaskNode = new WaitTaskNode();
            //
            fillToNode(waitTaskNode, waitTaskNodeModel);
            waitTaskNode.setInAction(ActionConvert.toAction(waitTaskNodeModel.getInAction()));
            waitTaskNode.setOutAction(ActionConvert.toAction(waitTaskNodeModel.getOutAction()));
            return waitTaskNode;
        } else if (model instanceof ContinueNodeModel) {
            ContinueNodeModel continueNodeModel = (ContinueNodeModel)model;
            ContinueNode continueNode = new ContinueNode();
            fillToNode(continueNode, continueNodeModel);
            continueNode.setExpression(continueNodeModel.getExpression());
            return continueNode;
        } else if (model instanceof BreakNodeModel) {
            BreakNodeModel breakNodeModel = (BreakNodeModel)model;
            BreakNode breakNode = new BreakNode();
            fillToNode(breakNode, breakNodeModel);
            breakNode.setExpression(breakNodeModel.getExpression());
            return breakNode;
        } else if (model instanceof WaitEventModel) {
            WaitEventModel waitEventModel = (WaitEventModel)model;
            WaitEventTaskNode waitEventNode = new WaitEventTaskNode();
            fillToNode(waitEventNode, waitEventModel);
            waitEventNode.setEventName(waitEventModel.getEventName());
            waitEventNode.setInAction(ActionConvert.toAction(waitEventModel.getInAction()));
            return waitEventNode;
        }

        return null;
    }

    public static List<FlowNode> toNodeList(List<BaseNodeModel> modelList) {

        if (null == modelList) {
            return null;
        }

        return modelList.stream().map(NodeConvert::toNode).filter(Objects::nonNull).collect(
            Collectors.toList());
    }

    public static BaseNodeModel toModel(FlowNode node) {

        if (null == node) {
            return null;
        }

        if (node instanceof AutoTaskNode) {
            AutoTaskNode autoTaskNode = (AutoTaskNode)node;
            AutoTaskNodeModel model = AutoTaskNodeModel.of();
            //
            fillToModel(model, autoTaskNode);
            model.setAction(ActionConvert.toModel(autoTaskNode.getAction()));
            return model;
        } else if (node instanceof DecisionNode) {
            DecisionNode decisionNode = (DecisionNode)node;
            DecisionNodeModel model = DecisionNodeModel.of();
            //
            fillToModel(model, decisionNode);
            model.setAction(ActionConvert.toModel(decisionNode.getAction()));
            return model;
        } else if (node instanceof EndNode) {
            EndNode endNode = (EndNode)node;
            EndNodeModel model = EndNodeModel.of();
            //
            fillToModel(model, endNode);
            return model;
        } else if (node instanceof LoopProcessNode) {
            LoopProcessNode loopProcessNode = (LoopProcessNode)node;
            LoopProcessNodeModel model = LoopProcessNodeModel.of();
            //
            fillToModel(model, loopProcessNode);
            model.setCollectionVarName(loopProcessNode.getCollectionVarName());
            model.setVariableName(loopProcessNode.getVariableName());
            model.setIndexVarName(loopProcessNode.getIndexVarName());
            model.setVariableClass(loopProcessNode.getVariableClass());
            model.setStartNodeId(loopProcessNode.getStartNodeId());
            model.setEndNodeId(loopProcessNode.getEndNodeId());
            model.setAllNodes(NodeConvert.toModelList(loopProcessNode.getAllNodes()));
            return model;
        } else if (node instanceof NoteNode) {
            NoteNode noteNode = (NoteNode)node;
            NoteNodeModel model = NoteNodeModel.of();
            //
            fillToModel(model, noteNode);
            model.setComment(noteNode.getComment());
            model.setVisible(noteNode.isVisible() ? "true" : "false");
            return model;
        } else if (node instanceof ScriptTaskNode) {
            ScriptTaskNode scriptTaskNode = (ScriptTaskNode)node;
            ScriptTaskNodeModel model = ScriptTaskNodeModel.of();
            //
            fillToModel(model, scriptTaskNode);
            model.setAction(ActionConvert.toModel(scriptTaskNode.getAction()));
            return model;
        } else if (node instanceof StartNode) {
            StartNode startNode = (StartNode)node;
            StartNodeModel model = StartNodeModel.of();
            //
            fillToModel(model, startNode);
            return model;
        } else if (node instanceof SubBpmNode) {
            SubBpmNode subBpmNode = (SubBpmNode)node;
            SubBpmNodeModel model = SubBpmNodeModel.of();
            //
            fillToModel(model, subBpmNode);
            model.setType(subBpmNode.getType());
            model.setSubBpmCode(subBpmNode.getSubBpmCode());
            model.setWaitForCompletion(subBpmNode.isWaitForCompletion());
            model.setVars(VarConvert.toModelList(subBpmNode.getVars()));
            return model;
        } else if (node instanceof WaitTaskNode) {
            WaitTaskNode waitTaskNode = (WaitTaskNode)node;
            WaitTaskNodeModel model = WaitTaskNodeModel.of();
            //
            fillToModel(model, waitTaskNode);
            model.setInAction(ActionConvert.toModel(waitTaskNode.getInAction()));
            model.setOutAction(ActionConvert.toModel(waitTaskNode.getOutAction()));
            return model;
        } else if (node instanceof ContinueNode) {
            ContinueNode continueNode = (ContinueNode)node;
            ContinueNodeModel model = ContinueNodeModel.of();
            fillToModel(model, continueNode);
            model.setExpression(continueNode.getExpression());
            return model;
        } else if (node instanceof BreakNode) {
            BreakNode breakNode = (BreakNode)node;
            BreakNodeModel model = BreakNodeModel.of();
            fillToModel(model, breakNode);
            model.setExpression(breakNode.getExpression());
            return model;
        } else if (node instanceof WaitEventTaskNode) {
            WaitEventTaskNode waitEventNode = (WaitEventTaskNode)node;
            WaitEventModel model = WaitEventModel.of();
            fillToModel(model, waitEventNode);
            model.setEventName(waitEventNode.getEventName());
            model.setInAction(ActionConvert.toModel(waitEventNode.getInAction()));
            return model;
        }

        return null;
    }

    public static List<BaseNodeModel> toModelList(List<FlowNode> nodeList) {

        if (null == nodeList) {
            return null;
        }

        return nodeList.stream().map(NodeConvert::toModel).filter(Objects::nonNull).collect(
            Collectors.toList());
    }

    private static void fillToNode(FlowNode node, BaseNodeModel model) {

        if (null == node || null == model) {
            return;
        }
        node.setId(model.getId());
        node.setG(model.getG());
        node.setName(model.getName());
        node.setTag(model.getTag());
        //
        List<Transition> inTransitionList = TransitionConvert.toTransitionList(model.getInTransitions());
        if (null != inTransitionList) {
            inTransitionList.forEach(node::addIncomingTransition);
        }
        List<Transition> outTransitionList = TransitionConvert.toTransitionList(model.getOutTransitions());
        if (null != outTransitionList) {
            outTransitionList.forEach(node::addOutgoingTransition);
        }
    }

    private static void fillToModel(BaseNodeModel model, FlowNode node) {

        if (null == node || null == model) {
            return;
        }
        model.setId(node.getId());
        model.setG(node.getG());
        model.setName(node.getName());
        model.setTag(node.getTag());
        //
        model.setOutTransitions(TransitionConvert.toModelList(node.getOutgoingTransitions()));
    }

}
