package cn.axzo.workflow.core.service.impl;

import cn.axzo.workflow.common.model.request.bpmn.RestBpmnProcessVariable;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.service.BpmnProcessVariableService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.runtime.Execution;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_ID_NOT_EXISTS;
import static cn.axzo.workflow.core.common.code.BpmnVariablesRespCode.PROCESS_VARIABLE_EXIST;
import static cn.axzo.workflow.core.common.code.BpmnVariablesRespCode.PROCESS_VARIABLE_NAME_MUST_BE_NOT_NULL;
import static cn.axzo.workflow.core.common.code.BpmnVariablesRespCode.PROCESS_VARIABLE_NOT_EXIST;
import static cn.axzo.workflow.core.common.code.BpmnVariablesRespCode.PROCESS_VARIABLE_SCOPE_ERROR;
import static cn.axzo.workflow.core.common.code.BpmnVariablesRespCode.PROCESS_VARIABLE_VARS_NOT_NULL;

@Service
@Slf4j
public class BpmnProcessVariableServiceImp implements BpmnProcessVariableService {

    @Resource
    protected RuntimeService runtimeService;

    @Override
    public void createVariable(String executionId, RestBpmnProcessVariable restVariable) {
        Execution execution = getExecutionFromRequest(executionId);
        setSimpleVariable(restVariable, execution, true);
    }

    @Override
    public void updateVariable(String executionId, RestBpmnProcessVariable restVariable) {
        Execution execution = getExecutionFromRequest(executionId);
        setSimpleVariable(restVariable, execution, false);
    }

    @Override
    public void updateVariables(String executionId, List<RestBpmnProcessVariable> restVariables) {
        if (CollectionUtils.isEmpty(restVariables)) {
            throw new WorkflowEngineException(PROCESS_VARIABLE_VARS_NOT_NULL);
        }
        Execution execution = getExecutionFromRequest(executionId);
        for (RestBpmnProcessVariable restVariable : restVariables) {
            setSimpleVariable(restVariable, execution, false);
        }
    }

    @Override
    public void deleteVariables(String executionId, List<String> variableNames, String scope) {
        Execution execution = getExecutionFromRequest(executionId);
        RestBpmnProcessVariable.RestVariableScope variableScope = RestBpmnProcessVariable.RestVariableScope.GLOBAL;
        if (scope != null) {
            variableScope = getScopeFromString(scope);
        }
        if (variableScope == RestBpmnProcessVariable.RestVariableScope.LOCAL) {
            runtimeService.removeVariablesLocal(execution.getId(), variableNames);
        } else {
            runtimeService.removeVariables(execution.getParentId(), variableNames);
        }
    }

    protected Execution getExecutionFromRequest(String executionId) {
        Execution execution = runtimeService.createExecutionQuery().executionId(executionId).singleResult();
        if (execution == null) {
            throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, executionId);
        }
        return execution;
    }

    protected void setSimpleVariable(RestBpmnProcessVariable restVariable, Execution execution, boolean isNew) {
        if (restVariable.getName() == null) {
            throw new WorkflowEngineException(PROCESS_VARIABLE_NAME_MUST_BE_NOT_NULL);
        }
        RestBpmnProcessVariable.RestVariableScope scope = restVariable.getScope();
        if (scope == null) {
            scope = RestBpmnProcessVariable.RestVariableScope.GLOBAL;
        }
        setVariable(execution, restVariable.getName(), restVariable.getValue(), scope, isNew);
    }

    protected void setVariable(Execution execution, String name, Object value, RestBpmnProcessVariable.RestVariableScope scope, boolean isNew) {
        boolean hasVariable = hasVariableOnScope(execution, name, scope);
        if (isNew && hasVariable) {
            throw new WorkflowEngineException(PROCESS_VARIABLE_EXIST, name, execution.getId());
        }
        if (!isNew && !hasVariable) {
            throw new WorkflowEngineException(PROCESS_VARIABLE_NOT_EXIST, execution.getId(), name);
        }

        if (scope == RestBpmnProcessVariable.RestVariableScope.LOCAL) {
            runtimeService.setVariableLocal(execution.getId(), name, value);
        } else {
            if (execution.getParentId() != null) {
                runtimeService.setVariable(execution.getParentId(), name, value);
            } else {
                runtimeService.setVariable(execution.getId(), name, value);
            }
        }
    }

    protected boolean hasVariableOnScope(Execution execution, String variableName, RestBpmnProcessVariable.RestVariableScope scope) {
        boolean variableFound = false;
        if (scope == RestBpmnProcessVariable.RestVariableScope.GLOBAL) {
            if (execution.getParentId() != null && runtimeService.hasVariable(execution.getParentId(), variableName)) {
                variableFound = true;
            }
        } else if (scope == RestBpmnProcessVariable.RestVariableScope.LOCAL) {
            if (runtimeService.hasVariableLocal(execution.getId(), variableName)) {
                variableFound = true;
            }
        }
        return variableFound;
    }

    public static RestBpmnProcessVariable.RestVariableScope getScopeFromString(String scope) {
        if (scope != null) {
            for (RestBpmnProcessVariable.RestVariableScope s : RestBpmnProcessVariable.RestVariableScope.values()) {
                if (s.name().equalsIgnoreCase(scope)) {
                    return s;
                }
            }
            throw new WorkflowEngineException(PROCESS_VARIABLE_SCOPE_ERROR, scope);
        } else {
            return RestBpmnProcessVariable.RestVariableScope.GLOBAL;
        }
    }
}
