﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Yarn.Unity;
using UnityEngine.SceneManagement;

public class YarnCommands : MonoBehaviour
{
    static YarnCommands instance;

    public AnimationCurve ease_in_out;

    private void Start()
    {
        if (instance != null && instance != this)
        {
            Destroy(gameObject);
            return;
        }
        else
        {
            instance = this;
            DontDestroyOnLoad(gameObject);
        }

        if (GameObject.FindObjectOfType<DialogueRunner>() == null)
            Debug.LogError("Error : You might be missing a 'Dialogue' gameobject.");
    }

    [YarnCommand("DebugLog")]
    public void DebugLog(string debug_msg)
    {
        Debug.Log(debug_msg);
    }

    [YarnCommand("quit")]
    public void quit()
    {
        Application.Quit();
    }

    [YarnCommand("camera_warp_target")]
    public void camera_move(string gameobject_name)
    {
        GameObject target = GameObject.Find(gameobject_name);
        if (target == null)
        {
            Debug.LogError("Yarn command \"camera_move\" did not provide the name of a gameobject.");
            return;
        }

        Camera.main.transform.position = target.transform.position;
        Camera.main.transform.rotation = target.transform.rotation;
    }

    [YarnCommand("warp_gameobject_position_rotation")]
    public void warp_gameobject_position_rotation(string gameobject_path, string x_str, string y_str, string z_str, string xr_str, string yr_str, string zr_str)
    {
        if (gameobject_path == null || gameobject_path == "")
        {
            Debug.LogError("[YarnCommands.warp_gameobject_position_rotation] Failed to provide a gameobject path.");
            return;
        }

        GameObject target = GetGameObjectAtHierarchyPath(gameobject_path);
        if (target == null)
        {
            Debug.LogError("[YarnCommands.warp_gameobject_position_rotation] Failed to find gameobject with path [" + gameobject_path + "]");
            return;
        }

        float x = target.transform.localPosition.x;
        float y = target.transform.localPosition.y;
        float z = target.transform.localPosition.z;

        float xr = target.transform.localRotation.eulerAngles.x;
        float yr = target.transform.localRotation.eulerAngles.y;
        float zr = target.transform.localRotation.eulerAngles.z;

        float.TryParse(x_str, out x);
        float.TryParse(y_str, out y);
        float.TryParse(z_str, out z);

        float.TryParse(xr_str, out xr);
        float.TryParse(yr_str, out yr);
        float.TryParse(zr_str, out zr);

        target.transform.localPosition = new Vector3(x, y, z);
        Quaternion q = new Quaternion();
        q.eulerAngles = new Vector3(xr, yr, zr);
        target.transform.localRotation = q;
    }

    /* One string param expected (name of position target gameobject) */
    [YarnCommand("camera_ease_target")]
    public void camera_ease_target(string gameobject_name)
    {
        float duration_sec = 1.0f;
        GameObject target = GameObject.Find(gameobject_name);

        if (target == null)
        {
            Debug.LogError("Yarn Command camera_ease_target was provided a target gameobject name of [" + gameobject_name + "] but no gameobject with this name exists at the root of the scene hierarchy.");
            return;
        }

        //foreach (string parameter in parameters)
        //    Debug.Log("[ PARAMETER" + parameter + "]");

        StartCoroutine(_ChangePositionOverTime(Camera.main.transform, target.transform, duration_sec, ease_in_out, null));
    }

    [YarnCommand("camera_warp_position")]
    public void camera_warp(string x_str, string y_str, string z_str, string xr_str, string yr_str, string zr_str)
    {
        float x = Camera.main.transform.position.x;
        float y = Camera.main.transform.position.y;
        float z = Camera.main.transform.position.z;

        float xr = Camera.main.transform.rotation.eulerAngles.x;
        float yr = Camera.main.transform.rotation.eulerAngles.y;
        float zr = Camera.main.transform.rotation.eulerAngles.z;

        float.TryParse(x_str, out x);
        float.TryParse(y_str, out y);
        float.TryParse(z_str, out z);

        float.TryParse(xr_str, out x);
        float.TryParse(yr_str, out y);
        float.TryParse(zr_str, out z);

        Camera.main.transform.position = new Vector3(x, y, z);
        Quaternion q = new Quaternion();
        q.eulerAngles = new Vector3(xr, yr, zr);
        Camera.main.transform.rotation = q;
    }

    [YarnCommand("restart_current_scene")]
    public void restart_current_scene()
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    }

    [YarnCommand("change_scene")]
    public void change_scene(string scene_name)
    {
        SceneManager.LoadScene(scene_name);
    }

    [YarnCommand("activate_gameobject")]
    public void activate_gameobject(string gameobject_path)
    {
        Debug.Log("ACTIVATE [" + gameobject_path + "]");
        GameObject target_gameobject = GetGameObjectAtHierarchyPath(gameobject_path);

        if (target_gameobject == null)
        {
            Debug.LogError("[YarnCommands.activate_gameobject] Failed to locate gameobject in scene at [" + gameobject_path + "]");
            return;
        }

        target_gameobject.SetActive(true);
    }

    [YarnCommand("deactivate_gameobject")]
    public void deactivate_gameobject(string gameobject_path)
    {
        GameObject target_gameobject = GetGameObjectAtHierarchyPath(gameobject_path);

        if (target_gameobject == null)
        {
            Debug.LogError("[YarnCommands.deactivate_gameobject] Failed to locate gameobject in scene at [" + gameobject_path + "]");
            return;
        }

        target_gameobject.SetActive(false);
    }

    GameObject GetGameObjectAtHierarchyPath(string gameobject_hierarchy_path)
    {
        string[] path_elements = gameobject_hierarchy_path.Split('/');
        if (path_elements.Length <= 0)
        {
            Debug.LogError("Failing (split) hierarchy path search for [" + gameobject_hierarchy_path + "]");
            return null;
        }

        GameObject g = FindGameObjectFromAll(path_elements[0]);
        if (g == null)
        {
            Debug.LogError("Failing (first find) hierarchy path search for [" + gameobject_hierarchy_path + "]");
            return null;
        }

        Transform current_t = g.transform;
        int path_index = 1;

        while (path_index < path_elements.Length)
        {
            Transform new_t = current_t.Find(path_elements[path_index]);
            if (new_t == null)
                break;
            current_t = new_t;
            path_index++;
        }

        return current_t.gameObject;
    }

    static GameObject FindGameObjectFromAll(string name)
    {
        GameObject[] all_scene_game_objects = GameObject.FindObjectsOfType<GameObject>(true);
        foreach (GameObject g in all_scene_game_objects)
        {
            if (g.name == name)
                return g;
        }

        return null;
    }

    [YarnCommand("play_audio_clip")]
    public void play_audio_clip(string audio_clip_path)
    {
        Debug.Log("PLAY AUDIO CLIP " + audio_clip_path);
        AudioClip clip = Resources.Load<AudioClip>(audio_clip_path);
        if (clip == null)
        {
            Debug.LogError("Yarn command \"play_audio_clip\" could not find audio clip in a resources folder with path [" + audio_clip_path + "]");
            return;
        }

        AudioSource.PlayClipAtPoint(clip, Camera.main.transform.position);
    }

    static GameObject GetAudioSystem()
    {
        GameObject audio_system = GameObject.Find("_AudioSystem");
        if (audio_system == null)
        {
            audio_system = new GameObject();
            audio_system.name = "_AudioSystem";
            GameObject.DontDestroyOnLoad(audio_system);
        }

        return audio_system;
    }

    static Dictionary<string, AudioSource> audio_tracks = new Dictionary<string, AudioSource>();

    [YarnCommand("play_audio_track")]
    public void play_audio_track(string audio_clip_path, string desired_volume_str)
    {
        string track_name = audio_clip_path;
        float desired_volume = float.Parse(desired_volume_str);

        /* Get audio track */
        GameObject audio_system = GetAudioSystem();
        AudioSource[] audio_sources = audio_system.GetComponents<AudioSource>();
        AudioSource audio_track = null;
        if (audio_tracks.ContainsKey(track_name))
        {
            audio_track = audio_tracks[track_name];
        }
        else
        {
            audio_track = audio_system.AddComponent<AudioSource>();
            audio_tracks[track_name] = audio_track;
            audio_track.volume = 0.0f;
            audio_track.bypassEffects = true;
            audio_track.loop = true;
        }

        /* Get clip */
        AudioClip clip = Resources.Load<AudioClip>(audio_clip_path);
        if (clip == null)
        {
            Debug.LogError("Yarn command \"play_audio_track\" could not find audio clip in a resources folder with path [" + audio_clip_path + "]");
            return;
        }

        /* Play clip */
        if (clip != audio_track.clip)
            audio_track.clip = clip;
        if (!audio_track.isPlaying)
            audio_track.Play();

        /* Fade volume */
        StartCoroutine(_ChangeAudioSourceVolumeOverTime(audio_track, desired_volume, 1.0f));
    }

    public static void PlayAudioTrack(string audio_clip_path, float desired_volume)
    {
        GameObject command_go = GameObject.Find("command");
        if (command_go == null)
        {
            Debug.LogError("[YarnCommands.PlayAudioTrack] The current scene doesn't have a command gameobject. Please find one from another scene and copy it in to this one.");
            return;
        }

        YarnCommands command = command_go.GetComponent<YarnCommands>();
        if (command == null)
        {
            Debug.LogError("[YarnCommands.PlayAudioTrack] The current scene's command gameobject does not have a YarnCommands component. Please add it.");
            return;
        }

        command.play_audio_track(audio_clip_path, desired_volume.ToString());
    }

    void StartAudioLogic()
    {
        YarnCommands.PlayAudioTrack("gameshow_layer_1", 0.0f);
    }

    /* One string param expected, representing desired density */
    [YarnCommand("set_fog_density")]
    public void set_fog_density(string[] parameters, System.Action onComplete)
    {
        if (parameters.Length <= 1 || parameters[1] == null || parameters[1].Trim() == "")
        {
            Debug.LogError("Yarn Command set_fog_density was not provided with a density parameter.");
            onComplete();
            return;
        }

        //foreach (string parameter in parameters)
        //    Debug.Log("[ PARAMETER" + parameter + "]");

        StartCoroutine(_ChangeFogDensityOverTime(float.Parse(parameters[1]), onComplete));
    }

    IEnumerator _ChangeFogDensityOverTime(float desired_fog_density, System.Action finished_callback)
    {
        float duration_sec = 1.0f;
        float initial_time = Time.time;
        float progress = (Time.time - initial_time) / duration_sec;
        RenderSettings.fog = true;
        float initial_fog_density = RenderSettings.fogDensity;

        while (progress < 1.0f)
        {
            progress = (Time.time - initial_time) / duration_sec;
            RenderSettings.fogDensity = Mathf.Lerp(initial_fog_density, desired_fog_density, progress);
            yield return null;
        }

        RenderSettings.fogDensity = desired_fog_density;
        finished_callback();
    }

    IEnumerator _ChangePositionOverTime(Transform target_obj, Transform dest_obj, float duration_sec, AnimationCurve ease, System.Action finished_callback)
    {
        float initial_time = Time.time;
        float progress = Time.time - initial_time / duration_sec;
        Vector3 initial_position = target_obj.position;
        Quaternion initial_rotation = target_obj.rotation;

        while (progress < 1.0f)
        {
            progress = Time.time - initial_time / duration_sec;

            float effective_progress = progress;
            if (ease != null)
                effective_progress = ease.Evaluate(effective_progress);

            target_obj.position = Vector3.LerpUnclamped(initial_position, dest_obj.position, effective_progress);
            target_obj.rotation = Quaternion.SlerpUnclamped(initial_rotation, dest_obj.rotation, effective_progress);
            yield return null;
        }

        target_obj.position = Vector3.LerpUnclamped(initial_position, dest_obj.position, 1.0f);
        target_obj.rotation = Quaternion.SlerpUnclamped(initial_rotation, dest_obj.rotation, 1.0f);

        if (finished_callback != null)
            finished_callback();
    }

    IEnumerator _ChangeAudioSourceVolumeOverTime(AudioSource target, float desired_volume, float duration_sec)
    {
        float initial_time = Time.time;
        float progress = (Time.time - initial_time) / duration_sec;
        float initial_volume = target.volume;

        while (progress < 1.0f)
        {
            progress = (Time.time - initial_time) / duration_sec;
            target.volume = Mathf.Lerp(initial_volume, desired_volume, progress);
            yield return null;
        }

        target.volume = desired_volume;
    }
}
