using System.Collections.Generic;

public class BPMGroupModel
{
    /*
     * 与设计书略微出入,我发现它还与数据类耦合,由于整个数据类就三个数值,完全可以改写为下列更有功能性的数据结构
     * 只需要使用两个值就可以实现数据项的查找与编辑
     * 为了保证其独立性,这里的游戏基础数据依赖外界手动更新,预计会为更新接口提供多个不同的重载,以满足注册事件的需要
     * 
     */
    /// <summary>
    /// 依据序号查找,在目标排序容器中,key为开始时间,value为目标BPM值
    /// </summary>
    private SortedDictionary<int, SortedList<float , float>> BPMData;
    /// <summary>
    /// 这是提供给不同游戏对象的引用对象列表
    /// </summary>
    private SortedDictionary<int, BPMGroup> BPMGroupList;

    private float noteStream;
    private float baseSpeed;
    private float baseBPM;
    private float trackLength;

    public BPMGroupModel(float noteStream, float baseSpeed, float baseBPM, float trackLength)
    {
        //初始化函数,需要传入游戏基本参数
        this.noteStream = noteStream;
        this.baseSpeed = baseSpeed;
        this.baseBPM = baseBPM;
        this.trackLength = trackLength;

        //初始化时,会创建数据字典和对象字典
        BPMData = new SortedDictionary<int, SortedList<float, float>>();
        BPMGroupList = new SortedDictionary<int, BPMGroup>();
    }

    /// <summary>
    /// 返回一个BPM组数据对象,符合Chart的规范,它可以直接嵌入现有数据中
    /// </summary>
    public List<BPMListItem> GetDataObject()
    {
        var list = new List<BPMListItem>();

        foreach (var item in BPMData)
        {
            var valuePairs = item.Value;

            foreach (var pair in valuePairs)
            {
                var bpmItem = new BPMListItem(pair.Key, pair.Value, item.Key);
                list.Add(bpmItem);
            }
        }
        return list;
    }
    /// <summary>
    /// 读取现有符合Chart规范的数据对象,填充这整个数据类
    /// </summary>
    public void ReadDataObject(List<BPMListItem> list)
    {
        var testList = new List<BPMListItem>();

        foreach (var item in list)
        {
            CreateNewBPMGroup(item.number, item.startTime, item.bpm);
        }
    }

    /// <summary>
    /// 在一个编号序列中创建新的数据项
    /// </summary>
    public void CreateNewBPMGroup(int groupNum, float startTime, float currentBPM)
    {
        if (BPMData.TryGetValue(groupNum, out var valuePairs))
        {
            //这里检查项目是否已经创建过,如果已经创建过了就调用编辑方法,因为每次初始化项目时该对象都会默认创建默认对象,这点需要注意
            if (valuePairs.ContainsKey(startTime))
            {
                EditBPMGroup(groupNum, startTime, currentBPM);
                return;// 编辑后直接返回
            }
            valuePairs.Add(startTime, currentBPM);
        }
        else
        {
            BPMData[groupNum] = new SortedList<float, float>
                {
                    { startTime, currentBPM }
                };
            //这里还需要塞一个在BPMGroupList中实例化新BPMGroup对象的子程序
            var group = new BPMGroup( currentBPM, groupNum, noteStream, baseSpeed, baseBPM, trackLength);
            BPMGroupList.Add(groupNum, group);
        }
    }

    /// <summary>
    /// 在一个编号序列中删除指定的数据项
    /// </summary>
    public void DeleteBPMGroup(int groupNum, float startTime)
    {
        if (BPMData.TryGetValue(groupNum, out var valuePairs))
        {

            if (!valuePairs.ContainsKey(startTime))
            {
                //这里需要补充输出错误的语法
                return;
            }
            valuePairs.Remove(startTime);
            //这里对这一整个序列进行检查,如果发现该列表已经空了,直接移除这一个序号
            if (valuePairs.Count == 0)
            {
                DeleteNewBPMGroupIndex(groupNum);
            }
            //这里补充输出删除成功的语法
        }
    }
    /// <summary>
    /// 删除已有的序号,在删除不存在的编号时将输出错误报告
    /// </summary>
    public void DeleteNewBPMGroupIndex(int groupNum)
    {
        if (!BPMData.ContainsKey(groupNum))
        {
            //输出错误报告
            return;
        }
        BPMData.Remove(groupNum);
        BPMGroupList.Remove(groupNum);
    }

    /// <summary>
    /// 输入新的数据,在一个编号序列中对已有的数据项进行编辑
    /// </summary>
    public void EditBPMGroup(int groupNum, float startTime, float newBPM)
    {
        //在预期的设计中,参数列表中的数值传递是被隐藏在编辑面板操作之下的,该函数只能由系统进行调用
        if (BPMData.TryGetValue(groupNum, out var valuePairs))
        {
            if (!valuePairs.ContainsKey(startTime))
            {
                //这里需要补充输出错误的语法
                return;
            }
            valuePairs[startTime] = newBPM;
        }
        else
        {
            //输出错误,非法查询
        }

    }
    /// <summary>
    /// 查询目标BPMGroup
    /// </summary>
    public BPMGroup QueryBPMGroup(int groupNum)
    {
        //检查字典是否为空
        if (BPMGroupList.TryGetValue(groupNum, out var value))
        {
            return value;
        }
        else { return null; }
    }
    /// <summary>
    /// 用于查验目标数据项是否存在
    /// </summary>
    public bool QueryBPMGroupDataItem(int groupNum, float startTime, float newBPM)
    {
        if (BPMData.TryGetValue(groupNum, out var valuePairs))
        {
            if (!valuePairs.ContainsKey(startTime))
            {
                return false;
            }
        }
        else
        {
            return false;
        }

        return true;
    }
    /// <summary>
    /// 每次时间变化时将会调用这个方法,将所有的BPMGroup对象调整为最新的状态
    /// </summary>
    public void UpdateBPMGroup(float currentTime)
    {
        //遍历字典,使用当前时间去查找所有列表小于当前时间的最大值
        foreach (var item in BPMData)
        {
            var value = FindMin(item.Value, currentTime);
            BPMGroupList[item.Key].CurrentBPM = value;
        }
    }
    private float FindMin(SortedList<float, float> valuePairs, float currentTime)
    {
        //这里先使用顺序查找实现,一般情况下时间组都不超过10个左右,哪怕是顺序查找也是可以接受的,之后优化阶段再考虑上二分
        //从尾部向前搜索
        float min = 114514;
        for (int i = valuePairs.Count - 1; i >= 0; i--)
        {
            if (valuePairs.Keys[i] < currentTime)
            {
                min = valuePairs[valuePairs.Keys[i]];
                break;
            }
        }
        return min;
    }
    public List<(int GroupNum, float startTime, float currentBPM)> GetAllBPMDataItem()
    {
        var list =new List<(int GroupNum,float startTime,float currentBPM)>();
        foreach (var BPMDataList in BPMData)
        {
            foreach (var pair in BPMDataList.Value)
            {
                list.Add((BPMDataList.Key, pair.Key, pair.Value));
            }
        }
        return list;
    }
    /// <summary>
    /// 在编辑器中会遇到各种直接修改游戏初始数值的情况,这个方法会遍历BPMGroupList,更新其中有关游戏设置的数值
    /// </summary>
    public void UpdatePlayerSetting(float noteStream, float baseSpeed, float baseBPM, float trackLength)
    {
        //修改内部数值
        this.noteStream = noteStream;
        this.baseSpeed = baseSpeed;
        this.baseBPM = baseBPM;
        this.trackLength = trackLength;
        foreach (var item in BPMGroupList.Values)
        {
            item.UpdatePlayerSetting(noteStream, baseSpeed, baseBPM, trackLength);
        }
    }
}
