using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;

public class TrackInfo
{
    public string Title { get; private set; }
    public string Artist { get; private set; }
    public string Genre { get; private set; }
    public float BPM { get; private set; }
    public int Level { get; private set; }

    public AudioClip BGM { get; private set; }

    public List<string> TrackList { get; private set; }

    public List<Note> Notes { get; private set; }

    public TrackInfo(string path): this(new FileInfo(path)) { }
    public TrackInfo(FileInfo file)
    {
        TrackList = new List<string>();

        ParseBPEHeader(file);
        Notes = ParseBPENote(file);
    }

    private void ParseBPEHeader(FileInfo file)
    {
        using (StreamReader reader
            = new StreamReader(new BufferedStream(file.OpenRead())))
        {
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                if (line == "" || line[0] != '#')
                    continue;

                string[] token = line.Split(new char[] { ' ' }, 2);
                string field = token[0];
                string value = token[1].Trim();

                switch (field)
                {
                    case "#TITLE":
                        Title = value;
                        break;
                    case "#ARTIST":
                        Artist = value;
                        break;
                    case "#GENRE":
                        Genre = value;
                        break;
                    case "#BPM":
                        float outBPM;
                        if (float.TryParse(value, out outBPM))
                            BPM = outBPM;
                        else
                            BPM = 0;
                        break;
                    case "#PLAYLEVEL":
                        int outLevel;
                        if (int.TryParse(value, out outLevel))
                            Level = outLevel;
                        else
                            Level = 0;
                        break;
                    case "#TRACKLIST":
                        TrackList.Add(value);
                        break;
                    case "#WAV":
                        BGM = new WWW("file:///" + file.DirectoryName + '\\' + value).GetAudioClip();
                        break;
                }
            }
        }
    }

    public List<Note> ParseBPENote(FileInfo file)
    {
        List<Note> notes = new List<Note>();
        float? tempLongBtnStart = null;
        float? tempLongMtnStart = null;

        int maxMeasure = 0;

        using (StreamReader reader
            = new StreamReader(new BufferedStream(file.OpenRead())))
        {
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                if (line == "" || line[0] != '#')
                    continue;

                string[] token = line.Split(new char[] { ' ' }, 2);
                string field = token[0];
                string value = token[1].Trim();

                if (field.Length != 7)
                    continue;

                string[] CHANNELS = { "SBT", "LBT", "SMO", "LMO" };

                int measure;
                string channel = field.Substring(4, 3);

                if (!int.TryParse(field.Substring(1, 3), out measure))
                    continue;
                if (Array.FindIndex(CHANNELS, x => x == channel) == -1)
                    continue;

                if (measure > maxMeasure)
                    maxMeasure = measure;

                int seq = value.Length / 2;
                float ms = 4 * 60 * 1000f / BPM;

                for (int i = 0; i < seq; i++)
                {
                    string key = value.Substring(i* 2, 2);
                    if (key == "00")
                        continue;
                    
                    float timing = (measure + (float)i / seq) * ms;

                    if (channel == "SBT")
                    {
                        notes.Add(new Note(key, channel, timing));
                    }
                    else if (channel == "LBT")
                    {
                        if (tempLongBtnStart == null)
                        {
                            tempLongBtnStart = timing;
                            continue;
                        }

                        float start = tempLongBtnStart.Value;
                        notes.Add(new Note(key, channel, start, timing));

                        tempLongBtnStart = null;
                    }

                    Type motionType;
                    if (!MotionNote.keymap.TryGetValue(key, out motionType))
                        continue;

                    if (channel == "SMO")
                    {
                        notes.Add((MotionNote)Activator.CreateInstance(
                            motionType, key, timing));
                    }
                    else if (channel == "LMO")
                    {
                        if (tempLongMtnStart == null)
                        {
                            tempLongMtnStart = timing;
                            continue;
                        }

                        float start = tempLongMtnStart.Value;
                        notes.Add((MotionNote)Activator.CreateInstance(
                            motionType, key, start, timing));

                        tempLongMtnStart = null;
                    }
                }
            }
        }

        for(int i = 0; i <= maxMeasure; i++)
        {
            for(int j = 1; j < 4; j++)
            {
                notes.Add(new Note(NoteType.BeatLine, (i + (float)j / 4) * 4 * 60 * 1000f / BPM));
            }
            notes.Add(new Note(NoteType.BeatLine, (i + 1) * 4 * 60 * 1000f / BPM));
        }

        return notes;
    }
}