/**
 * @file util.ts
 * @description 工具函数集合，提供各种通用功能函数
 */

/**
 * @description 根据角度和长度计算坐标点
 * @param {number} angle 角度值（0-360度）
 * @param {number} len 长度
 * @returns {{x: number, y: number}} 计算出的坐标点
 */
export function getPosByAngle(angle, len) {

  const radian = angle * Math.PI / 180;
  const x = Math.sin(radian) * len;
  const y = Math.cos(radian) * len;

  return { x, y };

}

/**
 * @description 根据两点坐标计算角度
 * @param {number} px 第一个点的x坐标
 * @param {number} py 第一个点的y坐标
 * @param {number} mx 第二个点的x坐标
 * @param {number} my 第二个点的y坐标
 * @returns {number} 计算出的角度值（0-360度）
 */
export function getAngleByPos(px, py, mx, my) {

  const x = Math.abs(px - mx);
  const y = Math.abs(py - my);

  const z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
  const cos = y / z;
  const radina = Math.acos(cos); // 用反三角函数求弧度
  let angle = Math.floor(180 / (Math.PI / radina) * 100) / 100; // 将弧度转换成角度

  if (mx > px && my > py) {// 鼠标在第四象限
    angle = 180 - angle;
  }
  if (mx === px && my > py) {// 鼠标在y轴负方向上
    angle = 180;
  }
  if (mx > px && my === py) {// 鼠标在x轴正方向上
    angle = 90;
  }
  if (mx < px && my > py) {// 鼠标在第三象限
    angle = 180 + angle;
  }
  if (mx < px && my === py) {// 鼠标在x轴负方向
    angle = 270;
  }
  if (mx < px && my < py) {// 鼠标在第二象限
    angle = 360 - angle;
  }

  // console.log('angle: ', angle);
  return angle;

}

/**
 * @description 将目标节点的坐标转换到基准节点的坐标系中
 * @param {cc.Node} baseNode 基准节点
 * @param {cc.Node} targetNode 目标节点
 * @returns {cc.Vec2} 转换后的坐标
 */
export function exchangeNodePos(baseNode, targetNode) {
  return baseNode.convertToNodeSpaceAR(targetNode._parent.convertToWorldSpaceAR(cc.v2(targetNode.x, targetNode.y)));
}

/**
 * @description 生成指定范围内的随机整数
 * @param {number} a 范围边界值1
 * @param {number} b 范围边界值2，默认为0
 * @returns {number} 生成的随机整数
 */
export function RandomInt(a, b = 0) {
  let max = Math.max(a, b);
  let min = Math.min(a, b);
  return Math.floor(Math.random() * (max - min) + min);
}

/**
 * @description 随机打乱数组顺序
 * @param {Array} arr 原始数组
 * @returns {Array} 打乱顺序后的新数组
 */
export function randomSortByArr(arr) {
  const newArr = [];
  const tmpArr = arr.concat();
  while (tmpArr.length > 0) {
    const randomIndex = Math.floor(tmpArr.length * Math.random());
    newArr.push(tmpArr[randomIndex]);
    tmpArr.splice(randomIndex, 1);
  }
  return newArr;
}

/**
 * @description 设置精灵节点的最大尺寸，保持比例
 * @param {cc.Node} sprNode 精灵节点
 * @param {number} maxW 最大宽度
 * @param {number} maxH 最大高度
 */
export function setSprNodeMaxLen(sprNode, maxW, maxH) {
  const sx = maxW / sprNode.width;
  const sy = maxH / sprNode.height;
  const s = Math.min(sx, sy);
  sprNode.scale = Math.round(s * 1000) / 1000;
}

/**
 * @description 将目标节点的本地坐标转换为基准节点父节点坐标系中的坐标
 * @param {cc.Node} baseNode 基准节点
 * @param {cc.Node} targetNode 目标节点
 * @returns {cc.Vec2} 转换后的坐标
 */
export function localPosTolocalPos(baseNode, targetNode) {
	const worldPos = targetNode.parent.convertToWorldSpaceAR(cc.v2(targetNode.x, targetNode.y));
	const localPos = baseNode.parent.convertToNodeSpaceAR(cc.v2(worldPos.x, worldPos.y));
	return localPos;
}

/**
 * @description 将世界坐标转换为基准节点父节点坐标系中的坐标
 * @param {cc.Vec2} worldPos 世界坐标
 * @param {cc.Node} baseNode 基准节点
 * @returns {cc.Vec2} 转换后的本地坐标
 */
export function worldPosToLocalPos(worldPos, baseNode) {
	const localPos = baseNode.parent.convertToNodeSpaceAR(cc.v2(worldPos.x, worldPos.y));
	return localPos;
}

/**
 * @description 获取两个节点之间的缩放比例
 * @param {cc.Node} baseNode 基准节点
 * @param {cc.Node} targetNode 目标节点
 * @param {boolean} maxFlag 是否取最大值，true取最大值，false取最小值
 * @returns {number} 计算出的缩放比例
 */
export function getScaleRateBy2Node(baseNode, targetNode, maxFlag = true) {
	const worldRect1 = targetNode.getBoundingBoxToWorld();
	const worldRect2 = baseNode.getBoundingBoxToWorld();

	const sx = worldRect1.width / worldRect2.width;
	const sy = worldRect1.height / worldRect2.height;
	if (maxFlag) {
		return Math.max(sx, sy);
	} else {
		return Math.min(sx, sy);
	}
}

/**
 * @description 计算两点之间的距离
 * @param {cc.Vec2|{x:number,y:number}} start 起始点
 * @param {cc.Vec2|{x:number,y:number}} end 终点
 * @returns {number} 两点之间的距离
 */
export function getDistance (start, end){
    var pos = cc.v2(start.x - end.x, start.y - end.y);
    var dis = Math.sqrt(pos.x*pos.x + pos.y*pos.y);
    return dis;
}

/**
 * @description 根据URL播放音频
 * @param {string} audio_url 音频URL
 * @param {Function} cb 音频播放完成后的回调函数
 */
export function playAudioByUrl(audio_url, cb=null) {
	if (audio_url) {
		cc.assetManager.loadRemote(audio_url, (err, audioClip) => {
      const audioId = cc.audioEngine.play(audioClip, false, 0.8);
      if (cb) {
        cc.audioEngine.setFinishCallback(audioId, () => {
          cb();
        });
      }
		});
	}
}


/**
 * @description 为按钮添加点击动画效果
 * @param {cc.Node} btn 按钮节点
 * @param {number} time 动画时间，默认0.15秒
 * @param {number} rate 缩放比例，默认1.05
 */
export function btnClickAnima(btn, time=0.15, rate=1.05) {
  btn.tmpScale = btn.scale;
  btn.on(cc.Node.EventType.TOUCH_START, () => {
    cc.tween(btn)
      .to(time / 2, {scale: btn.scale * rate})
      .start()
  })
  btn.on(cc.Node.EventType.TOUCH_CANCEL, () => {
    cc.tween(btn)
      .to(time / 2, {scale: btn.tmpScale})
      .start()
  })
  btn.on(cc.Node.EventType.TOUCH_END, () => {
    cc.tween(btn)
      .to(time / 2, {scale: btn.tmpScale})
      .start()
  })
}


/**
 * @description 根据URL加载精灵帧
 * @param {string} url 图片URL
 * @param {Function} cb 加载完成后的回调函数，参数为加载的精灵帧
 */
export function getSpriteFrimeByUrl(url, cb) {
  cc.loader.load({ url }, (err, img) => {
    const spriteFrame = new cc.SpriteFrame(img)
    if (cb) {
      cb(spriteFrame);
    }
  })
}

/**
 * @description 根据资源名称获取精灵节点
 * @param {string} resName 资源名称
 * @returns {cc.Node} 创建的精灵节点
 */
export function getSprNode(resName) {
  const sf = cc.find('Canvas/res/img/' + resName).getComponent(cc.Sprite).spriteFrame;
  const node = new cc.Node();
  node.addComponent(cc.Sprite).spriteFrame = sf;
  return node;
}

/**
 * @description 根据URL创建精灵节点
 * @param {string} url 图片URL
 * @param {Function} cb 创建完成后的回调函数，参数为创建的精灵组件
 */
export function getSprNodeByUrl(url, cb) {
  const node = new cc.Node();
  const spr = node.addComponent(cc.Sprite);
  getSpriteFrimeByUrl(url, (sf) => {
    spr.spriteFrame = sf;
    if (cb) {
      cb(spr);
    }
  })
}



/**
 * @description 播放音频剪辑
 * @param {cc.AudioClip} audioClip 音频剪辑
 * @param {Function} cb 音频播放完成后的回调函数
 */
export function playAudio(audioClip, cb = null) {
  if (audioClip) {
    const audioId = cc.audioEngine.playEffect(audioClip, false);
    if (cb) {
      cc.audioEngine.setFinishCallback(audioId, () => {
        cb();
      });
    }
  }
}

/**
 * @description 异步延迟函数
 * @param {number} time 延迟时间（秒）
 * @returns {Promise<null>} Promise对象
 */
export async function asyncDelay(time) {
  return new Promise((resolve, reject) => {
    try {
      cc.tween(cc.find('Canvas'))
        .delay(time)
        .call(()=>{
          resolve(null);
        })
        .start();
    } catch (e) {
      reject(e);
    }
  })
}

/**
 * @description 异步加载龙骨动画
 * @param {cc.Node} node 要添加动画的节点
 * @param {Object} param1 动画资源参数
 * @param {Object} param1.skeJsonData 骨骼数据
 * @param {string} param1.skeJsonData.url 骨骼数据URL
 * @param {Object} param1.texJsonData 纹理数据
 * @param {string} param1.texJsonData.url 纹理数据URL
 * @param {Object} param1.texPngData 纹理图片
 * @param {string} param1.texPngData.url 纹理图片URL
 * @returns {Promise<cc.Node>} 返回创建的动画节点
 */
export async function asyncLoadDragonBoneAnime(node, { skeJsonData: { url: skeJsonDataUrl }, texJsonData: { url: texJsonDataUrl }, texPngData: { url: texPngDataUrl } }) {

    if (!texPngDataUrl || !texJsonDataUrl || !texPngDataUrl
        || texPngDataUrl == '' || texJsonDataUrl == '' || texPngDataUrl == '') {
        return;
    }

    return new Promise((resolve, reject) => {
        if (node.animaNode) {
            node.animaNode.removeFromParent();
        }
        const animaNode = new cc.Node();
        animaNode.name = 'animaNode';
        animaNode.parent = node;
        animaNode.active = true;
        node.animaNode = animaNode;

        const dragonDisplay = animaNode.addComponent(dragonBones.ArmatureDisplay);

        const loadTexture = new Promise((resolve, reject) => {
            cc.assetManager.loadRemote(texPngDataUrl, (error, texture) => {
                if (error) {
                    reject(error);
                }
                resolve(texture);
            });
        });

        const loadTexJsonData = new Promise((resolve, reject) => {
            cc.assetManager.loadAny({ url: texJsonDataUrl }, (error, atlasJson) => {
                if (error) {
                    reject(error);
                }
                resolve(atlasJson);
            });
        });

        const loadSkeJsonData = new Promise((resolve, reject) => {
            cc.assetManager.loadAny({ url: skeJsonDataUrl }, (error, dragonBonesJson) => {
                if (error) {
                    reject(error);
                }
                resolve(dragonBonesJson);
            });
        });

        Promise.all([loadTexture, loadTexJsonData, loadSkeJsonData]).then(([texture, atlasJson, dragonBonesJson]) => {
            const atlas = new dragonBones.DragonBonesAtlasAsset();
            atlas.atlasJson = JSON.stringify(atlasJson);
            atlas.texture = texture;

            const asset = new dragonBones.DragonBonesAsset();
            asset.dragonBonesJson = JSON.stringify(dragonBonesJson);

            dragonDisplay.dragonAtlasAsset = atlas;
            dragonDisplay.dragonAsset = asset;

            let armatureNames = dragonBonesJson.armature.map(data => data.name);

            if (armatureNames.length > 0) {
                dragonDisplay.armatureName = armatureNames[0];
            }
            resolve(animaNode);
        });
    });
}

/**
 * @class FireworkSettings
 * @description 烟花效果的配置类
 */
export class FireworkSettings {
  /** @type {cc.Node} 父节点 */
  baseNode;
  /** @type {Array<cc.Node>} 火花节点的数组 */
  nodeList;
  /** @type {cc.Vec2} 发射点 */
  pos;
  /** @type {cc.Vec2} 发射方向 */
  side;
  /** @type {number} 扩散范围 */
  range;
  /** @type {number} 发射数量 */
  number;
  /** @type {number} 缩放范围 */
  scalseRange;
  
  /**
   * @description 构造函数
   * @param {cc.Node} baseNode 父节点
   * @param {Array<cc.Node>} nodeList 火花节点的数组
   * @param {cc.Vec2} pos 发射点，默认为(0,0)
   * @param {cc.Vec2} side 发射方向，默认为(0,100)
   * @param {number} range 扩散范围，默认为50
   * @param {number} number 发射数量，默认为100
   * @param {number} scalseRange 缩放范围，默认为0
   */
  constructor(baseNode, nodeList,
    pos = cc.v2(0, 0),
    side = cc.v2(0, 100),
    range = 50,
    number = 100,
    scalseRange = 0
  ) {
    this.baseNode = baseNode;
    this.nodeList = nodeList;
    this.pos = pos;
    this.side = side;
    this.range = range;
    this.number = number;
    this.scalseRange = scalseRange;
  }

  /**
   * @description 复制一个烟花设置对象
   * @param {FireworkSettings} firework 要复制的烟花设置对象
   * @returns {FireworkSettings} 新的烟花设置对象
   * @static
   */
  static copy(firework) {
    return new FireworkSettings(
      firework.baseNode,
      firework.nodeList,
      firework.pos,
      firework.side,
      firework.range,
      firework.number,
    );
  }
}

/**
 * @description 显示烟花效果
 * @param {FireworkSettings} fireworkSettings 烟花设置对象
 * @returns {Promise<void>}
 */
export async function showFireworks(fireworkSettings) {
  const { baseNode, nodeList, pos, side, range, number, scalseRange } = fireworkSettings;
  new Array(number).fill(' ').forEach(async (_, i) => {

    let rabbonNode = new cc.Node();
    rabbonNode.parent = baseNode;
    rabbonNode.x = pos.x;
    rabbonNode.y = pos.y;
    rabbonNode.angle = 60 * Math.random() - 30;

    let node = cc.instantiate(nodeList[RandomInt(nodeList.length)]);
    node.parent = rabbonNode;
    node.active = true;
    node.x = 0;
    node.y = 0;
    node.angle = 0;
    node.scale = (Math.random() - 0.5) * scalseRange + 1;

    const rate = Math.random();
    const angle = Math.PI * (Math.random() * 2 - 1);

    await asyncTweenBy(rabbonNode, 0.3, {
      x: side.x * rate + Math.cos(angle) * range * rate,
      y: side.y * rate + Math.sin(angle) * range * rate
    }, {
      easing: 'quadIn'
    });

    cc.tween(rabbonNode)
      .by(8, { y: -2000 })
      .start();

    cc.tween(rabbonNode)
      .to(5, { scale: (Math.random() - 0.5) * scalseRange + 1 })
      .start();

    rabbonFall(rabbonNode);

    await asyncDelay(Math.random());
    cc.tween(node)
      .by(0.15, { x: -10, angle: -10 })
      .by(0.3, { x: 20, angle: 20 })
      .by(0.15, { x: -10, angle: -10 })
      .union()
      .repeatForever()
      .start();

    cc.tween(rabbonNode)
      .delay(5)
      .to(0.3, { opacity: 0 })
      .call(() => {
        node.stopAllActions();
        node.active = false;
        node.parent = null;
        node = null;
      })
      .start();
  });
}

/**
 * @description 烟花粒子下落效果
 * @param {cc.Node} node 烟花粒子节点
 * @returns {Promise<void>}
 * @private
 */
async function rabbonFall(node) {
  const time = 1 + Math.random();
  const offsetX = RandomInt(-200, 200) * time;
  await asyncTweenBy(node, time, { x: offsetX, angle: offsetX * 60 / 200 });
  rabbonFall(node);
}


/**
 * @description 异步执行 tween.to 动画
 * @param {cc.Node} node 目标节点
 * @param {number} duration 动画持续时间
 * @param {Object} obj 动画属性对象
 * @param {Object} ease 缓动对象
 * @returns {Promise<null>} Promise对象
 */
export async function asyncTweenTo(node, duration, obj, ease = undefined) {
  return new Promise((resolve, reject) => {
    try {
      cc.tween(node)
        .to(duration, obj, ease)
        .call(() => {
          resolve(null);
        })
        .start();
    } catch (e) {
      reject(e);
    }
  });
}

/**
 * @description 异步执行 tween.by 动画
 * @param {cc.Node} node 目标节点
 * @param {number} duration 动画持续时间
 * @param {Object} obj 动画属性对象
 * @param {Object} ease 缓动对象
 * @returns {Promise<null>} Promise对象
 */
export async function asyncTweenBy(node, duration, obj, ease = undefined) {
  return new Promise((resolve, reject) => {
    try {
      cc.tween(node)
        .by(duration, obj, ease)
        .call(() => {
          resolve(null);
        })
        .start();
    } catch (e) {
      reject(e);
    }
  });
}

/**
 * @description 显示三重烟花效果（左中右）
 * @param {cc.Node} baseNode 父节点
 * @param {Array<cc.Node>} rabbonList 烟花粒子节点列表
 */
export function showTrebleFirework(baseNode, rabbonList) {
  const middle = new FireworkSettings(baseNode, rabbonList);
  middle.pos = cc.v2(0, -400);
  middle.side = cc.v2(0, 1000);
  middle.range = 200;
  middle.number = 100;
  middle.scalseRange = 0.4;

  const left = FireworkSettings.copy(middle);
  left.pos = cc.v2(-600, -400);
  left.side = cc.v2(200, 1000);

  const right = FireworkSettings.copy(middle);
  right.pos = cc.v2(600, -400);
  right.side = cc.v2(-200, 1000);

  showFireworks(middle);
  showFireworks(left);
  showFireworks(right);
}


/**
 * @description 作业完成时的回调函数
 * @param {Object} data 作业完成的数据
 */
export function onHomeworkFinish(data) {
  const middleLayer = cc.find('middleLayer');
  if (middleLayer) {
    const middleLayerComponent = middleLayer.getComponent('middleLayer');
    if (middleLayerComponent.role == 'student') {
      middleLayerComponent.onHomeworkFinish(() => { }, data);
    }
  } else {
    console.log('onHomeworkFinish');
  }
}