#if USING_NETCODE_GO

using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using Unity.Collections;
#if USING_XRI
using UnityEngine.XR.Interaction.Toolkit;
#endif
using Unity.Netcode;
#if USING_TMP
using TMPro;
#endif
using Unity.Netcode.Components;
using UnityEngine.UI;

namespace SimplifyXR {

    /// <summary>
    /// Share the value of a network variable over the network.
    /// </summary>
    [DirectiveCategory(DirectiveCategories.Action, DirectiveSubCategory.Networking,
        prettyName = "Share Network Variable",
        directiveInfo = "This <i>action</i> share the value of a network variable based on Client or sServer Authority.")]
    //[ExecuteInEditMode]
    public class ShareNetworkVariable : UseListOrSingleObject, IChangeWhatGameObjectExecutes, IChangeNetworkAuthority
    {
        /// <summary>
        /// If Network Object will be client or server authoritative
        /// </summary>
#if UNITY_EDITOR
        [Tooltip("If network object will be client or server authoritative")]
#endif
        public SimplifyXREnums.ClientOrServerAuthoritative NetworkAuthorityState = SimplifyXREnums.ClientOrServerAuthoritative.Client;

        /// <summary>
        /// If Network Variable will be bool, int, float, or FixedString128
        /// </summary>
#if UNITY_EDITOR
        [Tooltip("If Network Variable will be bool, int, float, or FixedString128")]
#endif
        public SimplifyXREnums.NetworkVariableTypes NetworkVariableType = SimplifyXREnums.NetworkVariableTypes.Bool;

        /// <summary>
        /// The bool to share across the network
        /// </summary>
#if UNITY_EDITOR
        [Tooltip("The bool to share across the network."), Conditional("NetworkVariableType",
            SimplifyXREnums.NetworkVariableTypes.Bool, ComparisonType.Equals),
            Required("A Bool must be selected")]
#endif
        public NetworkVariable<bool> _bool = new NetworkVariable<bool>
        (
            false,
            NetworkVariableReadPermission.Everyone,
            NetworkVariableWritePermission.Server
        );

        /// <summary>
        /// Choose to enter true, false, or toggle the bool value across the network
        /// </summary>
#if UNITY_EDITOR
        [Tooltip("Choose to enter true, false, or toggle the bool value across the network."), Conditional("NetworkVariableType",
            SimplifyXREnums.NetworkVariableTypes.Bool, ComparisonType.Equals),
            Required("A Bool must be selected")]
#endif
        public SimplifyXREnums.TrueFalseOrToggle _trueFalseToggle = SimplifyXREnums.TrueFalseOrToggle.Toggle;

        /// <summary>
        /// The int to share across the network
        /// </summary>
#if UNITY_EDITOR
        [Tooltip("The int to share across the network."), Conditional("NetworkVariableType",
            SimplifyXREnums.NetworkVariableTypes.Int, ComparisonType.Equals),
            Required("An Int must be selected")]
#endif
        public NetworkVariable<Int32> _int32 = new NetworkVariable<Int32>
        (
            0,
            NetworkVariableReadPermission.Everyone,
            NetworkVariableWritePermission.Server
        );

        /// <summary>
        /// Choose to enter value, increment, or decrement the int amount across the network
        /// </summary>
#if UNITY_EDITOR
        [Tooltip("Choose to enter value, increment, or decrement the int amount across the network."), Conditional("NetworkVariableType",
            SimplifyXREnums.NetworkVariableTypes.Int, ComparisonType.Equals),
            Required("An Int must be selected")]
#endif
        public SimplifyXREnums.ValueIncrementOrDecrement _intOperator = SimplifyXREnums.ValueIncrementOrDecrement.Value;

        /// <summary>
        /// The int amount to change across the network
        /// </summary>
#if UNITY_EDITOR
        [Tooltip("The int to share across the network."), Conditional("NetworkVariableType",
            SimplifyXREnums.NetworkVariableTypes.Int, ComparisonType.Equals),
            Required("An Int must be entered")]
#endif
        public Int32 _intAmount = 1;

        /// <summary>
        /// The float to share across the network
        /// </summary>
#if UNITY_EDITOR
        [Tooltip("The float to share across the network."), Conditional("NetworkVariableType",
            SimplifyXREnums.NetworkVariableTypes.Float, ComparisonType.Equals),
            Required("A float must be selected")]
#endif
        public NetworkVariable<float> _float = new NetworkVariable<float>
        (
            0,
            NetworkVariableReadPermission.Everyone,
            NetworkVariableWritePermission.Server
        );

        /// <summary>
        /// Choose to enter value, increment, or decrement the float amount across the network
        /// </summary>
#if UNITY_EDITOR
        [Tooltip("Choose to enter value, increment, or decrement the float amount across the network."), Conditional("NetworkVariableType",
            SimplifyXREnums.NetworkVariableTypes.Float, ComparisonType.Equals),
            Required("An Int must be selected")]
#endif
        public SimplifyXREnums.ValueIncrementOrDecrement _floatOperator = SimplifyXREnums.ValueIncrementOrDecrement.Value;

        /// <summary>
        /// The float amount to change across the network
        /// </summary>
#if UNITY_EDITOR
        [Tooltip("The float to share across the network."), Conditional("NetworkVariableType",
            SimplifyXREnums.NetworkVariableTypes.Float, ComparisonType.Equals),
            Required("A float must be entered")]
#endif
        public float _floatAmount = 0.5f;

        /// <summary>
        /// The FixedString128 to share across the network
        /// </summary>
#if UNITY_EDITOR
        [Tooltip("The FixedString128 to share across the network."), Conditional("NetworkVariableType",
            SimplifyXREnums.NetworkVariableTypes.FixedString128, ComparisonType.Equals),
            Required("An FixedString128 must be selected")]
#endif
        public NetworkVariable<FixedString128Bytes> _string = new NetworkVariable<FixedString128Bytes>
        (
            "",
            NetworkVariableReadPermission.Everyone,
            NetworkVariableWritePermission.Server
        );

        /// <summary>
        /// The FixedString128 to share across the network
        /// </summary>
#if UNITY_EDITOR
        [Tooltip("The FixedString128 to share across the network."), Conditional("NetworkVariableType",
            SimplifyXREnums.NetworkVariableTypes.FixedString128, ComparisonType.Equals),
            Required("An FixedString128 must be selected")]
#endif
        public String _stringField = "";

        public SimplifyXREnums.TextTypes TextType = SimplifyXREnums.TextTypes.Text;

#if UNITY_EDITOR
        [Tooltip("The Text Type to Update."), Conditional("TextType",
            SimplifyXREnums.TextTypes.Text, ComparisonType.Equals),
            Required("A Text Type must be selected")]
#endif
        public Text _textField;

#if UNITY_EDITOR
        [Tooltip("The Text Type to Update."), Conditional("TextType",
            SimplifyXREnums.TextTypes.TextMeshPro_UGUI, ComparisonType.Equals),
            Required("A Text Type must be selected")]
#endif
        public TextMeshProUGUI _textField_TMPUGUI;

#if UNITY_EDITOR
        [Tooltip("The Text Type to Update."), Conditional("TextType",
            SimplifyXREnums.TextTypes.TextMeshPro, ComparisonType.Equals),
            Required("A Text Type must be selected")]
#endif
        public TextMeshPro _textField_TMP;

        ShareClientNetworkVariableBehaviour shareClientNetworkVariableBehaviour;
        ShareServerNetworkVariableBehaviour shareServerNetworkVariableBehaviour;
        NetworkObject no;

        public override List<KnobKeywords> ReceiveKeywords()
        {
            return new List<KnobKeywords> {
                new KnobKeywords("GameObject", typeof(GameObject)),
                new KnobKeywords("Transform", typeof(Transform)),
                new KnobKeywords("ListOfGameObjects", typeof(List<GameObject>)),
                new KnobKeywords("NetworkObject", typeof(NetworkObject)),
                new KnobKeywords("ListOfNetworkObject", typeof(List<NetworkObject>)),
#if USING_XRI
                new KnobKeywords("BaseInteractionEventArgs", typeof(BaseInteractionEventArgs)),
#endif
                new KnobKeywords("NetworkAuthState", typeof(SimplifyXREnums.ClientOrServerAuthoritative)),
            };
        }

        public override List<KnobKeywords> SendKeywords()
        {
            return new List<KnobKeywords> {
                new KnobKeywords("GameObject", typeof(GameObject)),
                new KnobKeywords("Transform", typeof(Transform)),
                new KnobKeywords("ListOfGameObjects", typeof(List<GameObject>)),
                new KnobKeywords("NetworkObject", typeof(NetworkObject)),
                new KnobKeywords("ListOfNetworkObject", typeof(List<NetworkObject>)),
#if USING_XRI
                new KnobKeywords("BaseInteractionEventArgs", typeof(BaseInteractionEventArgs)),
#endif
                new KnobKeywords("NetworkAuthState", typeof(SimplifyXREnums.ClientOrServerAuthoritative)),
            };
        }

        public override void Execute()
        {
            ResetPassedObjects();
            FindObjectPassed();

            Debug.Log($"Execute : UseListOfObjects = {UseListOfObjects}");
            if (!UseListOfObjects)
            {
                ChangeNetworkVariableValue(ObjectToChange);
            } 
            else
            {
                foreach (GameObject g in ObjectsToChange)
                    ChangeNetworkVariableValue(g);
            }

            if (CheckIfObjectExists())
            {
                SendData();
            }
            ThisActionCompleted();

        }

        void FindObjectPassed()
        {
            var objectPassed = GetPassableData();
            if (objectPassed == null) return;

            if (KeywordInUse == "GameObject")
            {
                ObjectToChange = objectPassed as GameObject;
                UseListOfObjects = false;
            }
            else if (KeywordInUse == "Transform")
            {
                var passedTansform = objectPassed as Transform;
                ObjectToChange = passedTansform.gameObject;
                UseListOfObjects = false;
            }
            else if (KeywordInUse == "ListOfGameObjects")
            {
                var newList = objectPassed as List<GameObject>;
                ObjectsToChange = new List<GameObject>(newList);
                UseListOfObjects = true;
            }
            else if (KeywordInUse == "NetworkObject")
            {
                var passedNetworkObject = objectPassed as NetworkObject;
                ObjectToChange = passedNetworkObject.gameObject;
                UseListOfObjects = false;
            }
            else if (KeywordInUse == "ListOfNetworkObjects")
            {
                var newList = objectPassed as List<NetworkObject>;
                for(int i = 0; i<newList.Count; i++)
                {
                    ObjectsToChange.Add(newList[i].gameObject);
                }
                UseListOfObjects = true;
            }
#if USING_XRI
            else if (KeywordInUse == "BaseInteractionEventArgs")
            {
                var passedBaseInteractionEventArgs = objectPassed as BaseInteractionEventArgs;
                ObjectToChange = passedBaseInteractionEventArgs.interactableObject.transform.gameObject;
                UseListOfObjects = false;
            }
#endif
            else if (KeywordInUse == "NetworkAuthState")
            {
                SimplifyXREnums.ClientOrServerAuthoritative clientOrServerAuthoritative = (SimplifyXREnums.ClientOrServerAuthoritative)objectPassed;
                NetworkAuthorityState = clientOrServerAuthoritative;
            }
        }

        bool CheckIfObjectExists()
        {
            if ((!UseListOfObjects && ObjectToChange != null) || (UseListOfObjects && ObjectsToChange != null && ObjectsToChange.Any()))
            {
                GetGameObject();
                if (gameObjectsToChange != null)
                    return true;
            }
            else
                SimplifyXRDebug.SimplifyXRLog(SimplifyXRDebug.Type.AuthorError, "No GameObject specified or passed for {0}", SimplifyXRDebug.Args(this));

            return false;
        }

        void GetGameObject()
        {
            if (!UseListOfObjects)
                ChangeGameObject(ObjectToChange);
            else
            {
                foreach (GameObject g in ObjectsToChange)
                    ChangeGameObject(g);
            }
        }

#if UNITY_EDITOR
        void OnValidate()
        {
            UnityEditor.EditorApplication.delayCall += () =>
            {
                if (!UseListOfObjects)
                    ChangeGameObject(ObjectToChange);
                else
                {
                    foreach (GameObject g in ObjectsToChange)
                        ChangeGameObject(g);
                }
            };
        }
#endif

        void ChangeGameObject(GameObject go)
        {
            if (go != null)
            {
                // For sending along what's been changed
                if(gameObjectsToChange != null)
                    gameObjectsToChange.Add(go);

                AttachNetworkVariableBehaviour(ObjectToChange);
            }
        }

        void AttachNetworkVariableBehaviour(GameObject go)
        {
            no = go.GetComponent<NetworkObject>() != null ?
            go.GetComponent<NetworkObject>() : go.gameObject.AddComponent<NetworkObject>();

            if (NetworkAuthorityState == SimplifyXREnums.ClientOrServerAuthoritative.Client)
            {
                if (go.GetComponent<ShareServerNetworkVariableBehaviour>() != null)
                {
                    DestroyImmediate(go.GetComponent<ShareServerNetworkVariableBehaviour>());
                }

                shareClientNetworkVariableBehaviour = 
                    go.GetComponent<ShareClientNetworkVariableBehaviour>() != null ?
                    go.GetComponent<ShareClientNetworkVariableBehaviour>() : go.gameObject.AddComponent<ShareClientNetworkVariableBehaviour>();

                if (_textField != null)
                {
                    shareClientNetworkVariableBehaviour._textField = _textField;
                }
#if USING_TMP
                else if (_textField_TMPUGUI != null)
                {
                    shareClientNetworkVariableBehaviour._textField_TMPUGUI = _textField_TMPUGUI;
                }
                else if (_textField_TMP != null)
                {
                    shareClientNetworkVariableBehaviour._textField_TMP = _textField_TMP;
                }
#endif

                shareClientNetworkVariableBehaviour.NetworkVariableType = NetworkVariableType;
            }
            else if (NetworkAuthorityState == SimplifyXREnums.ClientOrServerAuthoritative.Server)
            {
                if (go.GetComponent<ShareClientNetworkVariableBehaviour>() != null)
                {
                    DestroyImmediate(go.GetComponent<ShareClientNetworkVariableBehaviour>());
                }

                shareServerNetworkVariableBehaviour =
                    go.GetComponent<ShareServerNetworkVariableBehaviour>() != null ?
                    go.GetComponent<ShareServerNetworkVariableBehaviour>() : go.gameObject.AddComponent<ShareServerNetworkVariableBehaviour>();

                if (_textField != null)
                {
                    shareServerNetworkVariableBehaviour._textField = _textField;
                }
#if USING_TMP
                else if(_textField_TMPUGUI != null)
                {
                    shareServerNetworkVariableBehaviour._textField_TMPUGUI = _textField_TMPUGUI;
                }
                else if (_textField_TMP != null)
                {
                    shareServerNetworkVariableBehaviour._textField_TMP = _textField_TMP;
                }
#endif

                shareServerNetworkVariableBehaviour.NetworkVariableType = NetworkVariableType;
            }
        }

        void PassNetworkVariableCondition(NetworkVariableBehaviour networkVariableBehaviour, SimplifyXREnums.NetworkVariableTypes networkVariable)
        {
            Debug.Log($"PassNetworkVariableCondition : networkVariable = {networkVariable}");
            if (networkVariable == SimplifyXREnums.NetworkVariableTypes.Bool)
            {
                if(_trueFalseToggle == SimplifyXREnums.TrueFalseOrToggle.Toggle)
                {
                    networkVariableBehaviour.UpdateBoolValue();
                }
                else if(_trueFalseToggle == SimplifyXREnums.TrueFalseOrToggle.True)
                {
                    networkVariableBehaviour.UpdateBoolValue(true);
                }
                else
                {
                    networkVariableBehaviour.UpdateBoolValue(false);
                }
            }
            else if (networkVariable == SimplifyXREnums.NetworkVariableTypes.Int)
            {
                if (_intOperator == SimplifyXREnums.ValueIncrementOrDecrement.Value)
                {
                    networkVariableBehaviour.UpdateIntValue(_intAmount);
                }
                else if (_intOperator == SimplifyXREnums.ValueIncrementOrDecrement.Increment)
                {
                    networkVariableBehaviour.UpdateIntValue(_intAmount, true);
                }
                else if (_intOperator == SimplifyXREnums.ValueIncrementOrDecrement.Decrement)
                {
                    networkVariableBehaviour.UpdateIntValue(_intAmount, false);
                }
            }
            else if (networkVariable == SimplifyXREnums.NetworkVariableTypes.Float)
            {
                if (_floatOperator == SimplifyXREnums.ValueIncrementOrDecrement.Value)
                {
                    networkVariableBehaviour.UpdateFloatValue(_floatAmount);
                }
                else if (_floatOperator == SimplifyXREnums.ValueIncrementOrDecrement.Increment)
                {
                    networkVariableBehaviour.UpdateFloatValue(_floatAmount, true);
                }
                else if (_floatOperator == SimplifyXREnums.ValueIncrementOrDecrement.Decrement)
                {
                    networkVariableBehaviour.UpdateFloatValue(_floatAmount, false);
                }
            }
            else if (networkVariable == SimplifyXREnums.NetworkVariableTypes.FixedString128)
            {
                networkVariableBehaviour.UpdateFixedString128Value(_stringField);
            }
        }

        void ChangeNetworkVariableValue(GameObject go)
        {
            Debug.Log($"ChangeNetworkVariableValue : go.name = {go.name}");
            no = go.GetComponent<NetworkObject>() != null ?
            go.GetComponent<NetworkObject>() : go.gameObject.AddComponent<NetworkObject>();



            Debug.Log($"ChangeNetworkVariableValue : no = {no}");
            if (NetworkAuthorityState == SimplifyXREnums.ClientOrServerAuthoritative.Client)
            {
                Debug.Log($"ChangeNetworkVariableValue : NetworkAuthorityState = {NetworkAuthorityState}");
                var shareClientNetworkVariableBehaviour = go.GetComponent<ShareClientNetworkVariableBehaviour>() != null ?
                go.GetComponent<ShareClientNetworkVariableBehaviour>() : go.gameObject.AddComponent<ShareClientNetworkVariableBehaviour>();
                Debug.Log($"ChangeNetworkVariableValue : shareClientNetworkVariableBehaviour = {shareClientNetworkVariableBehaviour}");

                if (no != null)
                {
                    if (Unity.Netcode.NetworkManager.Singleton.IsServer)// && IsOwner
                    {
                        PassNetworkVariableCondition(shareClientNetworkVariableBehaviour, shareClientNetworkVariableBehaviour.NetworkVariableType);
                    }
                    else
                    {
                        if (Unity.Netcode.NetworkManager.Singleton.LocalClient != null)
                        {
                            PassNetworkVariableCondition(shareClientNetworkVariableBehaviour, shareClientNetworkVariableBehaviour.NetworkVariableType);
                        }
                    }
                }
            }
            else if (NetworkAuthorityState == SimplifyXREnums.ClientOrServerAuthoritative.Server)
            {
                Debug.Log($"ChangeNetworkVariableValue : NetworkAuthorityState = {NetworkAuthorityState}");
                var shareServerNetworkVariableBehaviour = go.GetComponent<ShareServerNetworkVariableBehaviour>() != null ?
                go.GetComponent<ShareServerNetworkVariableBehaviour>() : go.gameObject.AddComponent<ShareServerNetworkVariableBehaviour>();
                Debug.Log($"ChangeNetworkVariableValue : shareServerNetworkVariableBehaviour = {shareServerNetworkVariableBehaviour}");

                if (no != null)
                {
                    if (Unity.Netcode.NetworkManager.Singleton.IsServer)// && IsOwner
                    {
                        Debug.Log($"ChangeNetworkVariableValue (Server) : {shareServerNetworkVariableBehaviour.NetworkVariableType}");
                        PassNetworkVariableCondition(shareServerNetworkVariableBehaviour, shareServerNetworkVariableBehaviour.NetworkVariableType);
                    }
                    else
                    {
                        if (Unity.Netcode.NetworkManager.Singleton.LocalClient != null)
                        {
                            Debug.Log($"ChangeNetworkVariableValue (LocalClient) : {shareServerNetworkVariableBehaviour.NetworkVariableType}");
                            PassNetworkVariableCondition(shareServerNetworkVariableBehaviour, shareServerNetworkVariableBehaviour.NetworkVariableType);
                        }
                    }
                }
            }
        }

        void SendData()
        {
            List<object> objects;
            if (!UseListOfObjects)
                objects = new List<object> { ObjectToChange, gameObjectsToChange, ObjectToChange.transform };
            else
                objects = new List<object> { null, gameObjectsToChange, null };

            AddPassableData(new List<string> { "GameObject", "ListOfGameObjects", "Transform" }, objects);
        }

#region IChangeWhatGameObjectExecutes implementation
        string IModify<IChangeWhatGameObjectExecutes>.ModifyObjectName { get { return "Object to change"; } }

        public void ChangeWhatGameObjectExecutes(GameObject newObject)
        {
            ObjectToChange = newObject;
        }
#endregion

#region IChangeNetworkAuthority implementation
        string IModify<IChangeNetworkAuthority>.ModifyObjectName { get { return "Client state"; } }

        public void SetClient()
        {
            NetworkAuthorityState = SimplifyXREnums.ClientOrServerAuthoritative.Client;
        }

        public void SetServer()
        {
            NetworkAuthorityState = SimplifyXREnums.ClientOrServerAuthoritative.Server;
        }
#endregion
    }
}

#endif