import NpcNormal from "./NpcNormal";
import NpcSupper from "./NpcSuper";
import ResLoaderManager from "../Manager/ResLoaderManager";

/* 
  Npc 的管理调度类
  单例实现
*/
const { ccclass, property } = cc._decorator;

// 空闲展台模型
interface Visit {
  x: number, // x坐标
  y: number, // y坐标
  direction?: string, // 方向
}

// 当程序去随机生成位置时，会遇到一些没有标清楚的点，对这些点进行寻路通常结果会失败，会带来极大的性能开销 通过手动遍历记录点信息让程序可以跳过这些点 坐标点都是独一无二的 (x,y) 作为对象名

@ccclass
export default class NpcManager extends cc.Component {

  // Npc的通用预制体
  @property(cc.Prefab)
  npcPrefab: cc.Prefab = null

  // NPC 添加进入这一层
  @property(cc.TiledLayer)
  wallLayer: cc.TiledLayer = null

  // 对象层 记录了 逗留点 离开点 初始化点的信息 
  @property(cc.TiledObjectGroup)
  pointLayer: cc.TiledObjectGroup = null

  /** 透明层 记录人物需要透明的位置*/
  @property(cc.TiledLayer)
  moveLayer: cc.TiledLayer = null;

  // 空闲展台列表
  freeExhibitions: Array<Visit> = []

  // 所有展台列表 {124: {x: y,}}
  allExhibitions: Object = {}

  // 参观完成的 NPC 列表, 但是没有空闲展台供其参观, 所以先加入列表 等候下一次调度
  visitFinish: Array<NpcSupper> = []

  // NPC 入场点
  inPointList: Array<any> = []

  // NPC 离场点
  outPointList: Array<any> = []

  // NPC 闲逛点 随机生成，不能重复
  strollList: Array<Visit> = []

  // 所有普通NPC的配置对象
  npcConfig: any = null

  // 所有特殊NPC的配置对象
  npcSC: any = null

  // 通用口头禅列表
  phraseList: Array<string> = null

  // 实例
  protected static instance: NpcManager = null

  /***************** 配置NPC的属性 *****************/

  // 从游戏开始到目前为止的NPC数量 只增加不减少
  allNpcNum: number = null
  // 当前处于场馆中的npc数量 不包括bus上的旅游团
  npcNum: number = null
  // 场上的特殊Npc数量
  specialNpcNum: number = 0;
  // 游戏一开始 游客上限默认为5
  upperLimit: number = null
  // 特殊NPC ID列表
  specialNpcIdList: Array<number> = null

  /***************** 配置NPC的属性 *****************/

  // 获取实例
  static getInstance(): NpcManager {
    if (this.instance === null) {
      this.instance = new NpcManager()
    }
    return this.instance
  }

  // 初始化
  init() {
    // TEST
    // this.freeExhibitions.push({
    //   x: 33,
    //   y: 44,
    //   direction: 'bottom'
    // })

    // 初始化数据
    this.initData()
    // 初始化停留点信息
    this.initPoints()
    // 事件绑定
    this.bindEvent()
  }

  // 初始化数据
  initData() {
    // 获取地图
    let map = cc.find('Canvas/Baseview/obj_map/map100').getComponent(cc.TiledMap)
    // 获取墙的图层
    this.wallLayer = map.getLayer('wall')
    // 获取对象层
    this.pointLayer = map.getObjectGroup("point");
    this.moveLayer = map.getLayer("move");

    // 获取资源管理器的实例 因为只初始化一次, 所以就没必要保存成属性
    let sm = ResLoaderManager.getInstance()

    // 获取npc预制体
    this.npcPrefab = sm.getPrefab('npc')
    // 读取配置表
    this.npcConfig = sm.getConfig('visitor')
    // 获取通用口头禅
    this.phraseList = []

    // NPC 配置数据初始化
    this.allNpcNum = this.npcNum = 0
    this.upperLimit = 40

  }

  /** 初始化 所有的开始 停留 结束 点*/
  initPoints() {

    let list = this.pointLayer.getObjects();
    for (let i = 0; i < list.length; i++) {
      // 临时变量， 只做接收list[i] 的值
      let temp = list[i]
      if (temp.pointType == 0) {
        this.inPointList.push({
          x: temp.posx,
          y: temp.posy
        })
      }
      else if (temp.pointType == 1) {
        let dirStr = '';
        switch (temp.direction) {
          case 1:
            dirStr = 'left';
            break;
          case 2:
            dirStr = 'top';
            break;
          case 3:
            dirStr = 'right';
            break;
          case 4:
            dirStr = 'bottom';
            break;
        }

        let data = {
          x: temp.posx,
          y: temp.posy,
          direction: dirStr
        }
        // 保存所有展品的ID 和对应的信息
        this.allExhibitions[temp['id']] = data
        // TEST
        this.freeExhibitions.push(data)
      }
      else if (temp.pointType == 2) {
        // this.outPointList.push({
        //   x: temp.posx,
        //   y: temp.posy
        // })
      }
    }

  }

  // NPC开始实例化, 不再交由Game类
  npcStart() {
    // 定时器开启, 每隔一段时间触发 实例化NPC进馆
    this.schedule(() => {
      //只有当前npc数量没有超出限制时 才会实例化 
      if (this.npcNum < this.upperLimit) {
        this.instantiateNpc()
      }
    }, 5)
  }

  /** 添加游客上限*/
  addVisitorMax(num: number) {
    this.upperLimit += num;
  }

  // 事件绑定
  bindEvent() {
    // 参观完成
    cc.systemEvent.on('visitfinish', this.release.bind(this))
    // npc 期望离开
    // cc.systemEvent.on('leave', this.leave.bind(this))
  }

  // 实例化NPC
  instantiateNpc() {
    // 生成实例
    let node = cc.instantiate(this.npcPrefab)

    // 每实例化一个NPC总数+1
    this.npcNum++
    this.allNpcNum++

    // 挂载节点 onload 触发
    this.moveLayer.addUserNode(node)

    // 挂载脚本 再获取其身上的脚本组件 传入出生点坐标信息
    let npc: NpcSupper = node.addComponent(NpcNormal)

    let config = this.npcConfig[~~(Math.random() * this.npcConfig.length)]
    // 增加一些基本口头禅用于进入
    config.visitor_phrase = config.visitor_phrase.concat(this.phraseList)
    // 初始化NPC的核心 一个NPC默认只是个模板，这也是npc初始化的必要步骤之一
    npc.soulInit(config)

    // 初始化出生点 ~~(Math.random() * this.inPointList.length)
    // npc.setBirthPoint(this.inPointList[~~(Math.random() * this.inPointList.length)])
    npc.setBirthPoint(this.inPointList[0])
    // npc.setBirthPoint(this.findStrollPoint(this.wallLayer))
    // 这段函数太过耗时, 先让出执行权
    setTimeout(() => {
      // 资源调度
      this.dispatch(npc)
    }, 100);
  }

  // 调度NPC 前往闲置的展台
  dispatch(npc: NpcSupper) {
    let random = ~~(this.freeExhibitions.length * Math.random())
    // 从空闲展台中，拿取第一位，
    let visit = this.freeExhibitions.splice(random, 1)[0]
    // 如果有值的话说明有空位
    if (visit) {
      // 通知其行进至哪个地点
      let isFeasible = npc.go(visit)
      // 看下当前传入的坐标点是否可行 如果找不到路径， 那么返回false 否则返回true
      if (!isFeasible) {
        console.log('寻路失败')
        // 再次进行调度
        this.dispatch(npc)
        // 同时 当前点将会被清除
      }
    } else {
      // 先找一遍看下它在不在里面， 如果已经在里面就不添加进入
      let isInVisitFinish = this.visitFinish.find(item => item === npc)

      if (isInVisitFinish == undefined) {
        // 将当前NPC先加入闲逛列表
        this.visitFinish.push(npc)
      }

      // 没有空位 NPC进入闲逛
      // 查找闲逛点 传入npc所在的墙的图层， 二是npc当前的位置
      let posi = this.findStrollPoint(npc.coordinate)
      // go go go
      let isFeasible = npc.go(posi)
      // 看下当前传入的坐标点是否可行 如果找不到路径， 那么返回false 否则返回true
      if (!isFeasible) {
        // 他都没有添加进去 清除个锤子
        if (isInVisitFinish == undefined) {
          // 当然 得先清除下当前NPC在闲逛列表中的标记
          this.visitFinish.pop()
        }

        // 再次进行调度
        this.dispatch(npc)
      }
    }
  }

  // 当npc参观完成后， 需要释放其身上的展台信息， 同时将该NPC加入空闲NPC列表中
  release(data: cc.Event.EventCustom) {
    // 接收到了节点
    let npc: NpcSupper = data.getUserData()

    // 释放当前展台
    if (npc.targetCoordinate.direction != undefined) {
      // 只有当它记录了展台信息才释放回去
      this.freeExhibitions.push(npc.targetCoordinate)
    }

    // 判断当前是否有处于闲逛中的npc
    if (this.visitFinish.length > 0) {
      // 调度当前npc
      let freeNpc = this.visitFinish.shift()
      // 进入寻路
      this.dispatch(freeNpc)
    }


    // 调度
    this.dispatch(npc)
  }

  // 寻找空闲可用点 当展品坐标数量不足的情况下，npc可以去展馆内闲逛
  findStrollPoint(coordinate: Visit): Visit {
    let len = this.strollList.length
    if (len <= 0) {
      cc.warn('闲逛点数量为0, 已经安排npc前往应急点')
      return { x: 41, y: 105 }
    } else {
      let { x, y } = this.strollList[~~(Math.random() * len)]
      if (coordinate.x !== x && coordinate.y !== y) { // 避免相同点
        return { x, y }
      } else {
        // 递归调用
        return this.findStrollPoint(coordinate)
      }
    }
  }

}
