import { ILogger, Inject, Provide } from '@midwayjs/core';
import * as moment from 'moment';
import { FlowContext } from './context';
import { FlowNode } from './node';
import { FlowResult } from './result';

/**
 * 执行器
 */
@Provide()
export class FlowExecutor {
  @Inject()
  logger: ILogger;

  /**
   * 执行一个节点
   * @param node
   * @param context
   * @param callback
   */
  async one(node: FlowNode, context: FlowContext, callback?: (res) => void) {
    const start = moment().valueOf();
    const result = await node.invoke(context);
    const end = moment().valueOf();
    callback &&
      callback({
        result,
        nodeId: node.id,
        duration: end - start,
        nextNodeId: null,
        count: context.getCount(),
        isEnd: true,
      });
    return result;
  }
  /**
   * 串行执行
   * @param nodes
   * @param context
   */
  async serial(
    nodes: FlowNode[],
    context: FlowContext,
    callback?: (res) => void
  ): Promise<FlowResult> {
    let isEnd = false;
    // 查找开始节点
    const startNode = nodes.find(node => node.type == 'start');
    // 当前节点
    let currentNode = await this.execNode(nodes, context, startNode.id);
    let result: FlowResult;
    if (currentNode.type == 'end') {
      isEnd = true;
    }

    // 节点执行结果
    const nodesResult = [];

    // 保存节点执行结果
    const saveNodeResult = (currentNode, result) => {
      nodesResult.push({
        ...currentNode,
        result,
      });
    };

    while (currentNode) {
      const start = moment().valueOf();
      try {
        // 执行当前节点
        result = await currentNode.invoke(context);

        // 保存节点执行结果
        saveNodeResult(currentNode, result);

        // 更新当前节点信息
        const flowNodeExec = context.getFlowNodeExec();
        flowNodeExec.prev = flowNodeExec.current;
        flowNodeExec.current = flowNodeExec.next;
        context.setFlowNodeExec(flowNodeExec);

        // 如果当前节点是 end，则结束
        if (currentNode.type == 'end') {
          isEnd = true;
          flowNodeExec.next = null;
        }
      } catch (error) {
        result = {
          success: false,
          error: {
            nodeId: currentNode.id,
            message: error.message,
            options: currentNode.config.options,
          },
        };

        // 保存节点执行结果
        saveNodeResult(currentNode, result);

        this.logger.error('执行节点出错', error);
        isEnd = true;
      }
      const end = moment().valueOf();
      callback &&
        callback({
          result,
          inputParams: currentNode.inputParams,
          nextNodeId: context.getFlowNodeExec().next,
          count: context.getCount(),
          duration: end - start,
          nodeId: currentNode.id,
          isEnd,
        });
      if (isEnd) {
        break;
      }
      // 获取下一个节点
      currentNode = await this.execNode(nodes, context, result.next);
    }

    result.nodesResult = nodesResult;

    return result;
  }

  /**
   * 获得要执行的节点
   * @param context
   */
  async execNode(
    nodes: FlowNode[],
    context: FlowContext,
    nextId?: string
  ): Promise<FlowNode> {
    const flowNodeExec = context.getFlowNodeExec();
    // 当前要执行的节点
    let execId: string;

    // 查找和设置后续节点
    const findAndSetNext = (source: string) => {
      // 找到与当前节点相连的所有下一节点
      const nextNodeIds = context
        .getFlowGraph()
        .edges.filter(line => line.source == source)
        .map(line => line.target);
      if (nextNodeIds.length === 0) {
        return;
      }
      if (!flowNodeExec.current) {
        flowNodeExec.current = source;
      }
      // 设置下一节点，如果是判断器或者分类器，有可能会有多个
      flowNodeExec.nextList = nextNodeIds;
      // 如果只有一个下一节点，则直接设置
      if (nextNodeIds.length == 1) {
        flowNodeExec.next = nextNodeIds[0];
      }
      // 更新执行信息
      context.setFlowNodeExec(flowNodeExec);
    };

    // 如果当前节点是 end，则返回 undefined 表示流程结束
    if (flowNodeExec.current === 'end') {
      return undefined;
    }

    // 如果已经指定了下一个节点
    if (nextId) {
      execId = nextId;
      findAndSetNext(nextId);
    } else {
      findAndSetNext(flowNodeExec.current);
      execId = flowNodeExec.current;
    }

    // 返回要执行的节点
    return nodes.find(node => node.id === execId);
  }

  /**
   * 并行执行
   * @param nodes
   * @param context
   */
  async parallel(nodes: FlowNode[], context: FlowContext): Promise<void> {
    await Promise.all(nodes.map(node => node.invoke(context)));
  }

  /**
   * 嵌套执行
   * @param nodes
   * @param context
   */
  async nested(
    nodes: (FlowNode | FlowNode[])[],
    context: FlowContext
  ): Promise<void> {
    for (const item of nodes) {
      if (Array.isArray(item)) {
        await this.parallel(item, context);
      } else {
        await item.invoke(context);
      }
    }
  }
}
