import ExpressionType from '../enums/ExpressionType'
import ReturnWayException from '../exception/ReturnWayException'

export default class Delegate {
  exp
  source
  lambdaOutProps = []
  objectNames

  constructor (exp, source, objectNames) {
    this.exp = exp
    this.source = source
    if (objectNames) {
      this.objectNames = objectNames
    } else {
      this.objectNames = []
    }
  }

  // 执行程序，执行前，参数必须实例化
  async invoke (params) {
    if (params == null) {
      params = []
    }
    // 把初始参数给参数表
    this.objectNames = params
    // 沿根Expression节点遍历，把delegate传递下去
    this.putDelegate(this.exp)
    // 调用exp的执行过程
    try {
      return await this.exp.invoke()
    } catch (returnWay) {
      if (returnWay instanceof ReturnWayException) {
        return returnWay.getReturnObject()
      }
      throw returnWay
    }
  }

  // 沿根节点递归，传递delegate的过程
  putDelegate (parent) {
    for (const child of parent.children) {
      // 有些节点会放空的子节点
      if (child == null) {
        continue
      }
      // try特殊处理
      if (child.type === ExpressionType.Try) {
        const exps = child.value
        if (exps[0] != null) {
          exps[0].setDelegate(this)
          this.putDelegate(exps[0])
        }
        if (exps[1] != null) {
          exps[1].setDelegate(this)
          this.putDelegate(exps[1])
        }
      }
      child.setDelegate(this)
      this.putDelegate(child)
    }
  }

  // 获取表达式
  getExp () {
    return this.exp
  }

  // 获取对象值存储
  getObjectNames () {
    return this.objectNames
  }

  // 获取lambda外层参数名
  getLambdaOutProps () {
    return this.lambdaOutProps
  }

  // 存储对象值
  put (key, value) {
    this.objectNames[key] = value
  }

  // 获取对象值
  get (key) {
    return this.objectNames[key]
  }

  // 检查是否包含某个键
  containsKey (key) {
    return Object.prototype.hasOwnProperty.call(this.objectNames, key)
  }

  // 获取源代码
  getSource () {
    return this.source
  }

  // 使用参数调用
  async apply (params) {
    if (params === null) {
      params = {}
    }
    const map = this.objectNames
    if (this.lambdaOutProps.length === 0) {
      this.lambdaOutProps.push(Object.keys(map))
    }
    const lambdaMap = {}
    for (const key in map) {
      lambdaMap[key] = map[key]
    }
    lambdaMap.data = params
    return await this.invoke(lambdaMap)
  }
}
