#if USING_NETCODE_GO

using UnityEngine;
using System.Linq;
using System.Collections.Generic;
using Unity.Netcode;
using Unity.Netcode.Components;


namespace SimplifyXR {

    /// <summary>
    /// Share network transform of GameObject(s) over network. Use <cref="ModifyNetworkAuthority"/> to change if setting Client or Server authority.
    /// </summary>
    [DirectiveCategory(DirectiveCategories.Action, DirectiveSubCategory.Networking,
        prettyName = "Share Network Transform",
        directiveInfo = "This <i>action</i> shares the transform of a Networked Object based on Server or Client movement.")]
    //[ExecuteInEditMode]
    public class ShareNetworkTransform : UseListOrSingleObject, IChangeWhatGameObjectExecutes, IChangeNetworkAuthority, IChangeTransformAxis
    {
        /// <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>
        /// Which network object position axis will be synced.
        /// </summary>
        #if UNITY_EDITOR
        [Tooltip("Which network object position axis will be synced.")]
        #endif
        public SimplifyXREnums.AxisChoices SyncPosition = SimplifyXREnums.AxisChoices.X_Y_ZAxis;

        /// <summary>
        /// Which network object rotation axis will be synced.
        /// </summary>
        #if UNITY_EDITOR
        [Tooltip("Which network object rotation axis will be synced.")]
        #endif
        public SimplifyXREnums.AxisChoices SyncRotation = SimplifyXREnums.AxisChoices.X_Y_ZAxis;

        /// <summary>
        /// Which network object scale axis will be synced.
        /// </summary>
        #if UNITY_EDITOR
        [Tooltip("Which network object scale axis will be synced.")]
        #endif
        public SimplifyXREnums.AxisChoices SyncScale = SimplifyXREnums.AxisChoices.X_Y_ZAxis;

        /// <summary>
        /// Which network object scale axis will be synced.
        /// </summary>
        #if UNITY_EDITOR
        [Tooltip("Which network object scale axis will be synced.")]
        #endif
        public bool InLocalSpace = false;

        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("NetworkAuthState", typeof(SimplifyXREnums.ClientOrServerAuthoritative)),
                new KnobKeywords("SyncPositionAxis", typeof(SimplifyXREnums.AxisChoices)),
                new KnobKeywords("SyncRotationAxis", typeof(SimplifyXREnums.AxisChoices)),
                new KnobKeywords("SyncScaleAxis", typeof(SimplifyXREnums.AxisChoices)),
                new KnobKeywords("InLocalSpace", typeof(bool))
            };
        }

        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>)),
            };
        }

        public override void Execute()
        {
            ResetPassedObjects();
            FindObjectPassed();
            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 == "NetworkAuthState")
            {
                SimplifyXREnums.ClientOrServerAuthoritative clientOrServerAuthoritative = (SimplifyXREnums.ClientOrServerAuthoritative) objectPassed;
                NetworkAuthorityState = clientOrServerAuthoritative;
            }
            else if (KeywordInUse == "SyncPositionAxis")
            {
                SimplifyXREnums.AxisChoices syncPosition = (SimplifyXREnums.AxisChoices)objectPassed;
                SyncPosition = syncPosition;
            }
            else if (KeywordInUse == "SyncRotationAxis")
            {
                SimplifyXREnums.AxisChoices syncRotation = (SimplifyXREnums.AxisChoices)objectPassed;
                SyncRotation = syncRotation;
            }
            else if (KeywordInUse == "SyncScaleAxis")
            {
                SimplifyXREnums.AxisChoices syncScale = (SimplifyXREnums.AxisChoices)objectPassed;
                SyncScale = syncScale;
            }
            else if (KeywordInUse == "InLocalSpace")
            {
               bool inLocalSpace = (bool)objectPassed;
                InLocalSpace = inLocalSpace;
            }
        }

        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);

                PickAuthorityState(go);
                SyncAxis(go);
                SetSpace(go);
            }
        }

        void PickAuthorityState(GameObject go)
        {
            bool hasNetworkRigidbody = false;

            if (!go.GetComponent<NetworkObject>())
                go.gameObject.AddComponent<NetworkObject>();

            if (NetworkAuthorityState == SimplifyXREnums.ClientOrServerAuthoritative.Client)
            {
                if (go.GetComponent<NetworkTransform>() != null)
                {
                    if(go.GetComponent<NetworkRigidbody>() != null)
                    {
                        hasNetworkRigidbody = true;
                        DestroyImmediate(go.GetComponent<NetworkRigidbody>());
                        DestroyImmediate(go.GetComponent<NetworkTransform>());
                    }
                }

                if (!go.GetComponent<ClientNetworkTransform>())
                    go.gameObject.AddComponent<NetworkTransform>();

                if (hasNetworkRigidbody)
                {
                    hasNetworkRigidbody = false;
                    go.AddComponent<NetworkRigidbody>();
                }
            }
            else if (NetworkAuthorityState == SimplifyXREnums.ClientOrServerAuthoritative.Server)
            {
                if (go.GetComponent<ClientNetworkTransform>() != null)
                {
                    if (go.GetComponent<NetworkRigidbody>() != null)
                    {
                        hasNetworkRigidbody = true;
                        DestroyImmediate(go.GetComponent<NetworkRigidbody>());
                        DestroyImmediate(go.GetComponent<ClientNetworkTransform>());
                    }
                }

                if(!go.GetComponent<NetworkTransform>())
                    go.gameObject.AddComponent<NetworkTransform>();

                if (hasNetworkRigidbody)
                {
                    hasNetworkRigidbody = false;
                    go.AddComponent<NetworkRigidbody>();
                }
            }
        }

        void SyncAxis(GameObject go)
        {
            //XAxis, YAxis, ZAxis, X_YAxis, X_ZAxis, Y_ZAxis, X_Y_ZAxis, None
            if (go.GetComponent<NetworkTransform>() != null) 
            {
                NetworkTransform networkTransform = go.GetComponent<NetworkTransform>();
                switch (SyncPosition)
                {
                    case SimplifyXREnums.AxisChoices.XAxis:
                        networkTransform.SyncPositionX = true;
                        networkTransform.SyncPositionY = false;
                        networkTransform.SyncPositionZ = false;
                        break;
                    case SimplifyXREnums.AxisChoices.YAxis:
                        networkTransform.SyncPositionX = false;
                        networkTransform.SyncPositionY = true;
                        networkTransform.SyncPositionZ = false;
                        break;
                    case SimplifyXREnums.AxisChoices.ZAxis:
                        networkTransform.SyncPositionX = false;
                        networkTransform.SyncPositionY = false;
                        networkTransform.SyncPositionZ = true;
                        break;
                    case SimplifyXREnums.AxisChoices.X_YAxis:
                        networkTransform.SyncPositionX = true;
                        networkTransform.SyncPositionY = true;
                        networkTransform.SyncPositionZ = false;
                        break;
                    case SimplifyXREnums.AxisChoices.X_ZAxis:
                        networkTransform.SyncPositionX = true;
                        networkTransform.SyncPositionY = false;
                        networkTransform.SyncPositionZ = true;
                        break;
                    case SimplifyXREnums.AxisChoices.Y_ZAxis:
                        networkTransform.SyncPositionX = false;
                        networkTransform.SyncPositionY = true;
                        networkTransform.SyncPositionZ = true;
                        break;
                    case SimplifyXREnums.AxisChoices.X_Y_ZAxis:
                        networkTransform.SyncPositionX = true;
                        networkTransform.SyncPositionY = true;
                        networkTransform.SyncPositionZ = true;
                        break;
                    case SimplifyXREnums.AxisChoices.None:
                        networkTransform.SyncPositionX = false;
                        networkTransform.SyncPositionY = false;
                        networkTransform.SyncPositionZ = false;
                        break;
                    default:
                        networkTransform.SyncPositionX = true;
                        networkTransform.SyncPositionY = true;
                        networkTransform.SyncPositionZ = true;
                        break;
                }

                switch (SyncRotation)
                {
                    case SimplifyXREnums.AxisChoices.XAxis:
                        networkTransform.SyncRotAngleX = true;
                        networkTransform.SyncRotAngleY = false;
                        networkTransform.SyncRotAngleZ = false;
                        break;
                    case SimplifyXREnums.AxisChoices.YAxis:
                        networkTransform.SyncRotAngleX = false;
                        networkTransform.SyncRotAngleY = true;
                        networkTransform.SyncRotAngleZ = false;
                        break;
                    case SimplifyXREnums.AxisChoices.ZAxis:
                        networkTransform.SyncRotAngleX = false;
                        networkTransform.SyncRotAngleY = false;
                        networkTransform.SyncRotAngleZ = true;
                        break;
                    case SimplifyXREnums.AxisChoices.X_YAxis:
                        networkTransform.SyncRotAngleX = true;
                        networkTransform.SyncRotAngleY = true;
                        networkTransform.SyncRotAngleZ = false;
                        break;
                    case SimplifyXREnums.AxisChoices.X_ZAxis:
                        networkTransform.SyncRotAngleX = true;
                        networkTransform.SyncRotAngleY = false;
                        networkTransform.SyncRotAngleZ = true;
                        break;
                    case SimplifyXREnums.AxisChoices.Y_ZAxis:
                        networkTransform.SyncRotAngleX = false;
                        networkTransform.SyncRotAngleY = true;
                        networkTransform.SyncRotAngleZ = true;
                        break;
                    case SimplifyXREnums.AxisChoices.X_Y_ZAxis:
                        networkTransform.SyncRotAngleX = true;
                        networkTransform.SyncRotAngleY = true;
                        networkTransform.SyncRotAngleZ = true;
                        break;
                    case SimplifyXREnums.AxisChoices.None:
                        networkTransform.SyncRotAngleX = false;
                        networkTransform.SyncRotAngleY = false;
                        networkTransform.SyncRotAngleZ = false;
                        break;
                    default:
                        networkTransform.SyncRotAngleX = true;
                        networkTransform.SyncRotAngleY = true;
                        networkTransform.SyncRotAngleZ = true;
                        break;
                }

                switch (SyncScale)
                {
                    case SimplifyXREnums.AxisChoices.XAxis:
                        networkTransform.SyncScaleX = true;
                        networkTransform.SyncScaleY = false;
                        networkTransform.SyncScaleZ = false;
                        break;
                    case SimplifyXREnums.AxisChoices.YAxis:
                        networkTransform.SyncScaleX = false;
                        networkTransform.SyncScaleY = true;
                        networkTransform.SyncScaleZ = false;
                        break;
                    case SimplifyXREnums.AxisChoices.ZAxis:
                        networkTransform.SyncScaleX = false;
                        networkTransform.SyncScaleY = false;
                        networkTransform.SyncScaleZ = true;
                        break;
                    case SimplifyXREnums.AxisChoices.X_YAxis:
                        networkTransform.SyncScaleX = true;
                        networkTransform.SyncScaleY = true;
                        networkTransform.SyncScaleZ = false;
                        break;
                    case SimplifyXREnums.AxisChoices.X_ZAxis:
                        networkTransform.SyncScaleX = true;
                        networkTransform.SyncScaleY = false;
                        networkTransform.SyncScaleZ = true;
                        break;
                    case SimplifyXREnums.AxisChoices.Y_ZAxis:
                        networkTransform.SyncScaleX = false;
                        networkTransform.SyncScaleY = true;
                        networkTransform.SyncScaleZ = true;
                        break;
                    case SimplifyXREnums.AxisChoices.X_Y_ZAxis:
                        networkTransform.SyncScaleX = true;
                        networkTransform.SyncScaleY = true;
                        networkTransform.SyncScaleZ = true;
                        break;
                    case SimplifyXREnums.AxisChoices.None:
                        networkTransform.SyncScaleX = false;
                        networkTransform.SyncScaleY = false;
                        networkTransform.SyncScaleZ = false;
                        break;
                    default:
                        networkTransform.SyncScaleX = true;
                        networkTransform.SyncScaleY = true;
                        networkTransform.SyncScaleZ = true;
                        break;
                }
            }
        }

        void SetSpace(GameObject go)
        {
            if (go.GetComponent<NetworkTransform>() != null)
                go.GetComponent<NetworkTransform>().InLocalSpace = InLocalSpace;
        }

        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

        #region IChangeTransformAxis implmentation
        string IModify<IChangeTransformAxis>.ModifyObjectName { get { return "Change Transform Axis"; } }

        void IChangeTransformAxis.SetPositionX()
        {
            SyncPosition = SimplifyXREnums.AxisChoices.XAxis;
        }

        void IChangeTransformAxis.SetPositionY()
        {
            SyncPosition = SimplifyXREnums.AxisChoices.YAxis;
        }

        void IChangeTransformAxis.SetPositionZ()
        {
            SyncPosition = SimplifyXREnums.AxisChoices.ZAxis;
        }

        void IChangeTransformAxis.SetPositionXY()
        {
            SyncPosition = SimplifyXREnums.AxisChoices.X_YAxis;
        }

        void IChangeTransformAxis.SetPositionXZ()
        {
            SyncPosition = SimplifyXREnums.AxisChoices.X_ZAxis;
        }

        void IChangeTransformAxis.SetPositionYZ()
        {
            SyncPosition = SimplifyXREnums.AxisChoices.Y_ZAxis;
        }

        void IChangeTransformAxis.SetPositionXYZ()
        {
            SyncPosition = SimplifyXREnums.AxisChoices.X_Y_ZAxis;
        }

        void IChangeTransformAxis.SetPositionNone()
        {
            SyncPosition = SimplifyXREnums.AxisChoices.None;
        }


        void IChangeTransformAxis.SetRotationX()
        {
            SyncRotation = SimplifyXREnums.AxisChoices.XAxis;
        }

        void IChangeTransformAxis.SetRotationY()
        {
            SyncRotation = SimplifyXREnums.AxisChoices.YAxis;
        }

        void IChangeTransformAxis.SetRotationZ()
        {
            SyncRotation = SimplifyXREnums.AxisChoices.ZAxis;
        }

        void IChangeTransformAxis.SetRotationXY()
        {
            SyncRotation = SimplifyXREnums.AxisChoices.X_YAxis;
        }

        void IChangeTransformAxis.SetRotationXZ()
        {
            SyncRotation = SimplifyXREnums.AxisChoices.X_ZAxis;
        }

        void IChangeTransformAxis.SetRotationYZ()
        {
            SyncRotation = SimplifyXREnums.AxisChoices.Y_ZAxis;
        }

        void IChangeTransformAxis.SetRotationXYZ()
        {
            SyncRotation = SimplifyXREnums.AxisChoices.X_Y_ZAxis;
        }

        void IChangeTransformAxis.SetRotationNone()
        {
            SyncRotation = SimplifyXREnums.AxisChoices.None;
        }


        void IChangeTransformAxis.SetScaleX()
        {
            SyncScale = SimplifyXREnums.AxisChoices.XAxis;
        }

        void IChangeTransformAxis.SetScaleY()
        {
            SyncScale = SimplifyXREnums.AxisChoices.YAxis;
        }

        void IChangeTransformAxis.SetScaleZ()
        {
            SyncScale = SimplifyXREnums.AxisChoices.ZAxis;
        }

        void IChangeTransformAxis.SetScaleXY()
        {
            SyncScale = SimplifyXREnums.AxisChoices.X_YAxis;
        }

        void IChangeTransformAxis.SetScaleXZ()
        {
            SyncScale = SimplifyXREnums.AxisChoices.X_ZAxis;
        }

        void IChangeTransformAxis.SetScaleYZ()
        {
            SyncScale = SimplifyXREnums.AxisChoices.Y_ZAxis;
        }

        void IChangeTransformAxis.SetScaleXYZ()
        {
            SyncScale = SimplifyXREnums.AxisChoices.X_Y_ZAxis;
        }

        void IChangeTransformAxis.SetScaleNone()
        {
            SyncScale = SimplifyXREnums.AxisChoices.None;
        }
        #endregion
    }
}


#endif