//============= Copyright (c) Ludic GmbH, All rights reserved. ==============
//
// Purpose: Part of the My Behaviour Tree Controller Code
//
//=============================================================================

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Video;
using MyBT;
using System.Linq;

#if FMOD_AVAILABLE
using FMODUnity;
#endif

#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
[CustomEditor(typeof(BTC))]
public class BehaviourTreeControllerInspector : Editor {
    public override void OnInspectorGUI() {
        BTC myTarget = (BTC)target;
        if (GUILayout.Button("ClearObjects")) {
            myTarget.ClearObjects();
            SetDirty(myTarget);
        }
        if (GUILayout.Button("UpdateObjects")) {
            myTarget.UpdateObjects();
            SetDirty(myTarget);
        }
        DrawDefaultInspector();
    }



    public void SetDirty (BTC myTarget) {
        EditorUtility.SetDirty(myTarget);
        EditorSceneManager.MarkSceneDirty(myTarget.gameObject.scene);
    }
}
#endif


public class BTC : MonoBehaviour {
    static BTC _instance = null;
    public static BTC Instance {
        get {
            if (_instance != null) {
                return _instance;
            }
            if (_instance == null) {
                _instance = Resources.FindObjectsOfTypeAll<BTC>().FirstOrDefault();
                if (_instance == null) {
                    _instance = new GameObject("BTC", typeof(BTC)).GetComponent<BTC>();
                }
            }
            return _instance;
        }
    }

    public List<ComponentController> namedObjects = new List<ComponentController>();

    #region update list of objects in rooms
    public void ClearObjects() {
        namedObjects = new List<ComponentController>();
    }

    public T GetNamedObject<T>(string objectName) where T : ComponentController  {
        for (int i = namedObjects.Count - 1; i >= 0; i--) {
            T ngo = namedObjects[i] as T;
            if (ngo != null) {
                // if name equals
                if (ngo.objectName == objectName) {
                    return ngo;
                }
            }
        }
        return null;
    }

    public List<ComponentHandler> GetHandlers (string objectName) {
        ComponentController ctrl = GetNamedObject<ComponentController>(objectName);
        if (ctrl != null)
            return ctrl.handlers.ToList();
        return new List<ComponentHandler>();
    }

    public bool autoUpdateAllObjects = true;

    public void UpdateObjects() {
        // iterate all objects in scene
        foreach (ComponentController namedObject in Resources.FindObjectsOfTypeAll<ComponentController>()) {
            if (autoUpdateAllObjects) {
                namedObject.UpdateObject();
            }
            // get object room id, create if not existing
            // RoomData roomData = GetRoomData(namedObject.roomId);
            // if (roomData != null) {
                // if room contains object (name equals), overwrite
                bool objectIdFound = false;
                bool objectNameFound = false;
                for (int i=0; i<namedObjects.Count; i++) {
                    if (namedObjects[i].GetInstanceID() == namedObject.GetInstanceID()) {
                        namedObjects[i] = namedObject;
                        objectIdFound = true;
                        // if (objectIdFound) {
                        //     Debug.Log($"object {namedObject.name} already in list as {namedObject.GetInstanceID()}");
                        // }
                    }
                    // else {
                    //     Debug.Log("UpdateObjects: match check failed "+roomData.namedObjects[i].objectName+" != "+namedObject.objectName );
                    // }
                    if (namedObjects[i].objectName == namedObject.objectName) {
                        objectNameFound = true;
                    }
                }
                // if object not found, append to list
                if (objectNameFound && !objectIdFound) {
                    Debug.LogWarning($"Object with name {namedObject.name} already exists, adding a unque id");
                    namedObject.uniqueId = namedObject.GetInstanceID().ToString();
                    namedObject.UpdateObject();
                }
                if (!objectIdFound && !objectNameFound) {
                    namedObjects.Add(namedObject);
                } 
                if (!objectNameFound && objectIdFound) {
                    Debug.LogWarning($"Found obsolete object {namedObject.name}, refresh data");
                }
            // }
            // else {
            //     Debug.LogError("should not happen: roomData undefined");
            // }
        }
    }
    #endregion

    #region general play, pause, unpause, stop
    [Task]
    public void Run(string objectName) {
        List<ComponentHandler> handlers = GetHandlers(objectName);
        handlers.ForEach(handler => handler.Run(Task.getState));
        if (handlers.Count == 0) {
            Debug.LogWarning($"BTC.Run: no components under the name '{objectName}'");
            Task.SetSucceeded();
        }
    }

    [Task]
    public void Abort(string objectName) {
        List<ComponentHandler> handlers = GetHandlers(objectName);
        handlers.ForEach(handler => handler.Abort(Task.getState));
        if (handlers.Count == 0) {
            Debug.LogWarning($"BTC.Abort: no components under the name '{objectName}'");
            Task.SetSucceeded();
        }
    }

    [Task]
    public void IsRunning(string objectName) {
        List<ComponentHandler> handlers = GetHandlers(objectName);
        handlers.All(handler => handler.IsRunning(Task.getState));
        if (handlers.Count == 0) {
            Debug.LogWarning($"BTC.IsRunning: no components under the name '{objectName}'");
            Task.SetSucceeded();
        }
    }

    [Task]
    public void Show(string objectName) {
        List<ComponentHandler> handlers = GetHandlers(objectName);
        handlers.ForEach(handler => handler.Show(Task.getState));
        if (handlers.Count == 0) {
            Debug.LogWarning($"BTC.Show: no components under the name '{objectName}'");
            Task.SetSucceeded();
        }
    }

    [Task]
    public void Hide(string objectName) {
        List<ComponentHandler> handlers = GetHandlers(objectName);
        handlers.ForEach(handler => handler.Hide(Task.getState));
        if (handlers.Count == 0) {
            Debug.LogWarning($"BTC.Hide: no components under the name '{objectName}'");
            Task.SetSucceeded();
        }
    }

    [Task]
    public void FadeIn(string objectName) {
        List<ComponentHandler> handlers = GetHandlers(objectName);
        handlers.ForEach(handler => handler.FadeIn(Task.getState));
        if (handlers.Count == 0) {
            Debug.LogWarning($"BTC.FadeIn: no components under the name '{objectName}'");
            Task.SetSucceeded();
        }
    }

    [Task]
    public void FadeOut(string objectName) {
        List<ComponentHandler> handlers = GetHandlers(objectName);
        handlers.ForEach(handler => handler.FadeOut(Task.getState));
        if (handlers.Count == 0) {
            Debug.LogWarning($"BTC.FadeOut: no components under the name '{objectName}'");
            Task.SetSucceeded();
        }
    }

    [Task]
    public void Set(string objectName, string key, string value) {
        List<ComponentHandler> handlers = GetHandlers(objectName);
        handlers.ForEach(handler => handler.Set(Task.getState, key, value));
        if (handlers.Count == 0) {
            Debug.LogWarning($"BTC.Set: no components under the name '{objectName}'");
            Task.SetSucceeded();
        }
    }

    [Task]
    public void SetFloat(string objectName, string key, float value)
    {
        List<ComponentHandler> handlers = GetHandlers(objectName);
        handlers.ForEach(handler => handler.SetFloat(Task.getState, key, value));
        if (handlers.Count == 0)
        {
            Debug.LogWarning($"BTC.SetFloat: no components under the name '{objectName}'");
            Task.SetSucceeded();
        }
    }

    [Task]
    public void Enable(string objectName)
    {
        List<ComponentHandler> handlers = GetHandlers(objectName);
        handlers.ForEach(handler => handler.Enable(Task.getState));
        if (handlers.Count == 0)
        {
            Debug.LogWarning($"BTC.Enable: no components under the name '{objectName}'");
            Task.SetSucceeded();
        }
    }

    [Task]
    public void Disable(string objectName)
    {
        List<ComponentHandler> handlers = GetHandlers(objectName);
        handlers.ForEach(handler => handler.Disable(Task.getState));
        if (handlers.Count == 0)
        {
            Debug.LogWarning($"BTC.Disable: no components under the name '{objectName}'");
            Task.SetSucceeded();
        }
    }

    [Task]
    public void SetPosition(string objectName, float newX, float newY, float newZ)
    {
        List<ComponentHandler> handlers = GetHandlers(objectName);
        handlers.ForEach(handler => handler.SetPosition(Task.getState, newX, newY, newZ));
        if (handlers.Count == 0)
        {
            Debug.LogWarning($"BTC.SetPosition: no components under the name '{objectName}'");
            Task.SetSucceeded();
        }
    }

    [Task]
    public void AbortEventListener(string objectName)
    {
        List<ComponentHandler> handlers = GetHandlers(objectName);
        handlers.ForEach(handler => handler.AbortEventListener(Task.getState));
        if (handlers.Count == 0)
        {
            Debug.LogWarning($"BTC.AbortEventListener: no components under the name '{objectName}'");
            Task.SetSucceeded();
        }
    }

    [Task]
    public void ListenToEvent(string objectName)
    {
        List<ComponentHandler> handlers = GetHandlers(objectName);
        handlers.ForEach(handler => handler.ListenToEvent(Task.getState));
        if (handlers.Count == 0)
        {
            Debug.LogWarning($"BTC.ListenToEvent: no components under the name '{objectName}'");
            Task.SetSucceeded();
        }
    }

    [Task]
    public void StopSound(string objectName)
    {
        List<ComponentHandler> handlers = GetHandlers(objectName);
        handlers.ForEach(handler => handler.StopSound(Task.getState));
        if (handlers.Count == 0)
        {
            Debug.LogWarning($"BTC.StopSound: no components under the name '{objectName}'");
            Task.SetSucceeded();
        }
    }
    #endregion

    #region video & sound tasks
#if FMOD
    [Task]
    public void PlayVideoAndSound(string videoName, string soundName) {
        NamedStudioEventEmitter sound = GetNamedObject<NamedStudioEventEmitter>(soundName);
        NamedVideoPlayer player = GetNamedObject<NamedVideoPlayer>(videoName);
        if ((player != null) && (sound != null)) {
            int val = -1;
            FMOD.RESULT res = sound.sound.EventInstance.getTimelinePosition(out val);
            Task.log = "audio " + res.ToString() + " " + val + " video playing=" + player.video.isPlaying + " isPrepared=" + player.video.isPrepared;
            // on start, play
            if (Task.isStarting) {
                Debug.Log("PlayVideoAndSound.isStarting : starting video='" + videoName + "' and sound='" + soundName + "'");
                player.HideNow();
                player.FadeInStart();
                player.video.Play();
                sound.sound.Play();
            }
            else {
                // if stopped playing

                if (!player.video.isPrepared) {
                    // not even prepared
                    return;
                }
                if ((!player.video.isPlaying) && (!sound.sound.IsPlaying())) {
                    player.ShowNow();
                    player.FadeOutStart();
                    Task.SetSucceeded();
                }
            }

            if (Input.GetKeyDown(KeyCode.Backspace)) {
                player.HideNow();
                player.video.Stop();
                sound.sound.Stop();
                Task.SetSucceeded();
            }
        }
        else {
            Debug.LogError("PlayVideoAndSound: Play Error: " + roomId + " video='" + videoName + "' (" + player + ") sound='" + soundName + "' (" + sound + ")");
        }
    }

    [Task]
    public void AbortVideoAndSound(string videoName, string soundName) {
        if (Task.isStarting) {
            NamedVideoPlayer player = GetNamedObject<NamedVideoPlayer>(videoName);
            if (player != null) {
                player.FadeOutStart();
                player.video.Stop();
            }
            NamedStudioEventEmitter sound = GetNamedObject<NamedStudioEventEmitter>(soundName);
            if (sound != null) {
                sound.sound.Stop();
            }
        }
        Task.SetSucceeded();
    }
#endif
    #endregion

    #region gameobject tasks
    // This structure is used to store data for rotation tweening.
    struct QuaternionData {
        public Quaternion startQuaternion;
    }

    [Task]
    public void TrackerRotatedAtLeast(string gameObjectName, float successRotation) {
        foreach (NamedGameObject ngo in GetHandlers(gameObjectName)) {
            // NamedGameObject ngo = GetNamedObject<NamedGameObject>(gameObjectName);
            if (ngo != null) {

                QuaternionData rd;
                if (Task.isStarting) {
                    Debug.Log("TrackerRotatedAtLeast.isStarting : gameObject=" + ngo.name);
                    rd.startQuaternion = ngo.transform.rotation;
                    Task.data = rd;
                }
                rd = (QuaternionData)Task.data;

                float angleOffset = Quaternion.Angle(rd.startQuaternion, ngo.transform.rotation);
                Task.log = string.Format(angleOffset + " " + rd.startQuaternion + " " + ngo.transform.rotation);
                if (angleOffset > successRotation) {
                    Task.SetSucceeded();
                }
            }
        }
    }

    [Task]
    public void TrackerRotatedAtMax(string gameObjectName, float successRotation) {
        foreach (NamedGameObject ngo in GetHandlers(gameObjectName)) {
            // NamedGameObject ngo = GetNamedObject<NamedGameObject>(gameObjectName);
            if (ngo != null) {

                QuaternionData rd;
                if (Task.isStarting) {
                    Debug.Log("TrackerRotatedAtMax.isStarting : trackerId=" + ngo.name + " status " + Task.getState + " ");
                    rd.startQuaternion = ngo.transform.rotation;
                    Task.data = rd;
                }
                rd = (QuaternionData)Task.data;

                float angleOffset = Quaternion.Angle(rd.startQuaternion, ngo.transform.rotation);
                Task.log = string.Format(angleOffset + " " + rd.startQuaternion + " " + ngo.transform.rotation);
                if (angleOffset < successRotation) {
                    Task.SetSucceeded();
                }
            }
        }
    }

    [Task]
    public void TrackerRotatedToAngle(string gameObjectName, float targetAngleFloat, float maxOffset) {
        foreach (NamedGameObject ngo in GetHandlers(gameObjectName)) {
            // NamedGameObject ngo = GetNamedObject<NamedGameObject>(gameObjectName);
            if (ngo != null) {
                Quaternion targetAngle = Quaternion.Euler(0, targetAngleFloat, 0);
                float angleOffset = Quaternion.Angle(targetAngle, ngo.transform.rotation);
                Task.log = string.Format(angleOffset + " < " + maxOffset + " ? " + ngo.transform.rotation.eulerAngles + " " + targetAngle.eulerAngles);
                if (angleOffset < maxOffset) {
                    Task.SetSucceeded();
                }
            }
        }
    }

    struct FloatData {
        public float startFloat;
    }

    [Task]
    public void TrackerMovedY(string gameObjectName, float successMovement) {
        foreach (NamedGameObject ngo in GetHandlers(gameObjectName)) {
            // NamedGameObject ngo = GetNamedObject<NamedGameObject>(gameObjectName);
            if (ngo != null) {
                //if ((trackerId >= 0) && (trackerId < GetRoomData(roomId).trackers.Count)) {
                //TrackerDataApplier tda = GetRoomData(roomId).trackers[trackerId];

                FloatData pd;
                if (Task.isStarting) {
                    Debug.Log("TrackerRotated.isStarting : gameObjectName=" + ngo.go.name);
                    pd.startFloat = ngo.go.transform.position.y;
                    Task.data = pd;
                }
                pd = (FloatData)Task.data;

                float positionOffset = Mathf.Abs(pd.startFloat - ngo.go.transform.position.y);
                Task.log = string.Format(positionOffset + " " + pd.startFloat + " " + ngo.go.transform.position.y);
                if (positionOffset > successMovement) {
                    Task.SetSucceeded();
                }
            }
        }
    }
    
    struct DataFloat2 {
        public float startTime;
        public float startY;
    }
    [Task]
    public void MoveObjectY(string gameObjectName, float startY, float finalY, float timer) {
        foreach (NamedGameObject ngo in GetHandlers(gameObjectName)) {
            // NamedGameObject ngo = GetNamedObject<NamedGameObject>(gameObjectName);
            if (ngo != null) {
                NodeState thisNodeState = Task.getState;
                switch (thisNodeState) {
                    case NodeState.FirstRun:
                        Task.data = new DataFloat2() { startTime = Time.time, startY=ngo.transform.localPosition.y };
                        goto case NodeState.Running;
                    case NodeState.Running:
                        DataFloat2 data = (DataFloat2)Task.data;
                        float elapsed = Time.time - data.startTime;
                        float newY = finalY;
                        if (timer > 0) {
                        //    newY = finalY;
                        //} else {
                            float interpolation = Mathf.Pow(elapsed / timer, 1f / 7f);
                            newY = Mathf.Lerp(startY, finalY, interpolation);
                        }
                        ngo.transform.localPosition = new Vector3(ngo.transform.localPosition.x, newY, ngo.transform.localPosition.z);
                        if (elapsed >= timer) {
                            Task.SetSucceeded();
                            return;
                        }
                        break;
                    case NodeState.Aborting:
                        ngo.transform.localPosition = new Vector3(ngo.transform.localPosition.x, finalY, ngo.transform.localPosition.z);
                        break;
                }
            }
            else {
                Debug.LogError($"MoveObjectY {gameObjectName} not found");
                Task.SetFailed();
            }
        }
    }

    [Task]
    public void SendGameObjectMessage(string gameObjectName, string methodName) {
        foreach (NamedGameObject ngo in GetHandlers(gameObjectName)) {
            // NamedGameObject ngo = GetNamedObject<NamedGameObject>(gameObjectName);
            if (ngo != null) {
                NodeState thisNodeState = Task.getState;
                switch (thisNodeState) {
                    case NodeState.FirstRun:
                        ngo.SendMessage(methodName);
                        goto case NodeState.Running;
                    case NodeState.Running:
                        Task.SetSucceeded();
                        break;
                    case NodeState.Aborting:
                        break;
                }
            }
            else {
                Debug.LogError($"SendGameObjectMessage {gameObjectName} {methodName}");
                Task.SetFailed();
            }
        }
    }
    #endregion

    #region camera or player tracking
    [Task]
    public bool CameraNearPosition(float x, float y, float z, float range) {
        Vector3 position = new Vector3(x, y, z);
        float positionOffset = (Camera.main.transform.position - position).magnitude;
        Task.log = $"{positionOffset} {Camera.main.transform.position} {position}"; // string.Format(positionOffset + " " + Camera.main.transform.position + " " + position);
        if (positionOffset < range) {
            Task.SetSucceeded();
            return true;
        }
        return false;
    }

    [Task]
    public void CameraXZNearPosition(float x, float z, float range) {
        Vector3 cameraPos = Camera.main.transform.position;
        Vector3 position = new Vector3(x, cameraPos.y, z);
        float positionOffset = (cameraPos - position).magnitude;
        Task.log = $"{positionOffset} {cameraPos} {position}"; // string.Format(positionOffset + " " + cameraPos + " " + position);
        if (positionOffset < range) {
            Task.SetSucceeded();
        }
    }

    [Task]
    public void SetCameraRootPosition(float x, float y, float z) {
        if (Task.isStarting) {
            Camera.main.transform.root.position = new Vector3(x, y, z);
        }
        Task.SetSucceeded();
    }
    #endregion

    #region MegapointCache
#if MEGAFIERS
    [Task]
    public void PlayMegaPointCache(string string gameObjectName, float startTime, bool playing, float playRate) {
        NamedMegaPointCache nmpc = GetNamedObject<NamedMegaPointCache>(gameObjectName);
        if (nmpc != null) {
            if (Task.isStarting) {
                nmpc.SetFrame(startTime);
                nmpc.SetPlay(playing);
                nmpc.SetPlayRate(playRate);
                Task.SetSucceeded();
            }
        }
    }

    /// <summary>
    /// attack: animation time to ease in the animation
    /// delay: animation time at full speed
    /// release: animation time to ease out the animation
    /// 
    /// attack * 2 + delay + release * 2 = animation play rate
    /// </summary>
    public float gletscherFrame = 0;
    [Task]
    public void PlayMegaPointCacheTimeline(string objectName, float start, float attack, float delay, float release, float duration) {
        NamedMegaPointCache nmpc = GetNamedObject<NamedMegaPointCache>(objectName);
        if (nmpc != null) {
            float overallTime = Mathf.Abs(attack * 2 + delay + release * 2); // for example: 0.2 * 2 + 0.6 + 0.2 * 2 = 1.4, after 10 seconds of real time, the internal counter needs to have run 1.4 seconds
            float timeScale = overallTime / duration;  // for example: 1.4 / 10 = 0.14
            float internalCounter = 0;  // between 0 and overallTime
            float animationTime = start; // final time written to animation
            string state = "undefined";
            // only for debugging
            gletscherFrame = nmpc.GetFrame();
            switch (Task.getState) {
                case NodeState.FirstRun: // equals isStarting
                    Task.data = Time.time; // store starttime
                    nmpc.SetPlay(false);
                    nmpc.SetPlayRate(0);
                    Debug.Log("BTC.PlayMegaPointCacheTimeline: start "+ start + " attack " + attack + " delay " + delay + " release " + release + " duration " + duration + " timeScale " + timeScale + " frame " + Time.frameCount);
                    break;
                case NodeState.Running:
                    float startTime = (float)Task.data; // for example 33
                    internalCounter = (Time.time - startTime) * timeScale; // for example: (33-35) * 0.14 = 0.28

                    if (internalCounter >= overallTime) {
                        //Debug.Log("BTC.PlayMegaPointCacheTimeline.running: finished " + nmpc.GetFrame() + " " + internalCounter);
                        state = "finished";
                        animationTime += overallTime;
                        Task.SetSucceeded();
                        //return;
                    }
                    // in release
                    else if (internalCounter >= Mathf.Abs(attack * 2 + delay)) { // for example: 0.28 > (0.2*2+0.6) = false
                        internalCounter -= Mathf.Abs(attack * 2 + delay);
                        float t = internalCounter / (2 * release);
						animationTime += attack + delay + release * IntegralEasing.EaseOutCubic(t);
                        //Debug.Log("BTC.PlayMegaPointCacheTimeline: release " + animationTime + " i " + internalCounter + " t " + t);
                        state = "release";
                    }
                    // in delay
                    else if (internalCounter >= Mathf.Abs(attack * 2)) { // for example: 0.28 > (0.2*2) = false
                        internalCounter -= Mathf.Abs(attack * 2);
                        float t = internalCounter / delay;
                        animationTime += attack + delay * t;
                        //Debug.Log("BTC.PlayMegaPointCacheTimeline: delay " + animationTime + " i " + internalCounter + " t " + t);
                        state = "delay";
                    }
                    // in attack
                    else { // for example: true
                        internalCounter = internalCounter; // for example: 0.28 = 0.28
                        float t = internalCounter / (2 * attack);
						animationTime += attack * IntegralEasing.EaseInCubic(t);
                        //Debug.Log("BTC.PlayMegaPointCacheTimeline: attack " + animationTime + " i " + internalCounter + " t " + t);
                        state = "attack";
                    }
                    break;
                case NodeState.Aborting:
                    //Debug.Log("BTC.PlayMegaPointCacheTimeline.stopping: " + nmpc.GetFrame() + " " + internalCounter);
                    internalCounter = overallTime;
                    //Debug.Log("BTC.PlayMegaPointCacheTimeline: stop ");
                    state = "stop";
                    animationTime += attack + delay + release;
                    // prevent applying the time
                    return;
                    break;
            }
            //Task.log = "t " + animationTime + " i  " + internalCounter + " s " + timeScale + " o " + overallTime;
            float delta = (animationTime - nmpc.GetFrame()) / timeScale;
            //Debug.Log("BTC.PlayMegaPointCacheTimeline: " + state + "  " + animationTime + " delta " + delta + " i " + internalCounter);

            nmpc.SetFrame(animationTime);
        }
    }
#endif
    #endregion

    #region Speech Intent Recognizer and Speech Synthesizer
    private ViaggioAIManager _speechMng { get { return ViaggioAIManager.Instance; } }
    private RequestDataModel _requestDataModel = new RequestDataModel();
    private string _recognizedIntentID = "";
    private string _speechErrorText = "";
    private bool _onIntentRecognitionSucceededEventTriggered = false;
    private bool _onUserSpeechInputStartedEventTriggered = false;
    private bool _onIntentRecognitionFailedEventTriggered = false;
    private bool _onSpeechOutputStartedEventTriggered = false;
    private bool _onSpeechOutputEndedEventTriggered = false;
    private bool _abortSpeechEventListener = false;
    private bool _onErrorEventTriggered = false;

    void OnDisable()
    {
        UnsubscribeFromEvents();
    }

    private void SubscribeToEvents()
    {
        if (_speechMng != null)
        {
            // Speech Intent Recognizer Events
            _speechMng.OnUserSpeechInputStartedEvent += UserSpeechInputStartedEventHandler;
            _speechMng.OnIntentRecognitionSucceededEvent += IntentRecognitionSucceededEventHandler;
            _speechMng.OnIntentRecognitionFailedEvent += IntentRecognitionFailedEventHandler;

            // Speech Output Events
            _speechMng.OnSpeechOutputStartedEvent += SpeechOutputStartedEventHandler;
            _speechMng.OnSpeechOutputEndedEvent += SpeechOutputEndedEventHandler;

            // Error Event
            _speechMng.OnViaggioAIErrorEvent += SpeechErrorEventHandler;

            Debug.Log("SubscribeToEvents successfull.");
        }
    }

    private void UnsubscribeFromEvents()
    {
        if (_speechMng != null)
        {
            _speechMng.OnIntentRecognitionSucceededEvent -= IntentRecognitionSucceededEventHandler;
            _speechMng.OnUserSpeechInputStartedEvent -= UserSpeechInputStartedEventHandler;
            _speechMng.OnIntentRecognitionFailedEvent -= IntentRecognitionFailedEventHandler;
            _speechMng.OnSpeechOutputStartedEvent -= SpeechOutputStartedEventHandler;
            _speechMng.OnSpeechOutputEndedEvent -= SpeechOutputEndedEventHandler;
            _speechMng.OnViaggioAIErrorEvent -= SpeechErrorEventHandler;
        }
    }

    private void IntentRecognitionSucceededEventHandler(object sender, string intentID)
    {
        _onIntentRecognitionSucceededEventTriggered = true;
        _recognizedIntentID = intentID;
    }

    private void UserSpeechInputStartedEventHandler(object sender, object e)
    {
        _onUserSpeechInputStartedEventTriggered = true;
    }

    private void IntentRecognitionFailedEventHandler(object sender, bool e)
    {
        _onIntentRecognitionFailedEventTriggered = true;
    }

    private void SpeechOutputStartedEventHandler(object sender, bool e)
    {
        _onSpeechOutputStartedEventTriggered = true;
    }

    private void SpeechOutputEndedEventHandler(object sender, bool e)
    {
        _onSpeechOutputEndedEventTriggered = true;
    }

    private void SpeechErrorEventHandler(object sender, string errorText)
    {
        _onErrorEventTriggered = true;
        _speechErrorText = errorText;
    }

    [Task]
    public async void InitializeSpeechManager()
    {
        if (Task.getState == NodeState.FirstRun)
        {
            while (_speechMng == null)
            {
                await System.Threading.Tasks.Task.Delay(10);
            }

            SubscribeToEvents();
            Task.SetSucceeded();
            return;
        }
    }

    [Task]
    public void AddPossbileSpeechIntent(string id)
    {
        if (Task.getState == NodeState.FirstRun)
        {
            _requestDataModel.PossibleIntents.Add(id, SpeechData.intents[id]);
            foreach (var r in _requestDataModel.PossibleIntents)
            {
                Debug.Log($"Possible Intent: {r.Key}, {r.Value}");
            }

            Task.SetSucceeded();
            return;
        }
    }

    [Task]
    public void StartSpeechIntentRecognition()
    {
        if (Task.getState == NodeState.FirstRun)
        {
            _onUserSpeechInputStartedEventTriggered = false;
            _speechMng.StartIntentRecognition(_requestDataModel.PossibleIntents);
            Task.SetSucceeded();
            return;
        }
    }

    [Task]
    public void UserStartedSpeechInput()
    {
        if (Task.getState == NodeState.FirstRun)
        {
            _onIntentRecognitionSucceededEventTriggered = false;
            _onIntentRecognitionFailedEventTriggered = false;
        }

        if (_onUserSpeechInputStartedEventTriggered)
        {
            Task.SetSucceeded();
            Debug.Log("UserStartedSpeechInput succeded.");
            return;
        }

        if (_abortSpeechEventListener)
        {
            Task.SetFailed();
            Debug.Log("UserStartedSpeechInput failed.");
            _abortSpeechEventListener = false;
            return;
        }
    }

    [Task]
    public void SpeechIntentRecognized()
    {
        if (_onIntentRecognitionSucceededEventTriggered)
        {
            Task.SetSucceeded();
            Debug.Log("SpeechIntentRecognized successfull");
            return;
        }

        if (_onIntentRecognitionFailedEventTriggered)
        {
            Task.SetFailed();
            Debug.Log("SpeechIntentRecognized failed");
            return;
        }
    }

    [Task]
    public void CompareUserSpeechInputStarted(bool value)
    {
        if (Task.getState == NodeState.FirstRun)
        {
            if (_onUserSpeechInputStartedEventTriggered == value)
            {
                Debug.Log($"CompareUserSpeechInputStarted with {value} = equal");
                Task.SetSucceeded();
                return;
            }
            else
            {
                Debug.Log($"CompareUserSpeechInputStarted with {value} = not equal");
                Task.SetFailed();
                return;
            }
        }
    }

    [Task]
    public void CompareIntentID(string intentID)
    {
        if (Task.getState == NodeState.FirstRun)
        {
            if (_recognizedIntentID == intentID)
            {
                Debug.Log($"CompareIntentID {_recognizedIntentID} with {intentID} = equal");
                Task.SetSucceeded();
                return;
            }
            else
            {
                Debug.Log($"CompareIntentID {_recognizedIntentID} with {intentID} = not equal");
                Task.SetFailed();
                return;
            }
        }
    }

    [Task]
    public void AbortSpeechEventListener()
    {
        if (Task.getState == NodeState.FirstRun)
        {
            _abortSpeechEventListener = true;
            Task.SetSucceeded();
            return;
        }
    }

    [Task]
    public void ClearPossbileSpeechIntents()
    {
        if (Task.getState == NodeState.FirstRun)
        {
            _requestDataModel.PossibleIntents.Clear();
            _recognizedIntentID = "";
            Task.SetSucceeded();
            return;
        }
    }

    [Task]
    public void StopSpeechIntentRecognition()
    {
        if (Task.getState == NodeState.FirstRun)
        {
            _speechMng.StopIntentRecognition();
            Task.SetSucceeded();
            return;
        }
    }

    [Task]
    public void SetSpeechRecognitionLanguage(string languageCode)
    {
        if (Task.getState == NodeState.FirstRun)
        {
            _speechMng.SetSpeechRecognitionLanguage(languageCode);
            SpeechData.languageCode = languageCode;
            Debug.Log($"Set Speech Recognition Language to {languageCode}");
            Task.SetSucceeded();
            return;
        }
    }

    [Task]
    public void SetVoiceName(string voiceName)
    {
        if (Task.getState == NodeState.FirstRun)
        {
            SpeechData.voiceName = voiceName;
            Debug.Log($"Set VoiceName to {voiceName}");
            Task.SetSucceeded();
            return;
        }
    }

    [Task]
    public void SynthesizeText(string text)
    {
        if (Task.getState == NodeState.FirstRun)
        {
            _onSpeechOutputStartedEventTriggered = false;
            _onSpeechOutputEndedEventTriggered = false;

            _speechMng.SynthesizeText(text, SpeechData.languageCode, SpeechData.voiceName);
        }

        if (_onSpeechOutputStartedEventTriggered)
        {
            Debug.Log("SynthesizeText: Speech Output started.");
            Task.SetSucceeded();
            return;
        }
    }

    [Task]
    public void SpeechOutputEnded()
    {
        if (_onSpeechOutputEndedEventTriggered)
        {
            Debug.Log("SynthesizeText: Speech Output ended.");
            Task.SetSucceeded();
            return;
        }
    }

    [Task]
    public void SpeechErrorOccured()
    {
        if (_onErrorEventTriggered)
        {
            Task.SetSucceeded();
            Debug.Log($"ERROR: Speech Service not working. Reason: {_speechErrorText}");
            _onErrorEventTriggered = false;
            return;
        }
    }
    #endregion

    #region Visited Stories Manager
    [Task]
    public void StoryAVisited()
    {
        if (Task.getState == NodeState.FirstRun)
        {
            if (VisitedStories.StoryA)
            {
                Debug.Log("Story A was visited.");
                Task.SetSucceeded();
                return;
            }
            else
            {
                Debug.Log("Story A was not visited.");
                Task.SetFailed();
                return;
            }
        }
    }

    [Task]
    public void StoryBVisited()
    {
        if (Task.getState == NodeState.FirstRun)
        {
            if (VisitedStories.StoryB)
            {
                Debug.Log("Story B was visited.");
                Task.SetSucceeded();
                return;
            }
            else
            {
                Debug.Log("Story B was not visited.");
                Task.SetFailed();
                return;
            }
        }
    }

    [Task]
    public void StoryCVisited()
    {
        if (Task.getState == NodeState.FirstRun)
        {
            if (VisitedStories.StoryC)
            {
                Debug.Log("Story C was visited.");
                Task.SetSucceeded();
                return;
            }
            else
            {
                Debug.Log("Story C was not visited.");
                Task.SetFailed();
                return;
            }
        }
    }

    [Task]
    public void SetStoryAVisited()
    {
        if (Task.getState == NodeState.FirstRun)
        {
            VisitedStories.StoryA = true;
            Debug.Log($"Set Visited Story A = {VisitedStories.StoryA}");
            Task.SetSucceeded();
            return;
        }
    }

    [Task]
    public void SetStoryBVisited()
    {
        if (Task.getState == NodeState.FirstRun)
        {
            VisitedStories.StoryB = true;
            Debug.Log($"Set Visited Story B = {VisitedStories.StoryB}");
            Task.SetSucceeded();
            return;
        }
    }

    [Task]
    public void SetStoryCVisited()
    {
        if (Task.getState == NodeState.FirstRun)
        {
            VisitedStories.StoryC = true;
            Debug.Log($"Set Visited Story C = {VisitedStories.StoryC}");
            Task.SetSucceeded();
            return;
        }
    }

    [Task]
    public void IncrementRepetitionVisitedCounter()
    {
        if (Task.getState == NodeState.FirstRun)
        {
            VisitedStories.RepetitionCounter++;
            Debug.Log($"Set Visited Repetition Counter = {VisitedStories.RepetitionCounter}");
            Task.SetSucceeded();
            return;
        }
    }

    [Task]
    public void CompareRepetitionVisitedCounter(int number)
    {
        if (Task.getState == NodeState.FirstRun)
        {
            if (VisitedStories.RepetitionCounter == number)
            {
                Debug.Log($"Visited Repetition Counter equals {number}");
                Task.SetSucceeded();
                return;
            }
            else
            {
                Debug.Log($"Visited Repetition Counter not equals {number}");
                Task.SetFailed();
                return;
            }
        }
    }

    [Task]
    public void NoStoriesVisited()
    {
        if (Task.getState == NodeState.FirstRun)
        {
            if (!VisitedStories.StoryA && !VisitedStories.StoryB && !VisitedStories.StoryC)
            {
                Task.SetSucceeded();
                return;
            }
            else
            {
                Task.SetFailed();
                return;
            }
        }
    }
    #endregion

    #region Oculus Input
#if OCULUSVR_AVAILABLE
    [Task]
    public void OVRInputGetDown (string buttonName) {
        if (System.Enum.TryParse(buttonName, out OVRInput.Button button)) {
            switch (Task.getState) {
                case NodeState.FirstRun:
                case NodeState.Running:
                    if (OVRInput.GetDown(button)) {
                        Task.SetSucceeded();
                        return;
                    }
                    break;
                case NodeState.Aborting:
                case NodeState.NotRunning:
                    break;
            }
        } else {
            Debug.LogError($"OVRInputGetDown: unknown button {buttonName}");
            Task.SetError();
        }
    }

    [Task]
    public void OVRInputGetUp (string buttonName) {
        if (System.Enum.TryParse(buttonName, out OVRInput.Button button)) {
            switch (Task.getState) {
                case NodeState.FirstRun:
                case NodeState.Running:
                    if (OVRInput.GetUp(button)) {
                        Task.SetSucceeded();
                        return;
                    }
                    break;
                case NodeState.Aborting:
                case NodeState.NotRunning:
                    break;
            }
        } else {
            Debug.LogError($"OVRInputGetUp: unknown button {buttonName}");
            Task.SetError();
        }
    }

    [Task]
    public void OVRInputGet (string buttonName) {
        if (System.Enum.TryParse(buttonName, out OVRInput.Button button)) {
            switch (Task.getState) {
                case NodeState.FirstRun:
                case NodeState.Running:
                    if (OVRInput.Get(button)) {
                        Task.SetSucceeded();
                        return;
                    } else {
                        Task.SetFailed();
                        return;
                    }                    
                    break;
                case NodeState.Aborting:
                case NodeState.NotRunning:
                    break;
            }
        } else {
            Debug.LogError($"OVRInputGet: unknown button {buttonName}");
            Task.SetError();
        }
    }
    #endif
    #endregion

    #region keyboard input

    [Task]
    public void GetKeyDown (string buttonName) {
        KeyCode button;
        if (System.Enum.TryParse(buttonName, out button)) {
            switch (Task.getState) {
                case NodeState.FirstRun:
                case NodeState.Running:
                    // Debug.Log($"GetKey: checking {button}");
                    if (UnityEngine.Input.GetKeyDown(button)) {
                        Task.SetSucceeded();
                        return;
                    }                  
                    break;
                case NodeState.Aborting:
                case NodeState.NotRunning:
                    break;
            }
        } else {
            Debug.LogError($"GetKeyDown: unknown key {buttonName}");
            Task.SetError();
        }
    }

    [Task]
    public void GetKeyUp (string buttonName) {
        KeyCode button;
        if (System.Enum.TryParse(buttonName, out button)) {
            switch (Task.getState) {
                case NodeState.FirstRun:
                case NodeState.Running:
                    // Debug.Log($"GetKey: checking {button}");
                    if (UnityEngine.Input.GetKeyUp(button)) {
                        Task.SetSucceeded();
                        return;
                    }
                    break;
                case NodeState.Aborting:
                case NodeState.NotRunning:
                    break;
            }
        } else {
            Debug.LogError($"GetKeyUp: unknown key {buttonName}");
            Task.SetError();
        }
    }

    [Task]
    public void GetKey (string buttonName) {
        KeyCode button;
        if (System.Enum.TryParse(buttonName, out button)) {
            switch (Task.getState) {
                case NodeState.FirstRun:
                case NodeState.Running:
                    // Debug.Log($"GetKey: checking {button}");
                    if (UnityEngine.Input.GetKey(button)) {
                        Task.SetSucceeded();
                        return;
                    } else {
                        Task.SetFailed();
                        return;
                    }                    
                    break;
                case NodeState.Aborting:
                case NodeState.NotRunning:
                    break;
            }
        } else {
            Debug.LogError($"GetKey: unknown key {buttonName}");
            Task.SetError();
        }
    }
#endregion

    #region SC
    public Transform cameraRoot;
    //public Transform[] cameraRootPositions;

    [Task]
    void ReparentCameraToGameObject(string gameObjectName) {
        foreach (NamedGameObject ngo in GetHandlers(gameObjectName)) {
            // NamedGameObject ngo = GetNamedObject<NamedGameObject>(gameObjectName);
            if (ngo != null) {
                if (Task.isStarting) { 
                    cameraRoot.transform.parent = ngo.transform;
                    cameraRoot.localPosition = Vector3.zero;
                    Debug.Log($"BTC.ReparentCameraToGameObject: reparent camera to {ngo.name}");
                }
                Task.SetSucceeded();
                return;
            }
            Task.SetFailed();
        }
    }

    [Task]
    public void Wait(float timer) {
        switch (Task.getState) {
            case NodeState.FirstRun:
                Task.data = Time.time;
                break;
            case NodeState.Running:
                float startTime = (float)Task.data;
                float elapsed = Time.time - startTime;
                Task.log = "" + timer;
                if (elapsed > timer) {
                    Task.SetSucceeded();
                    return;
                }
                break;
            case NodeState.Aborting:
                break;
        }

        //if (debug)
        //    Debug.Log("TestScript.Timer: " + timer + " " + thisNodeState);
    }

    [Task]
    public void RestartBT() {
        switch (Task.getState) {
            case NodeState.FirstRun:
            case NodeState.Running:
                GetComponent<TaskController>().Restart();
                // dont succeeed, wait for restart
                break;
            case NodeState.Aborting:
                break;
        }
    }
    #endregion

    #region Blackboard
    Dictionary<string, bool> boolValueDictionary = new Dictionary<string, bool>();
    [Task]
    void ClearBool (string key) {
        if ((Task.getState == NodeState.FirstRun) || (Task.getState == NodeState.Running)) {
            Debug.Log($"ClearBool {key}");
            boolValueDictionary.Remove(key);
            Task.SetSucceeded();
        }
    }

    [Task]
    void SetBool (string key) {
        if ((Task.getState == NodeState.FirstRun) || (Task.getState == NodeState.Running)) {
            SetBool(key, true);
            Task.SetSucceeded();
        }
    }

    [Task]
    void SetBool (string key, bool value) {
        if ((Task.getState == NodeState.FirstRun) || (Task.getState == NodeState.Running)) {
            Debug.Log($"SetBool {key} to {value}");
            boolValueDictionary[key] = value;
            Task.SetSucceeded();
        }
    }

    [Task]
    void CompareBool (string key) {
        if ((Task.getState == NodeState.FirstRun) || (Task.getState == NodeState.Running)) {
            CompareBool(key, true);
        }
    }
    [Task]
    void CompareBool (string key, bool value) {
        if ((Task.getState == NodeState.FirstRun) || (Task.getState == NodeState.Running)) {
            if (boolValueDictionary.ContainsKey(key)) {
                if (boolValueDictionary[key] == value) {
                    Debug.Log($"CompareBool {key} with {value} = equal");
                    Task.SetSucceeded();
                    return;
                }
                else {
                    Debug.Log($"CompareBool {key} with {value} = not equal");
                    Task.SetFailed();
                    return;
                } 
            } else {
                Task.SetFailed();
                return;
            }
        }
    }


    Dictionary<string, float> floatValueDictionary = new Dictionary<string, float>();
    [Task]
    void ClearFloat (string key) {
        if ((Task.getState == NodeState.FirstRun) || (Task.getState == NodeState.Running)) {
            floatValueDictionary.Remove(key);
            Task.SetSucceeded();
        }
    }

    [Task]
    void AddFloat (string key, float value) {
        if ((Task.getState == NodeState.FirstRun) || (Task.getState == NodeState.Running)) {
            if (!floatValueDictionary.ContainsKey(key)) {
                floatValueDictionary[key] = 0;
            }
            floatValueDictionary[key] += value;
            Task.SetSucceeded();
        }
    }

    [Task]
    void SetFloat (string key, int value) {
        if ((Task.getState == NodeState.FirstRun) || (Task.getState == NodeState.Running)) {
            floatValueDictionary[key] = value;
            Task.SetSucceeded();
        }
    }

    [Task]
    void CompareFloatBigger (string key, float compareValue) {
        if ((Task.getState == NodeState.FirstRun) || (Task.getState == NodeState.Running)) {
            float value = 0;
            if (floatValueDictionary.ContainsKey(key)) {
                value = floatValueDictionary[key];
            }
            else {
                Debug.LogError($"FloatBigger: no value defined for {key}");
            }

            if (value > compareValue) {
                Task.SetSucceeded();
            }
            else {
                Task.SetFailed();
            }
        }
    }

    Dictionary<string, int> intDictionary = new Dictionary<string, int>();
    [Task]
    void ClearInt (string key) {
        if ((Task.getState == NodeState.FirstRun) || (Task.getState == NodeState.Running)) {
            intDictionary.Remove(key);
            Task.SetSucceeded();
        }
    }

    [Task]
    void AddInt (string key, int value) {
        if ((Task.getState == NodeState.FirstRun) || (Task.getState == NodeState.Running)) {
            if (!intDictionary.ContainsKey(key)) {
                intDictionary[key] = 0;
            }
            intDictionary[key] += value;
            Debug.Log($"IndAdd {key} {intDictionary[key]}");
            Task.SetSucceeded();
        }
    }

    [Task]
    void SetInt (string key, int value) {
        if ((Task.getState == NodeState.FirstRun) || (Task.getState == NodeState.Running)) {
            intDictionary[key] = value;
            Debug.Log("IntSet "+key+" "+intDictionary[key]);
            Task.SetSucceeded();
        }
    }

    [Task]
    void CompareIntBigger (string key, float compareValue) {
        if ((Task.getState == NodeState.FirstRun) || (Task.getState == NodeState.Running)) {
            int value = 0;
            if (intDictionary.ContainsKey(key)) {
                value = intDictionary[key];
            }
            else {
                Debug.LogError("CounterBigger: no value defined for "+key);
            }

            Debug.Log("IntBigger "+key+" "+value+" > "+compareValue);

            if (value > compareValue) {
                Task.SetSucceeded();
            }
            else {
                Task.SetFailed();
            }
        }
    }

    [Task]
    void LogMessage(string logMessage) {
        Debug.Log(logMessage);
        Task.SetSucceeded();
    }

    #endregion

    #region Succeed and fail
    [Task]
    void Fail() {
        Task.SetFailed();
    }
    
    [Task]
    void Succeed() {
        Task.SetSucceeded();
    }
    #endregion
}