using System;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 用于描述Note判定情况的委托
/// </summary>
public delegate void JudgeEventHandler(JugdeLevel level, Vector3 NotePos, BaseNote baseNote);
public delegate void ReturnPoolEventHandler(BaseNote note);
public enum JugdeLevel
{
    Prefect, Great, Bad, Miss
};
/// <summary>
/// 游戏中所有物件的基类
/// </summary>
/// <remarks>
///     <para>
///         在这里，所有基于这个类派生出来的子对象我们都称之为<paramref name="物件"/>
///     </para>
///     <para>
///         它们都拥有<paramref name="被创建的时间"/>，以及<paramref name="被回收的时间"/>，因此，一个物件至少需要指定一个<paramref name="TargetTime"/>，
///         我们可以将装饰物件从这个类派生出来。并且添加<paramref name="IsDecorated"/>去标识那些物件是装饰，使得我们能每一个物件都受到时间组控制。
///     </para>
///     <para>
///         所以一个每一个物件对象至少都含有一个时间组对象的引用。也许关卡设计者会需要一些装饰物件，装饰物件同样也符合之前物件的特点够更加方便的管理他们。
///         我们有时还需要统计这些物件的数量，以便于我们去完成更加复杂的逻辑。
///     </para>
///     <para>
///     <term>因此每一个物件都应该拥有以下属性：</term>
///     <paramref name="TargetTime"/>,
///     <paramref name="ItemQuantity"/>,
///     <paramref name="IsValid"/>,
///     <paramref name="IsDecorated"/>
///     </para>
///     <para>
///         对于物件的行为，可以分为几种情况
///         一个是物件的运动，物件若是想要确定自己的位置，它就必须拥有歌曲当前的时间，还有当前的速度。
///         一个是物件的判定，判定的过程需要判定的区间，判定的范围，判定的所需的手指使用情况，并且传递自身的坐标，判定结果
///         最后是物件的回收，当一个物件完成了所有的未尽事宜之后，就会自己返回对象池。
///     </para>
/// </remarks>   
public abstract class BaseNote : MonoBehaviour
{
    /// <summary>
    /// 用于存储自身在数据类的引用，用于检索，该字段仅在谱面编辑器中存在
    /// </summary>
    public RuntimeBaseNoteData SelfRef;

    /// <summary>
    /// 物件自身的Transform组件引用
    /// </summary>
    protected Transform _transform;
    /// <summary>
    /// 物件渲染层Transform引用
    /// </summary>
    protected Transform _rendererTransform;
    /// <summary>
    /// 物件定位标记的Transform引用
    /// </summary>
    protected Transform _anchorPointTransform;

    /// <summary>
    /// 目标时间，即一个物件生存周期的终点
    /// </summary>
    [SerializeField]
    public float TargetTime {  get; protected set; }  
    /// <summary>
    /// 自身物件数,用于分数统计
    /// </summary>
    protected int ItemQuantity { get; set; }
    /// <summary>
    /// 当前Note是否已经失效,当被标记为失效是将移除出管理器的判定序列
    /// </summary>
    public bool IsValid { get; set; }
    /// <summary>
    /// 是否为装饰，勾选之后该物件将不会进入判定流程，它只在被实例化时决定
    /// </summary>
    public bool IsDecorated { get; set; }
    /// <summary>
    /// 歌曲必要信息的容器，提供歌曲时间
    /// </summary>
    public SongInformationContainer _songInformation { get; set; }
    /// <summary>
    /// 物件所属的BPM组
    /// </summary>
    public BPMGroup _BPMGroup { get; set; }
    /// <summary>
    /// 当游玩物件被判定时触发该事件
    /// </summary>
    public event JudgeEventHandler OnNoteJudged;
    /// <summary>
    /// 当物件在最后阶段触发，向对象池传递自身的引用
    /// </summary>
    public event ReturnPoolEventHandler OnNoteUesd;
    /// <summary>
    /// 基于当前时间计算该物件的渲染的位置
    /// </summary>
    protected virtual void UpdateRenderer(float currentTime, float noteSpeed)
    {
        /* 物件渲染机制：
         * 一个物件的生命周期是从被游戏控制器生成开始，到被游戏控制器判定标记失活之后，被对象池回收结束
         * 其特点是，一旦生成，就会存在至自己的目标时间
         * 至少生存至 目标时间-最大判定区间 ，至多生存至 目标时间+最大判定区间。
         * 
         * 在一个物件的生命周期内，其位置是关于时间的函数
         * 它由音符的速度，目标时间与当前时间的差值（在未抵达判定区域前时当前时间通常小于目标时间）
         * 因此，它相对于0平面的位移是：f(currentTime)=NoteSpeed*(targetTime-currentTime)
         * 一旦一个音符被实例化，那么在它被回收失活之前，它的位置将如以上运动学方程所示
         * 
         * 渲染时间范围（instantiateOffset）和物件速度（NoteSpeed）在一局游戏中将不会是固定的，甚至在某些情况下，物件速度有可能会是负数
         * 依据这样的方式计算出的物件将会从0平面的反向生成，向着背离玩家的方向运动直到被失活
         * (targetTime-currentTime)这一项的值会从正数，逐渐变成0，最后变成负数
         * 这意味着，即使物件速度发生了更改，物件的位置也会被重新计算，无论变换如何的复杂
         * 物件一定会在目标时间到达判定平面，视存活时间穿过判定平面
         * 
         * 由于需要平衡游戏性能消耗的缘故，在实际的游戏中，一首曲子中的所有物件并不会全部生成
         * 一个物件只有在进入渲染时间范围之后才会被实例化
         * 渲染时间范围是在控制器依据当前游戏的物件速度，以及游戏场景中音符固定的下落距离，反向计算出的一个数值
         * 已经被实例化的物件，即使在渲染时间范围变动后，到达了渲染时间范围之外，它也不会立刻消失
         * 它依然会计算自己的位置，并且等待生命周期的结束
         * 由于这里使用的时间是毫秒，所以在计算时需要更换会国际单位
         */
        float z = (TargetTime - currentTime) * noteSpeed;
        //使用世界坐标进行赋值
        _transform.position = new Vector3(_transform.position.x, _transform.position.y, z);

    }
    protected virtual void UpdateAnchorPoint(float currentTime, float noteSpeed, float beatTime)
    {
        if (_anchorPointTransform.gameObject.activeSelf == true) return;
        if (_rendererTransform.position.z > 800 * noteSpeed * 0.001) return;
        _anchorPointTransform.gameObject.SetActive(true);
    }
    protected virtual void ThisObjectIsValid()
    {
        /* 由于物体其实并不存储于对象池中，对象池只负责提供新的对象
         * 因此，物件在一般情况下是由其他数据结构管理的
         * 为了方便区分那些超过了有效时间的物件
         * 设置了一个用于检查的标志
         */
        IsValid = true; 
    }
    protected virtual void ReturnNoteToPool()
    {
        OnNoteUesd?.Invoke(this);
        //因为在对象池回收时，存储在这些时间里面的函数并不会清空，需要手动清理
        OnNoteJudged = null;
    }
    /// <summary>
    /// 检查自身是否超过判定时间(子类可重写)
    /// </summary>
    protected virtual void CheckMiss(float CurrentTime, float missInterval)
    {
        if (CurrentTime < TargetTime + missInterval) return;
        IsValid = true;
        OnNoteJudged?.Invoke(JugdeLevel.Miss, _transform.position, this);
        OnNoteUesd?.Invoke(this);
    }
    /// <summary>
    /// 用于执行Note的判定
    /// </summary>
    public virtual void CheckHit(Dictionary<int, bool> isValidFingerID, Camera MainCamera)
    {
        OnNoteJudged?.Invoke(JugdeLevel.Prefect, _transform.position, this);
    }
    /// <summary>
    /// 用于初始化游戏所需配置等
    /// </summary>
    public void InitConfigFile()
    {
        //像判定区间，还有游戏难度，关卡属性，可以放在这里初始化
    }
}
