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

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml.Serialization;
using UnityEngine;

namespace MyBT {
    public enum Actions {
        Idle,
        Log,
        RestartBehaviourTree,
        StartBehaviourTree,
        StopBehaviourTree,
        Wait
    }

    public class ActionNode : Node {

        [SerializeField]
        public string functionName;

        [SerializeField]
        public TaskParameterGroup taskParameterGroup;

        // cannot be seralized
        [NonSerialized]
        public Action action;

        public void Init (int _nodeDepth, Token[] _tokens, string _functionName, TaskParameterGroup _taskParameterGroup) {
            base.Init(_nodeDepth, _tokens);
            functionName = _functionName;
            taskParameterGroup = _taskParameterGroup;
        }

        /// <summary>
        /// reset the whole behaviour tree structure
        /// </summary>
        public override void Destroy() {
            Debug.Log(NodeLogger($"Destroy", ""));
            base.Destroy();
            action = null;
            taskParameterGroup = null;
            functionName = "";

            base.Destroy();
        }

        public bool isBound {
            get {
                return action != null;
            }
        }

        public override void Execute (NodeRuntimeData myNodeRuntimeData, NodeExecute nodeExecute) {
            ActionNodeRuntimeData actionNodeRuntimeData = (ActionNodeRuntimeData)myNodeRuntimeData;

            actionNodeRuntimeData.taskHasChanges = base.PreTick(nodeExecute, actionNodeRuntimeData);

            // clear data on firstrun
            if (actionNodeRuntimeData.nodeState == NodeState.FirstRun) {
                actionNodeRuntimeData.Clear();
            }

            //actionNodeRuntimeData.taskResult = actionNodeRuntimeData.nodeResult;
            Task.currentNode = this;
            Task.currentNodeRuntimeData = actionNodeRuntimeData;
            
            // run action
            // executing the action might change: logString, userData or taskResult of this node
            if (action != null) { 
                try {
                    //if (debugInternalActive) Debug.Log(NodeLogger($"Tick.{nodeExecute}.BeforeAction", $"nodeState {actionNodeRuntimeData.nodeState} nodeResult {actionNodeRuntimeData.nodeResult} taskResult {actionNodeRuntimeData.taskResult}"));
                    action();
                    //if (debugInternalActive) Debug.Log(NodeLogger($"Tick.{nodeExecute}.AfterAction", $"nodeState {actionNodeRuntimeData.nodeState} nodeResult {actionNodeRuntimeData.nodeResult} taskResult {actionNodeRuntimeData.taskResult}"));
                }
                catch (Exception ex) {
                    Debug.LogError(NodeLogger($"Tick.{nodeExecute}", $"Exception in Action \"{codeLine}({taskParameterGroup})\" {(tokens.Length > 0 ? $"{tokens[0].location.line}:{tokens[0].location.col}" : "undefined")} caused an Exception!\n===\n{ex.InnerException}\n===\n{ex}\n===\n"));
                    // Debug.LogError($"ex.Data:\n{ex.Data}\n\nex.InnerException.Message:\n{ex.InnerException}\n\nex.Message:\n{ex.Message}\n\nex:\n{ex}");
                    actionNodeRuntimeData.nodeResult = NodeResult.Error;
                }
            }
            else { Debug.LogError(NodeLogger($"Tick.{nodeExecute}", $"Action is undefined")); }

            if (debugInternalActive && (actionNodeRuntimeData.nodeResult == NodeResult.Error)) {
                Debug.LogError(NodeLogger($"Tick.{nodeExecute}", $"taskResult {actionNodeRuntimeData.nodeResult}"));
            }

            // update task and node according to each others changes
            actionNodeRuntimeData.taskHasChanges |= base.PostTick(nodeExecute, actionNodeRuntimeData);

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

            if (debugInternalActive) Debug.Log(NodeLogger($"Tick.{nodeExecute}.Last", $"nodeState {actionNodeRuntimeData.nodeState} nodeResult {actionNodeRuntimeData.nodeResult}"));
        }

        public override IEnumerable<NodeResult> Tick(NodeRuntimeData parentNodeRuntimeData, int recursions = 0) {
            if (debugInternalActive) Debug.Log(NodeLogger($"Tick", $"recursions {recursions}"));

            if ((action != null)) {
                using (ActionNodeRuntimeData myNodeRuntimeData = new ActionNodeRuntimeData(this, parentNodeRuntimeData)) {
                    do {
                        Execute(myNodeRuntimeData, NodeExecute.Run);

                        yield return myNodeRuntimeData.nodeResult;
                    } while (myNodeRuntimeData.nodeResult == NodeResult.Continue);
                    if (debugInternalActive) Debug.Log(NodeLogger($"Tick.Finished", $"taskResult {myNodeRuntimeData.nodeResult}"));
                }
            }
            else {
                Debug.LogError(NodeLogger("Tick", $"action undefined?"));
                yield return NodeResult.Error;
            }

            yield break;
        }

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

            tn.functionName = functionName;
            tn.taskParameterGroup = taskParameterGroup.Duplicate();
            tn.action = action;

            return tn;
        }

        public override string ToString() {
            return base.ToString();
        }
    }
}