package com.huigou.explorer.application.impl;

import com.huigou.exception.ApplicationException;
import com.huigou.explorer.application.ModelApplication;
import com.huigou.explorer.converters.WorkflowModeConverter;
import com.huigou.explorer.domain.model.Model;
import com.huigou.explorer.repository.ModelRepository;
import com.huigou.uasp.bpm.managment.domain.model.ProcDefinition;
import com.huigou.uasp.bpm.managment.repository.ProcDefinitionRespository;
import com.mxgraph.io.mxCodec;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGraphModel;
import com.mxgraph.util.mxXmlUtils;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.apache.commons.io.IOUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.w3c.dom.Document;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * @author yonghuan
 */
@Aspect
@Service
public class ModelApplicationImpl implements ModelApplication {

    private final Logger LOG = LoggerFactory.getLogger(ModelApplicationImpl.class);
    private ModelRepository modelRepository;
    private ProcDefinitionRespository procDefinitionRespository;
    private WorkflowModeConverter workflowModeConverter;
    private RepositoryService repositoryService;
    private String mxGraphXmlTemplate;
    private BpmnXMLConverter bpmnXMLConverter;

    @Autowired
    public void setModelRepository(ModelRepository modelRepository) {
        this.modelRepository = modelRepository;
    }

    @Autowired
    public void setProcDefinitionRespository(ProcDefinitionRespository procDefinitionRespository) {
        this.procDefinitionRespository = procDefinitionRespository;
    }

    @Autowired
    public void setWorkflowModeConverter(WorkflowModeConverter workflowModeConverter) {
        this.workflowModeConverter = workflowModeConverter;
    }

    @Autowired
    public void setRepositoryService(RepositoryService repositoryService) {
        this.repositoryService = repositoryService;
    }

    @Value("classpath:mxGraphXmlTemplate.xml")
    public void setMxGraphXmlTemplate(Resource mxGraphXmlTemplate) throws IOException {
        this.mxGraphXmlTemplate = String.join("\n", IOUtils.readLines(mxGraphXmlTemplate.getInputStream(), StandardCharsets.UTF_8.name()));
    }

    @Autowired
    public void setBpmnXMLConverter(BpmnXMLConverter bpmnXMLConverter) {
        this.bpmnXMLConverter = bpmnXMLConverter;
    }

    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public Model save(Model model) {
        return modelRepository.save(model);
    }

    @Override
    public Model findByProcDefinitionId(String procDefinitionId) {
        return modelRepository.findByProcDefinitionId(procDefinitionId);
    }

    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public void deploy(String id) {
        ProcDefinition pd = procDefinitionRespository.getOne(id);
        Model model = modelRepository.findByProcDefinitionId(id);
        Assert.notNull(model, "模型不存在或者模型还未保存，请先保存模型再进行部署");
        BpmnModel bpmnModel;
        try {
            if (LOG.isInfoEnabled()) {
                LOG.info("mxgraphXml:{}", model.getXml());
            }
            bpmnModel = workflowModeConverter.convertToBpmnModel(model.getXml().getBytes(), StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new ApplicationException(e);
        }
        byte[] bpmnXml = bpmnXMLConverter.convertToXML(bpmnModel);
        if (LOG.isInfoEnabled()) {
            LOG.info(new String(bpmnXml, StandardCharsets.UTF_8));
        }
        Deployment deployment = this.repositoryService.createDeployment()
                .addInputStream(pd.getCode() + ".bpmn", new ByteArrayInputStream(bpmnXml))
                .deploy();
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).singleResult();
        pd.setProcId(processDefinition.getKey());
        procDefinitionRespository.save(pd);
    }

    /**
     * 创建流程时候自动生成流程模型。
     */
    @Transactional
    @Around("execution(* com.huigou.uasp.bpm.managment.application.ProcDefinitionApplication.insertProcDefinition(..))")
    protected Object onInsertProcDefinition(ProceedingJoinPoint pjp) throws Throwable {
        ProcDefinition procDefinition = (ProcDefinition) pjp.getArgs()[0];
        Object returnVal = pjp.proceed(pjp.getArgs());
        if ("proc".equals(procDefinition.getNodeKindId())) {
            // 创建流程时候自动生成流程模型
            Model model = new Model();
            model.setProcDefinitionId(procDefinition.getId());
            Document document = mxXmlUtils.parseXml(mxGraphXmlTemplate);
            mxCodec codec = new mxCodec(document);
            mxGraphModel graphModel = (mxGraphModel) codec.decode(document.getDocumentElement());
            mxCell rootCell = (mxCell) graphModel.getRoot();
            rootCell.setAttribute("label", procDefinition.getName());
            rootCell.setAttribute("code", procDefinition.getCode());
            model.setXml(mxXmlUtils.getXml(codec.encode(graphModel)));
            model.setImageXml("");
            modelRepository.save(model);
        }
        return returnVal;
    }
}
