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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Serialization;
using UnityEngine;

namespace MyBT {
    public enum CompositeParameter {
        Undefined,

        Sequence,
        Selector,
        Race,
        Marathon,
    }

    // [XmlInclude(typeof(CompositeNode))]
    public class CompositeNode : Node {
        [SerializeField]
        public List<CompositeParameter> parameters;
        
        [SerializeField]
        private CompositeParameter compositeType = CompositeParameter.Undefined;

        public bool Has(CompositeParameter compositeParameter) {
            return parameters.Contains(compositeParameter);
        }

        public void Init(int _nodeDepth, Token[] _tokens, List<string> stringParameter) {
            base.Init(_nodeDepth, _tokens);
            parameters = new List<CompositeParameter>();
            for (int i = 0; i < stringParameter.Count; i++) {
                CompositeParameter compositeParameter = CompositeParameter.Undefined;
                if (Enum.TryParse(stringParameter[i], out compositeParameter)) {
                    parameters.Add(compositeParameter);
                }

                else {
                    UnityEngine.Debug.LogError("CompositeNode." + this + " cannot convert parameter " + stringParameter[i]);
                    //throw new Exception("CompositeNode." + this + " cannot convert parameter " + stringParameter[i]);
                }
            }

            if (Has(CompositeParameter.Race)) {
                compositeType = CompositeParameter.Race;
            }
            else if (Has(CompositeParameter.Marathon)) {
                compositeType = CompositeParameter.Marathon;
            }
            else if (Has(CompositeParameter.Sequence)) {
                compositeType = CompositeParameter.Sequence;
            }
            else if (Has(CompositeParameter.Selector)) {
                compositeType = CompositeParameter.Selector;
            }
        }

        /// <summary>
        /// reset the whole behaviour tree structure
        /// </summary>
        public override void Destroy() {
            base.Destroy();
            parameters = null;
            //activeNodeId = 0;
        }

		public override IEnumerable<NodeResult> Tick(NodeRuntimeData parentNodeRuntimeData, int recursions) {

            if (debugInternalActive) {
                Debug.Log(NodeLogger($"Tick", $""));
            }

            // only work with 1 child && failsave in case we have an infinite loop
            if (recursions < recursionLimit) {
                using (NodeRuntimeData myNodeRuntimeData = new NodeRuntimeData(this, parentNodeRuntimeData)) {
                    myNodeRuntimeData.taskHasChanges = false;
                    if (debugInternalActive) Debug.Log(NodeLogger($"Tick.OnStart", $"{myNodeRuntimeData} nodeResult {myNodeRuntimeData.nodeResult} nodeState {myNodeRuntimeData.nodeState}"));

                    switch (compositeType) {
                        /// <summary>
                        /// Selector (fallback) node
                        /// 
                        /// Runs all Children in sequence:
                        /// - any child running -> continue in loop
                        /// - any child failed -> continue in loop 
                        /// - any child success -> abort loop with succeeded result
                        /// - finally -> exit failed (there is no looping)
                        /// 
                        /// In pseudocode, the algorithm for a fallback composition is:
                        /// 1 for i from 1 to n do
                        /// 2     childstatus ← Tick(child(i))
                        /// 3     if childstatus = success
                        /// 4        return success
                        /// 5 end
                        /// 6 return failure
                        /// </summary>
                        case CompositeParameter.Selector:
                            //int selectorIndex = 0;
                            myNodeRuntimeData.nodeResult = NodeResult.Continue;
                            for (int selectorIndex = 0; selectorIndex < children.Count; selectorIndex++) {
                                myNodeRuntimeData.currentIndex = selectorIndex;
                                //while (myNodeRuntimeData.nodeResult == NodeResult.Continue) {

                                myNodeRuntimeData.tickExecuteEnumerator = children[selectorIndex].Tick(myNodeRuntimeData, recursions + 1).GetEnumerator();

                                // must be run before MoveNext
                                myNodeRuntimeData.taskHasChanges = base.PreTick(NodeExecute.Run, myNodeRuntimeData);
                                while ((myNodeRuntimeData.tickExecuteEnumerator != null) && myNodeRuntimeData.tickExecuteEnumerator.MoveNext() && (myNodeRuntimeData.nodeResult == NodeResult.Continue)){
                                    //if (debugInternalActive) Debug.Log(NodeLogger($"Tick.Selector", $"{selectorIndex} exec {children[selectorIndex]} childResult {myNodeRuntimeData.tickEnumerator.Current}"));

                                    // selector succeedes when any child succeedes
                                    if (myNodeRuntimeData.tickExecuteEnumerator.Current == NodeResult.Succeeded) {
                                        myNodeRuntimeData.nodeResult = NodeResult.Succeeded;
                                    }
                                    // if last selector fails all children have failed
                                    if ((myNodeRuntimeData.tickExecuteEnumerator.Current == NodeResult.Failed) && (selectorIndex == children.Count - 1)) {
                                        myNodeRuntimeData.nodeResult = NodeResult.Failed;
                                    }

                                    myNodeRuntimeData.taskHasChanges |= base.PostTick(NodeExecute.Run, myNodeRuntimeData);

                                    if (debugInternalActive) Debug.Log(NodeLogger($"Tick.Selector", $"childResult {myNodeRuntimeData.tickExecuteEnumerator.Current} nodeState {myNodeRuntimeData.nodeState} nodeResult {myNodeRuntimeData.nodeResult}"));
                                    yield return myNodeRuntimeData.nodeResult;

                                    if (myNodeRuntimeData.taskHasChanges && taskController) {
                                        taskController.SetUiUpdate();
                                    }

                                    if (myNodeRuntimeData.nodeResult != NodeResult.Continue) {
                                        yield break;
                                    }

                                    // this should not be run again if it isnt in runnning mode anymore
                                    if (myNodeRuntimeData.nodeState == NodeState.Running) {
                                        myNodeRuntimeData.taskHasChanges = PreTick(NodeExecute.Run, myNodeRuntimeData);
                                    }
                                }

                                if (myNodeRuntimeData.nodeState != NodeState.Running) { 
                                    break;
                                }
                            }
                            myNodeRuntimeData.nodeResult = NodeResult.Failed;
                            yield return myNodeRuntimeData.nodeResult;
                            if (debugInternalActive) Debug.Log(NodeLogger($"Tick.Selector", $"finished nodeState {myNodeRuntimeData.nodeState} nodeResult {myNodeRuntimeData.nodeResult}"));
                            yield break;



                        /// <summary>
                        /// Sequence node
                        /// 
                        /// Starts all Children in sequence:
                        /// - any child running -> continue running
                        /// - any child success -> continue running
                        /// - any child failed -> abort loop with failed result
                        /// - finally -> return success result
                        ///
                        /// In pseudocode, the algorithm for a sequence composition is:
                        /// 1 for i from 1 to n do
                        /// 2     childstatus ← Tick(child(i))
                        /// 3     if childstatus = failure
                        /// 4        return failure
                        /// 5 end
                        /// 6 return success
                        /// </summary>
                        case CompositeParameter.Sequence:
                            myNodeRuntimeData.nodeResult = NodeResult.Continue;
                            for (int sequenceIndex = 0; sequenceIndex < children.Count; sequenceIndex++) {
                                myNodeRuntimeData.currentIndex = sequenceIndex;

                                myNodeRuntimeData.tickExecuteEnumerator = children[sequenceIndex].Tick(myNodeRuntimeData, recursions + 1).GetEnumerator();
                                // must be run before MoveNext

                                myNodeRuntimeData.taskHasChanges = base.PreTick(NodeExecute.Run, myNodeRuntimeData);
                                while ((myNodeRuntimeData.tickExecuteEnumerator != null) && myNodeRuntimeData.tickExecuteEnumerator.MoveNext() && (myNodeRuntimeData.nodeResult == NodeResult.Continue)) {

                                    // if any child fails, return failure
                                    if (myNodeRuntimeData.tickExecuteEnumerator.Current == NodeResult.Failed) {
                                        myNodeRuntimeData.nodeResult = NodeResult.Failed;
                                    }
                                    // if the last child succeedes, return succeeded
                                    if ((myNodeRuntimeData.tickExecuteEnumerator.Current == NodeResult.Succeeded) && (sequenceIndex == children.Count - 1)) {
                                        myNodeRuntimeData.nodeResult = NodeResult.Succeeded;
                                    }

                                    myNodeRuntimeData.taskHasChanges |= base.PostTick(NodeExecute.Run, myNodeRuntimeData);

                                    if (debugInternalActive) Debug.Log(NodeLogger($"Tick.Sequence", $"exec {sequenceIndex} childResult {myNodeRuntimeData.tickExecuteEnumerator.Current} nodeResult {myNodeRuntimeData.nodeResult}"));
                                    yield return myNodeRuntimeData.nodeResult;

                                    if (myNodeRuntimeData.taskHasChanges && taskController) {
                                        taskController.SetUiUpdate();
                                    }

                                    if (myNodeRuntimeData.nodeResult != NodeResult.Continue) {
                                        yield break;
                                    }

                                    // this should not be run again if it isnt in runnning mode anymore
                                    if (myNodeRuntimeData.nodeState == NodeState.Running) {
                                        myNodeRuntimeData.taskHasChanges = PreTick(NodeExecute.Run, myNodeRuntimeData);
                                    }
                                }

                                if (myNodeRuntimeData.nodeState != NodeState.Running) {
                                    break;
                                }
                            }
                            myNodeRuntimeData.nodeResult = NodeResult.Succeeded;
                            yield return myNodeRuntimeData.nodeResult;
                            if (debugInternalActive) Debug.Log(NodeLogger($"Tick.Selector", $"finished nodeState {myNodeRuntimeData.nodeState} nodeResult {myNodeRuntimeData.nodeResult}"));
                            yield break;



                        /// <summary>
                        /// Marathon Node
                        /// 
                        /// Starts all Children in parallel:
                        /// - any child running -> continue in loop
                        /// - any child success -> continue in loop
                        /// - any child failed -> continue in loop
                        /// - return after 1 iteration
                        /// - succeed when all are succeeded or failed
                        /// </summary>
                        case CompositeParameter.Marathon:
                            myNodeRuntimeData.childResults = new NodeResult[children.Count];
                            myNodeRuntimeData.subTickExecuteEnumerators = new IEnumerator<NodeResult>[children.Count];
                            for (int i = 0; i < children.Count; i++) {
                                myNodeRuntimeData.childResults[i] = NodeResult.Continue;
                                myNodeRuntimeData.subTickExecuteEnumerators[i] = children[i].Tick(myNodeRuntimeData, recursions + 1).GetEnumerator();
                            }

                            myNodeRuntimeData.nodeResult = NodeResult.Continue;
                            myNodeRuntimeData.taskHasChanges = base.PreTick(NodeExecute.Run, myNodeRuntimeData);
                            while (myNodeRuntimeData.nodeResult == NodeResult.Continue) {
                                // run one interation of all childs
                                for (int i = 0; i < children.Count; i++) {
                                    // if in continue, run
                                    if (debugInternalActive) Debug.Log(NodeLogger($"HandleMarathonExecute.BeforeRunning", $"running {i} {myNodeRuntimeData.childResults[i]} {children[i].GetType().ToString()}"));
                                    if (myNodeRuntimeData.childResults[i] == NodeResult.Continue) {
                                        myNodeRuntimeData.subTickExecuteEnumerators[i].MoveNext();
                                        myNodeRuntimeData.childResults[i] = (NodeResult)myNodeRuntimeData.subTickExecuteEnumerators[i].Current;

                                        if (myNodeRuntimeData.childResults[i] == NodeResult.Undefined) {
                                            //throw new System.ArgumentException("Parameter should not be undefined", $"childResults[{i}] -> {childResults[i]} from {children[i].ToString()}");
                                            Debug.LogError(NodeLogger($"HandleMarathonExecute", $"Parameter should not be undefined childResults[{i}] -> NodeResult.Undefined")); // from {children[i].ToString()}");
                                        }
                                        if (myNodeRuntimeData.childResults[i] == NodeResult.Error) {
                                            Debug.LogError(NodeLogger($"HandleMarathonExecute", $"error in child {i} : {children[i]}"));
                                        }
                                    }
                                    if (debugInternalActive) Debug.Log(NodeLogger($"HandleMarathonExecute.AfterRunning", $"running {i} {myNodeRuntimeData.childResults[i]}"));
                                }

#if false // nice but slow
                                // evaluate results
                                bool allSucceededOrFailed = myNodeRuntimeData.childResults.All(result => ((result == NodeResult.Succeeded) || (result == NodeResult.Failed)));
                                bool anyError = myNodeRuntimeData.childResults.Any(result => result == NodeResult.Error);

                                if (anyError) {
                                    Debug.LogError(NodeLogger($"HandleMarathonExecute", "HandleMarathonExecute: some node returned error"));
                                    myNodeRuntimeData.nodeResult = NodeResult.Error;
                                }
#else
                                bool allSucceededOrFailed = true;
                                foreach (NodeResult childResult in myNodeRuntimeData.childResults) {
                                    switch (childResult) {
                                        case NodeResult.Succeeded:
                                        case NodeResult.Failed:
                                            break;
                                        case NodeResult.Undefined:
                                        case NodeResult.Continue:
                                            allSucceededOrFailed = false;
                                            break;
                                        case NodeResult.Error:
                                            myNodeRuntimeData.nodeResult = NodeResult.Error;
                                            break;
                                    }
                                    //if (childResult == NodeResult.Error) {
                                    //    //anyError = true;
                                    //    Debug.LogError(NodeLogger($"HandleMarathonExecute", "HandleMarathonExecute: some node returned error"));
                                    //    myNodeRuntimeData.nodeResult = NodeResult.Error;
                                    //    break;
                                    //}
                                    //if ( ! ((childResult == NodeResult.Succeeded) || (childResult == NodeResult.Failed)) ) {
                                    //    allSucceededOrFailed = false;
                                    //    //myNodeRuntimeData.nodeResult = NodeResult.Succeeded;
                                    //    break;
                                    //}
                                }
#endif

                                // generate result
                                if (allSucceededOrFailed) {
                                    myNodeRuntimeData.nodeResult = NodeResult.Succeeded;
                                }
                                myNodeRuntimeData.taskHasChanges |= base.PostTick(NodeExecute.Run, myNodeRuntimeData);

                                if (myNodeRuntimeData.taskHasChanges && taskController) {
                                    taskController.SetUiUpdate();
                                }

                                if (debugInternalActive) Debug.Log(NodeLogger($"HandleMarathonExecute.Final", $"return {myNodeRuntimeData.nodeResult}"));
                                yield return myNodeRuntimeData.nodeResult;

                                // this should not be run again if it isnt in runnning mode anymore
                                if (myNodeRuntimeData.nodeState == NodeState.Running) {
                                    if (debugInternalActive) Debug.Log(NodeLogger($"Tick.Cleanup", $"changed to {myNodeRuntimeData.nodeResult}"));
                                    myNodeRuntimeData.taskHasChanges = PreTick(NodeExecute.Run, myNodeRuntimeData);
                                }
                                else {
                                    if (debugInternalActive) Debug.Log(NodeLogger($"Tick.Cleanup", $"Testing, should be executed once after success of enumerator"));
                                }
                            }

                            yield break;



                        /// <summary>
                        /// Race Node
                        /// 
                        /// Starts all Children in parallel:
                        /// - any child running -> continue in loop
                        /// - any child success -> return success, abort all remaining running
                        /// - any child failed -> continue in loop
                        /// - return after 1 iteration
                        /// - if all fail, fail.
                        /// </summary>
                        case CompositeParameter.Race:
                            myNodeRuntimeData.childResults = new NodeResult[children.Count];
                            myNodeRuntimeData.subTickExecuteEnumerators = new IEnumerator<NodeResult>[children.Count];
                            for (int i = 0; i < children.Count; i++) {
                                myNodeRuntimeData.childResults[i] = NodeResult.Continue;
                                myNodeRuntimeData.subTickExecuteEnumerators[i] = children[i].Tick(myNodeRuntimeData, recursions + 1).GetEnumerator();
                            }

                            myNodeRuntimeData.nodeResult = NodeResult.Continue;
                            myNodeRuntimeData.taskHasChanges = base.PreTick(NodeExecute.Run, myNodeRuntimeData);
                            while (myNodeRuntimeData.nodeResult == NodeResult.Continue) {
                                // run one interation of all childs
                                for (int i = 0; i < children.Count; i++) {
                                    if (myNodeRuntimeData.childResults[i] == NodeResult.Continue) {
                                        myNodeRuntimeData.subTickExecuteEnumerators[i].MoveNext();
                                        myNodeRuntimeData.childResults[i] = myNodeRuntimeData.subTickExecuteEnumerators[i].Current;
                                        if (debugInternalActive) Debug.Log(NodeLogger($"Race", $"node resulted in {myNodeRuntimeData.childResults[i]}"));
                                    }

                                    if (myNodeRuntimeData.childResults[i] == NodeResult.Error) {
                                        if (debugInternalActive) Debug.LogError(NodeLogger($"Race", $"{children[i]} has error"));
                                    }
                                }

#if false
                                // nice but slow?
                                bool anySucceeded = myNodeRuntimeData.childResults.Any(result => result == NodeResult.Succeeded);
                                bool allFailed = myNodeRuntimeData.childResults.All(result => result == NodeResult.Failed);
                                bool anyError = myNodeRuntimeData.childResults.Any(result => result == NodeResult.Error);

                                if (anyError) {
                                    Debug.LogError(NodeLogger($"Race", "some node returned error"));
                                    myNodeRuntimeData.nodeResult = NodeResult.Error;
                                }
                                if (anySucceeded) {
                                    myNodeRuntimeData.nodeResult = NodeResult.Succeeded;
                                }
#else
                                bool allFailed = true;
                                foreach (NodeResult childResult in myNodeRuntimeData.childResults) {
                                    switch (childResult) {
                                        case NodeResult.Failed:
                                            break;
                                        case NodeResult.Succeeded:
                                            myNodeRuntimeData.nodeResult = NodeResult.Succeeded;
                                            goto case NodeResult.Undefined;
                                        case NodeResult.Undefined:
                                        case NodeResult.Continue:
                                            allFailed = false;
                                            break;
                                        case NodeResult.Error:
                                            myNodeRuntimeData.nodeResult = NodeResult.Error;
                                            break;
                                    }

                                    //if (childResult != NodeResult.Failed) {
                                    //    allFailed = false;
                                    //}
                                    //if (childResult == NodeResult.Error) {
                                    //    //anyError = true;
                                    //    Debug.LogError(NodeLogger($"Race", "some node returned error"));
                                    //    myNodeRuntimeData.nodeResult = NodeResult.Error;
                                    //    break;
                                    //}
                                    //if (childResult == NodeResult.Succeeded) {
                                    //    //anySucceeded = true;
                                    //    myNodeRuntimeData.nodeResult = NodeResult.Succeeded;
                                    //    break;
                                    //}
                                }
#endif

                                if (allFailed) {
                                    myNodeRuntimeData.nodeResult = NodeResult.Failed;
                                }
                                myNodeRuntimeData.taskHasChanges |= base.PostTick(NodeExecute.Run, myNodeRuntimeData);

                                if (myNodeRuntimeData.taskHasChanges && taskController) {
                                    taskController.SetUiUpdate();
                                }

                                if (debugInternalActive) Debug.Log(NodeLogger($"Race", $"HandleRaceExecute: result {myNodeRuntimeData.nodeResult}"));
                                yield return myNodeRuntimeData.nodeResult;

                                // this should not be run again if it isnt in runnning mode anymore
                                if (myNodeRuntimeData.nodeState == NodeState.Running) {
                                    if (debugInternalActive) Debug.Log(NodeLogger($"Race", $"changed to {myNodeRuntimeData.nodeResult}"));
                                    myNodeRuntimeData.taskHasChanges = PreTick(NodeExecute.Run, myNodeRuntimeData);
                                }
                                else {
                                    if (debugInternalActive) Debug.Log(NodeLogger($"Race", $"Testing, should be executed once after success of enumerator"));
                                }
                            }

                            yield break;




                        default:
                            Debug.LogError(NodeLogger($"Tick", "no behaviour defined for CompositeNode, use at least Sequence or Parallel"));
                            yield break;
                    }
                }
            }
            else {
                Debug.LogError(NodeLogger("RunTreeNode.Tick", $"recursions {recursions}"));
                taskController.SetUiUpdate();
                yield return NodeResult.Error;
            }

            yield break;
        }




        public override Node Duplicate () {
            CompositeNode tn = ScriptableObject.CreateInstance<CompositeNode>();
            base.BaseDuplicate (tn);

            // CompositeNode tn = base.Duplicate() as CompositeNode;
            tn.parameters = parameters;
            tn.compositeType = compositeType;
            return tn;
        }

        public string ToString (NodeRuntimeData myNodeRuntimeData) {
            string customData = "";

            switch (compositeType) {
                case CompositeParameter.Race:
                case CompositeParameter.Marathon:
                    customData += "CResults: ";
                    if (myNodeRuntimeData.childResults != null) {
                        for (int i = 0; i < children.Count; i++) {
                            if (i < myNodeRuntimeData.childResults.Length) {
                                customData += myNodeRuntimeData.childResults[i] + " ";
                            }
                        }
                    }
                    break;
                case CompositeParameter.Sequence:
                    //customData += "Index: "+sequenceIndex;
                    break;
                case CompositeParameter.Selector:
                    //customData += "Index: "+selectorIndex;
                    break;
            }

            return base.ToString() + " " + customData;
        }
    }
}