using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Valve.VR;
using System;
using Valve.VR.InteractionSystem;

[RequireComponent(typeof(AudioSource))]
public class PlayEngine : SingletonBehaviour<PlayEngine>
{
    public LevelScriptableObject[] levelList;
    public ForwardNoteObject ForwardNoteObject;
    public EdgeNoteObject[] EdgeNoteObjects = new EdgeNoteObject[4];
    public ReloadNoteObject ReloadNoteObject;

    private double startTime;
    
    private Level level;

    private AudioSource audioSource;
    private AudioClip audioClip;

    private bool isPlaying;
    private double startDspTime;

    public int test;

    public List<GameObject> hitEffectPrefabs = new List<GameObject>();
    public BlinkLight edgeHitEffectPrefab;

    public AudioClip gunSfx;

    public GameObject explosion;

    public SteamVR_Action_Boolean fire;
    public SteamVR_Input_Sources leftHand;
    public SteamVR_Input_Sources rightHand;

    public GameObject player;

    private MotionTracker leftHandTracker = new MotionTracker();
    private MotionTracker rightHandTracker = new MotionTracker();

    private int combo;
    private int score;

    [SerializeField]
    private Transform HitEffectObjects;

    public void Start()
    {
        audioSource = GetComponent<AudioSource>();
        //LoadAndPlay(0);
    }

    public void LoadAndPlay(int idx)
    {
        var levelData = levelList[idx];
        var bmsText = levelData.bms.text;
        var audioFile = levelData.audioFile;
        var offsetBarTime = levelData.offsetBarTime;

        var bms = new BmsParser().Parse(bmsText);
        var level = bms.ToLevel();

        this.level = level;

        var dspTime = AudioSettings.dspTime;
        var scheduleOffset = 0.5; // scheduling offset
        var playOffset = scheduleOffset + offsetBarTime * (60.0 / level.bpm) * 4; // includes offset bar time

        startDspTime = scheduleOffset + dspTime;

        audioSource.clip = audioFile;
        audioSource.PlayScheduled(playOffset + dspTime);
        Debug.Log(playOffset);
    }

    void OnEnable()
    {
    }

    private void Update()
    {
        //get laser
        var handObjects = player.GetComponent<Valve.VR.InteractionSystem.Player>().hands;

        var leftHandObject = handObjects[0];
        var rightHandObject = handObjects[1];

        TrackHand(leftHandObject, leftHandTracker);
        TrackHand(rightHandObject, rightHandTracker);

        if (level != null)
        {
            var playbackTime = AudioSettings.dspTime - startDspTime;
            PlayerInput input = new PlayerInput();
            input.time = playbackTime;

            level.UpdateNotes(playbackTime);

            if (Input.GetKeyDown(KeyCode.Alpha1))
            {
                input.ray = Camera.main.ScreenPointToRay(Input.mousePosition);
                input.hand = HandType.Left;
                level.HandleInput(input);
            }

            if (Input.GetKeyDown(KeyCode.Alpha2))
            {
                input.ray = Camera.main.ScreenPointToRay(Input.mousePosition);
                input.hand = HandType.Right;
                level.HandleInput(input);
            }

            try // when no VR device is available, SteamVR causes an error.
            {
                // TODO: handle vr input
                if (fire.GetStateDown(leftHand))
                {
                    input.hand = HandType.Left;

                    if (leftHandObject)
                    {
                        var gun = leftHandObject.GetComponentInChildren<GunBehaviour>();
                        input.ray = gun.GetRay();
                        gun.Fire();

                        level.HandleInput(input);
                    }
                }
                if (fire.GetStateDown(rightHand))
                {
                    input.hand = HandType.Right;
                    if (rightHandObject)
                    {
                        var gun = rightHandObject.GetComponentInChildren<GunBehaviour>();
                        input.ray = gun.GetRay();
                        gun.Fire();

                        level.HandleInput(input);
                    }
                }

                PlayerMotion motion = new PlayerMotion();
                motion.leftHand = leftHandTracker;
                motion.rightHand = rightHandTracker;
                motion.time = playbackTime;

                level.HandleMotion(motion);
            }
            catch (NullReferenceException e)
            {
                if (e.Source != "SteamVR") // ignore SteamVR, rethrow otherwise
                {
                    Debug.LogError(e.StackTrace);
                    throw e;
                }
            }
        }

        if (Input.GetKeyDown(KeyCode.LeftArrow))
        {
            audioSource.time -= 1.0f;
            startDspTime += 1.0f;
        }

        if (Input.GetKeyDown(KeyCode.RightArrow))
        {
            audioSource.time += 1.0f;
            startDspTime -= 1.0f;
        }

        // UI
        if (level == null)
        {
            try // when no VR device is available, SteamVR causes an error.
            {
                // TODO: handle vr input
                if (fire.GetStateDown(leftHand))
                {
                    if (leftHandObject)
                    {
                        var gun = leftHandObject.GetComponentInChildren<GunBehaviour>();
                        gun.Fire();

                        ClickButton(gun.GetRay());
                    }
                }
                if (fire.GetStateDown(rightHand))
                {
                    if (rightHandObject)
                    {
                        var gun = rightHandObject.GetComponentInChildren<GunBehaviour>();
                        gun.Fire();

                        ClickButton(gun.GetRay());
                    }
                }
            }
            catch (NullReferenceException e)
            {
                if (e.Source != "SteamVR") // ignore SteamVR, rethrow otherwise
                {
                    Debug.LogError(e.StackTrace);
                    throw e;
                }
            }
        }
    }

    private void ClickButton(Ray ray)
    {
        RaycastHit hit;
        if (Physics.Raycast(ray, out hit))
        {
            var button = hit.collider.gameObject.GetComponent<UnityEngine.UI.Button>();
            if (button)
            {
                button.onClick.Invoke();
            }
        }
    }

    private void TrackHand(Hand handObject, MotionTracker tracker)
    {
        if (handObject)
        {
            tracker.Activate();
            tracker.FeedPosition(handObject.transform.position);
        } else
        {
            tracker.Deactivate();
        }
    }

    // Simple implementations of Combo, Score UIs
    // It needs to be changed if PlayEngine don't have any responsibilities of score & combo
    public void HandleNoteJudge(JudgeType type)
    {
        if (type == JudgeType.Ignore)
            return;
        combo = type != JudgeType.Miss ? combo + 1 : 0;
        score += (int)type;

        IngameUIManager.inst.UpdateComboUI(combo);
        IngameUIManager.inst.UpdateScoreUI(score);

        foreach (var renderer in HitEffectObjects.GetComponentsInChildren<MeshRenderer>())
        {
            Color col = Color.white;
            switch (type)
            {
                case JudgeType.Miss:
                    col = Color.red;
                    break;
                case JudgeType.Hit:
                    col = Color.green;
                    break;
                case JudgeType.Perfect:
                    col = Color.blue;
                    break;
            }
            StartCoroutine(HitEffectRoutine(renderer, col));
        }
    }
    //
    private IEnumerator HitEffectRoutine(MeshRenderer renderer, Color color)
    {
        for (float t = 0; t< 0.25f; t += Time.deltaTime)
        {
            renderer.material.color = Color.Lerp(color, new Color(color.r, color.g, color.b, 0.025f), t * 4);
            //renderer.material.SetVector("_EmissionColor", Mathf.Lerp(0, -10, t * 4) * color);
            yield return null;
        }
    }

}

public class PlayerInput
{
    public Ray ray;
    public HandType hand;
    public double time;
}

public class PlayerMotion
{
    public MotionTracker leftHand;
    public MotionTracker rightHand;
    public double time;
}