using System;
using System.Collections.Generic;
using System.Linq;
using NodeCanvas.Framework;
using ParadoxNotion;
using UnityEngine;
using Logger = ParadoxNotion.Services.Logger;

namespace NodeCanvas.DialogueTrees
{

    ///<summary> Use DialogueTrees to create Dialogues between Actors</summary>
    [GraphInfo(
        packageName = "NodeCanvas",
        docsURL = "https://nodecanvas.paradoxnotion.com/documentation/",
        resourcesURL = "https://nodecanvas.paradoxnotion.com/downloads/",
        forumsURL = "https://nodecanvas.paradoxnotion.com/forums-page/"
        )]
    [CreateAssetMenu(menuName = "ParadoxNotion/NodeCanvas/Dialogue Tree Asset")]
    public class DialogueTree : Graph
    {

        ///----------------------------------------------------------------------------------------------
        [System.Serializable]
        class DerivedSerializationData
        {
            public List<ActorParameter> actorParameters;
        }

        public override object OnDerivedDataSerialization() {
            var data = new DerivedSerializationData();
            data.actorParameters = this.actorParameters;
            return data;
        }

        public override void OnDerivedDataDeserialization(object data) {
            if ( data is DerivedSerializationData ) {
                this.actorParameters = ( (DerivedSerializationData)data ).actorParameters;
            }
        }
        ///----------------------------------------------------------------------------------------------

        ///<summary>An Actor Parameter</summary>
        [System.Serializable]
        public class ActorParameter
        {
            [SerializeField] private string _keyName;
            [SerializeField] private string _id;
            [SerializeField] private UnityEngine.Object _actorObject;
            [System.NonSerialized] private IDialogueActor _actor;


            ///<summary>Key name of the parameter</summary>
            public string name {
                get { return _keyName; }
                set { _keyName = value; }
            }

            ///<summary>ID of the parameter</summary>
            public string ID => string.IsNullOrEmpty(_id) ? _id = System.Guid.NewGuid().ToString() : _id;

            ///<summary>The reference actor of the parameter</summary>
            public IDialogueActor actor {
                get
                {
                    if ( _actor == null ) {
                        _actor = _actorObject as IDialogueActor;
                    }
                    return _actor;
                }
                set
                {
                    _actor = value;
                    _actorObject = value as UnityEngine.Object;
                }
            }

            public ActorParameter() { }
            public ActorParameter(string name) { this.name = name; }
            public ActorParameter(string name, IDialogueActor actor) {
                this.name = name;
                this.actor = actor;
            }

            public override string ToString() { return name; }
        }

        ///----------------------------------------------------------------------------------------------

        ///<summary>The string used for the starting actor"</summary>
        public const string INSTIGATOR_NAME = "SELF";

        ///<summary>The dialogue actor parameters. We let Unity serialize this as well</summary>
        [SerializeField] public List<ActorParameter> actorParameters = new List<ActorParameter>();
        private bool enterStartNodeFlag;

        public static event Action<DialogueTree> OnDialogueStarted;
        public static event Action<DialogueTree> OnDialoguePaused;
        public static event Action<DialogueTree> OnDialogueFinished;
        public static event Action<SubtitlesRequestInfo> OnSubtitlesRequest;
        public static event Action<MultipleChoiceRequestInfo> OnMultipleChoiceRequest;

        ///<summary>The current DialogueTree running</summary>
        public static DialogueTree currentDialogue { get; private set; }
        ///<summary>The previous DialogueTree running</summary>
        public static DialogueTree previousDialogue { get; private set; }

        ///<summary>The current node of this DialogueTree</summary>
        public DTNode currentNode { get; private set; }

        ///----------------------------------------------------------------------------------------------
        public override System.Type baseNodeType => typeof(DTNode);
        public override bool requiresAgent => false;
        public override bool requiresPrimeNode => true;
        public override bool isTree => true;
        public override bool allowBlackboardOverrides => true;
        sealed public override bool canAcceptVariableDrops => false;
        public sealed override PlanarDirection flowDirection => PlanarDirection.Vertical;
        ///----------------------------------------------------------------------------------------------

        ///<summary>A list of the defined names for the involved actor parameters</summary>
        public List<string> definedActorParameterNames {
            get
            {
                var list = actorParameters.Select(r => r.name).ToList();
                list.Insert(0, INSTIGATOR_NAME);
                return list;
            }
        }

        ///<summary>Returns the ActorParameter by id</summary>
        public ActorParameter GetParameterByID(string id) {
            return actorParameters.Find(p => p.ID == id);
        }

        ///<summary>Returns the ActorParameter by name</summary>
        public ActorParameter GetParameterByName(string paramName) {
            return actorParameters.Find(p => p.name == paramName);
        }

        ///<summary>Returns the actor by parameter id.</summary>
        public IDialogueActor GetActorReferenceByID(string id) {
            var param = GetParameterByID(id);
            return param != null ? GetActorReferenceByName(param.name) : null;
        }

        ///<summary>Resolves and gets an actor based on the key name</summary>
        public IDialogueActor GetActorReferenceByName(string paramName) {

            //Check for INSTIGATOR selection
            if ( paramName == INSTIGATOR_NAME ) {

                //return it directly if it implements IDialogueActor
                if ( agent is IDialogueActor ) {
                    return (IDialogueActor)agent;
                }

                //Otherwise use the default actor and set name and transform from agent
                if ( agent != null ) {
                    return new ProxyDialogueActor(agent.gameObject.name, agent.transform);
                }

                return new ProxyDialogueActor("NO ACTOR", null);
            }

            //Check for non INSTIGATOR selection. If there IS an actor reference return it
            var refData = actorParameters.Find(r => r.name == paramName);
            if ( refData != null && refData.actor != null ) {
                return refData.actor;
            }

            //Otherwise use the default actor and set the name to the key and null transform
            Logger.Log(string.Format("An actor entry '{0}' on DialogueTree has no reference. A dummy Actor will be used with the entry Key for name", paramName), "Dialogue Tree", this);
            return new ProxyDialogueActor(paramName, null);
        }


        ///<summary>Set the target IDialogueActor for the provided key parameter name</summary>
        public void SetActorReference(string paramName, IDialogueActor actor) {
            var param = actorParameters.Find(p => p.name == paramName);
            if ( param == null ) {
                Logger.LogError(string.Format("There is no defined Actor key name '{0}'", paramName), "Dialogue Tree", this);
                return;
            }
            param.actor = actor;
        }

        ///<summary>Set all target IDialogueActors at once by provided dictionary</summary>
        public void SetActorReferences(Dictionary<string, IDialogueActor> actors) {
            foreach ( var pair in actors ) {
                var param = actorParameters.Find(p => p.name == pair.Key);
                if ( param == null ) {
                    Logger.LogWarning(string.Format("There is no defined Actor key name '{0}'. Seting actor skiped", pair.Key), "Dialogue Tree", this);
                    continue;
                }
                param.actor = pair.Value;
            }
        }

        ///<summary>Continues the DialogueTree at provided child connection index of currentNode</summary>
        public void Continue(int index = 0) {
            if ( index < 0 || index > currentNode.outConnections.Count - 1 ) {
                Stop(true);
                return;
            }
            currentNode.outConnections[index].status = Status.Success; //editor vis
            EnterNode((DTNode)currentNode.outConnections[index].targetNode);
        }

        ///<summary>Enters the provided node</summary>
        public void EnterNode(DTNode node) {
            currentNode = node;
            currentNode.Reset(false);
            if ( currentNode.Execute(agent, blackboard) == Status.Error ) {
                Stop(false);
            }
        }

        ///<summary>Raise the OnSubtitlesRequest event</summary>
        public static void RequestSubtitles(SubtitlesRequestInfo info) {
            if ( OnSubtitlesRequest != null )
                OnSubtitlesRequest(info);
            else Logger.LogWarning("Subtitle Request event has no subscribers. Make sure to add the default '@DialogueGUI' prefab or create your own GUI.", "Dialogue Tree");
        }

        ///<summary>Raise the OnMultipleChoiceRequest event</summary>
        public static void RequestMultipleChoices(MultipleChoiceRequestInfo info) {
            if ( OnMultipleChoiceRequest != null )
                OnMultipleChoiceRequest(info);
            else Logger.LogWarning("Multiple Choice Request event has no subscribers. Make sure to add the default '@DialogueGUI' prefab or create your own GUI.", "Dialogue Tree");
        }

        protected override void OnGraphStarted() {
            previousDialogue = currentDialogue;
            currentDialogue = this;

            Logger.Log(string.Format("Dialogue Started '{0}'", this.name), "Dialogue Tree", this);
            if ( OnDialogueStarted != null ) {
                OnDialogueStarted(this);
            }

            if ( !( agent is IDialogueActor ) ) {
                Logger.Log("Agent used in DialogueTree does not implement IDialogueActor. A dummy actor will be used.", "Dialogue Tree", this);
            }

            enterStartNodeFlag = true;
        }

        protected override void OnGraphUpdate() {
            if ( enterStartNodeFlag ) {
                //use a flag so that other nodes can do stuff on graph started
                enterStartNodeFlag = false;
                EnterNode(currentNode != null ? currentNode : (DTNode)primeNode);
            }

            if ( currentNode is IUpdatable ) {
                ( currentNode as IUpdatable ).Update();
            }
        }

        protected override void OnGraphStoped() {
            currentDialogue = previousDialogue;
            previousDialogue = null;
            currentNode = null;

            Logger.Log(string.Format("Dialogue Finished '{0}'", this.name), "Dialogue Tree", this);
            if ( OnDialogueFinished != null ) {
                OnDialogueFinished(this);
            }
        }

        protected override void OnGraphPaused() {
            Logger.Log(string.Format("Dialogue Paused '{0}'", this.name), "Dialogue Tree", this);
            if ( OnDialoguePaused != null ) {
                OnDialoguePaused(this);
            }
        }

        protected override void OnGraphUnpaused() {
            EnterNode(currentNode != null ? currentNode : (DTNode)primeNode);

            Logger.Log(string.Format("Dialogue Resumed '{0}'", this.name), "Dialogue Tree", this);
            if ( OnDialogueStarted != null ) {
                OnDialogueStarted(this);
            }
        }

        ///----------------------------------------------------------------------------------------------
        ///---------------------------------------UNITY EDITOR-------------------------------------------
#if UNITY_EDITOR
        [UnityEditor.MenuItem("Tools/ParadoxNotion/NodeCanvas/Create/Dialogue Tree Object", false, 2)]
        static void Editor_CreateGraph() {
            var dt = new GameObject("DialogueTree").AddComponent<DialogueTreeController>();
            UnityEditor.Selection.activeObject = dt;
        }
#endif

    }
}