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

public class JudgeManager : MonoBehaviour
{
    private static JudgeManager instance;
    public static JudgeManager Instance
    {
        get
        {
            if (instance == null)
            {
                instance = FindObjectOfType(typeof(JudgeManager))
                    as JudgeManager;
            }

            return instance;
        }
    }
    
    float latency = 225f;
    float scrollMultiplier = 1.0f;
    float startOffset = -5f;

    private GameObject offset;
    private GameObject noteobj, smobj, lmobj;
    private GameObject motionGuage;
    private GameObject judgeText;

    private GameObject motionSampleDisplayPrefab;

    private Vector3 initialPos;

    private float elapsedTime = 0;

    private float MsPerBeat
    {
        get
        {
            return 60 * 1000f / GameManager.Instance.CurrentTrack.BPM;
        }
    }
    
    private float ScrollSpeed
    {
        get
        {
            return scrollMultiplier * MsPerBeat / 1000f;
        }
    }

    private float elapsedMotion;
    private float motionTimeout;
    private void MotionGuageReset(float timeout = 0f)
    {
        if (timeout <= 0f)
        {
            elapsedMotion = 0f;
            motionGuage.transform.parent.gameObject.SetActive(false);
            return;
        }

        motionTimeout = timeout;
        motionGuage.transform.parent.gameObject.SetActive(true);
        motionGuage.SetActive(true);
    }
    private void MotionGuageUpdate()
    {
        if (motionGuage.transform.parent.gameObject.activeInHierarchy)
            elapsedMotion += Time.deltaTime * 1000f;

        if (elapsedMotion >= motionTimeout)
            MotionGuageReset();
        else
            motionGuage.GetComponent<Image>().fillAmount = elapsedMotion / motionTimeout;
    }

    // Use this for initialization
    void Start()
    {
        offset = GameObject.Find("Offset");
        noteobj = GameObject.Find("Noteobj");
        smobj = GameObject.Find("SMobj");
        lmobj = GameObject.Find("LMobj");
        judgeText = GameObject.Find("Judge");
        motionGuage = GameObject.Find("Motion Guage");

        motionSampleDisplayPrefab = Resources.Load("Motion Sample Display") as GameObject;

        initialPos = offset.transform.position;
        judgeText.SetActive(false);
        MotionGuageReset();

        LoadNotes(GameManager.Instance.CurrentTrack.Notes);
        Instantiate(GameManager.Instance.CurrentTrack.BGM);
    }

    // Update is called once per frame
    void Update()
    {
        elapsedTime += Time.deltaTime * 1000;
        float timing = elapsedTime;

        offset.transform.position = new Vector3(-timing * ScrollSpeed, 0, 0);

        MotionGuageUpdate();

        if (noteobj.transform.childCount <= 0
            && smobj.transform.childCount <= 0
            && lmobj.transform.childCount <= 0)
            Invoke("ShowResult", 2f);

        new Action(() =>
        {
            if (noteobj.transform.childCount <= 0)
                return;

            GameObject obj = noteobj.transform.GetChild(0).gameObject;
            Note note = obj.GetComponent<Note.Controller>().Instance;

            if (note.IsLong && note.Activated)
            {
                if (InputManager.Instance.IsButtonHolding)
                {
                    if (Judge.IsNonEmptyMiss(note, timing, true))
                    {
                        SetJudge(Judge.MISS);
                        DeactivateNote(note);
                    }
                    return;
                }
                if (InputManager.Instance.IsButtonReleased)
                {
                    SetJudge(Judge.TestJudge(note, timing), true);
                    DeactivateNote(note);
                    return;
                }
            }

            Judge judge = Judge.TestJudge(note, timing);

            if (Judge.IsNonEmptyMiss(note, timing))
            {
                SetJudge(judge);
                DeactivateNote(note);
            }

            if (InputManager.Instance.IsButtonPressed)
            {
                SetJudge(judge);

                if (judge == Judge.MISS) // Empty Miss
                {
                    return;
                }

                if (note.IsLong)
                    note.Activated = true;
                else
                    DeactivateNote(note);
            }
        })();

        new Action(() =>
        {
            if (smobj.transform.childCount <= 0)
                return;

            GameObject smo = smobj.transform.GetChild(0).gameObject;
            MotionNote smnote = (MotionNote)smo.GetComponent<Note.Controller>().Instance;

            smnote.Checkpoint();

            if (!smnote.Activated && timing >= (smnote.StartTiming - MsPerBeat))
            {
                GameObject motionSample = Instantiate(motionSampleDisplayPrefab);
                MotionSampleDisplay msd = motionSample.GetComponent<MotionSampleDisplay>();
                msd.sprite = smnote.Image;
                msd.timeout = MsPerBeat;
                smnote.MotionSampleDisplay = msd;
                smnote.Activated = true;
            }
            if(timing >= (smnote.StartTiming - Judge.MaxButtonTimingRange))
            {
                if (smnote.FinalJudgeAction() || (timing > (smnote.EndTiming + Judge.MaxButtonTimingRange)))
                {
                    SetJudge(Judge.TestJudge(smnote, timing, true), true);
                    DeactivateNote(smnote);
                }
            }

//            Debug.Log("T: " + timing + " nQ: " + activatedNotes.Count);

            /*
            for(int i=activatedNotes.Count-1;i>=0;i--)
            {
                MotionNote note = activatedNotes[i];
                if (note.FinalJudgeAction() || (timing > (note.EndTiming + Judge.MaxButtonTimingRange)))
                {
                    SetJudge(Judge.TestJudge(note, timing + 350, false), true);
                    activatedNotes.RemoveAt(i);
                    DeactivateNote(note);
                }
            }*/
        })();
        /*
        new Action(() =>
        {
            if (lmobj.transform.childCount <= 0)
                return;

            GameObject lmo = lmobj.transform.GetChild(0).gameObject;
            MotionNote lmnote = (MotionNote)lmo.GetComponent<Note.Controller>().Instance;

            lmnote.Checkpoint();

            if (!lmnote.Activated && elapsedTime >= (lmnote.StartTiming - MsPerBeat))
            {
                GameObject motionSample = Instantiate(motionSampleDisplayPrefab);
                MotionSampleDisplay msd = motionSample.GetComponent<MotionSampleDisplay>();
                msd.sprite = lmnote.Image;
                msd.timeout = MsPerBeat;
                lmnote.Activated = true;
                lmnote.MotionSampleDisplay = msd;
                activatedNotes.Add(lmnote);
            }

            for (int i = activatedNotes.Count - 1; i >= 0; i--)
            {
                MotionNote note = activatedNotes[i];
                if (elapsedTime > note.StartTiming)
                    MotionGuageReset(note.Length);
                if (note.FinalJudgeAction() || elapsedTime > note.EndTiming + Judge.MaxButtonTimingRange)
                {
                    SetJudge(Judge.TestJudge(note, timing, true));
                    activatedNotes.RemoveAt(i);
                    DeactivateNote(note);

                    MotionGuageReset();
                }
            }
        })();
        */
    }

    private void DeactivateNote(Note note)
    {
        note.Activated = false;
        note.Component.transform.SetParent(offset.transform);
        note.Component.Deactivate();
    }

    private void SetJudge(Judge judge, bool isMotion = false)
    {
        if (!judge.IsBreak)
            GameManager.Instance.Combo++;
        else
            GameManager.Instance.Combo = 0;
        GameManager.Instance.Score += judge.Score;
        GameManager.Instance.JudgeCount[judge]++;

        Debug.Log(judge.Name + (isMotion ? " Motion" : " Note") + " Combo: " + GameManager.Instance.Combo);

        judgeText.SetActive(true);
        judgeText.GetComponent<Text>().text = judge.Name;
        judgeText.GetComponent<Text>().color = judge.Color;
    }

    private void LoadNotes(List<Note> notes)
    {
        foreach (Note note in notes)
        {
            GameObject obj = Instantiate(
                Resources.Load(note.Type.ToString(), typeof(GameObject)),
                noteobj.transform)
                as GameObject;

            if (note is MotionNote && !note.IsLong)
                obj.transform.SetParent(smobj.transform);
            else if (note is MotionNote && note.IsLong)
                obj.transform.SetParent(lmobj.transform);
            else if (note.Type == NoteType.MeasureLine || note.Type == NoteType.BeatLine)
                obj.transform.SetParent(offset.transform);

            if (note.IsLong)
            {
                float length = note.Length * ScrollSpeed;

                var holdTransform = obj.transform.GetChild(1)
                    .GetComponent<RectTransform>();
                var endTransform = obj.transform.GetChild(2)
                    .GetComponent<RectTransform>();

                holdTransform.SetSizeWithCurrentAnchors(
                    RectTransform.Axis.Horizontal, length);
                holdTransform.position += new Vector3(length / 2, 0, 0);
                endTransform.position += new Vector3(length, 0, 0);
            }

            obj.transform.position += initialPos + new Vector3(
                note.StartTiming * ScrollSpeed, 0, 0);

            obj.AddComponent<Note.Controller>().Instance = note;
        }
    }

    private bool onResult = false;

    private void ShowResult()
    {
        if (!onResult)
        {
            GameManager.Instance.SceneTransition("Result", true);
            onResult = true;
        }
    }
}
