using System.Collections.Generic;
using UnityEngine;
using Mirror;
using System.Linq;
using SanAndreasUnity.Utilities;
using SanAndreasUnity.Net;

namespace SanAndreasUnity.Behaviours
{
    public partial class Ped
    {
        public NetworkTransform NetTransform { get; private set; }

        [Range(1f / 60f, 0.5f)] [SerializeField] float m_inputSendInterval = 1f / 30f;
        float m_timeSinceSentInput = 0f;

        [SyncVar] GameObject m_net_playerOwnerGameObject;
        internal GameObject NetPlayerOwnerGameObject { set { m_net_playerOwnerGameObject = value; } }
        public Player PlayerOwner => Player.GetOwningPlayer(this);

        [SyncVar(hook=nameof(Net_OnIdChanged))] int m_net_pedId = 0;

        struct StateSyncData
        {
            public string state;
            public string additionalData;
        }
        //[SyncVar(hook=nameof(Net_OnStateChanged))] StateSyncData m_net_stateData;
        [SyncVar] string m_net_additionalStateData = "";
        [SyncVar(hook=nameof(Net_OnStateChanged))] string m_net_state = "";
        //[SyncVar] Weapon m_net_weapon = null;
        
        public static int NumStateChangesReceived { get; private set; }

        public class SyncDictionaryStringUint : Mirror.SyncDictionary<string, uint> { }

        public SyncDictionaryStringUint syncDictionaryStringUint = new SyncDictionaryStringUint();

        [SyncVar] Vector3 m_net_movementInput;
        [SyncVar] Vector3 m_net_heading;

        [SyncVar] float m_net_health;

        //[SyncVar(hook=nameof(Net_OnWeaponChanged))] GameObject m_net_weaponGameObject;
        [SyncVar(hook=nameof(Net_OnWeaponChanged))] int m_net_currentWeaponSlot;

        [SyncVar] internal Vector3 m_net_aimDir;

        public Vector3 NetFirePos { get; set; }
        public Vector3 NetFireDir { get; set; }



        void Awake_Net()
        {
            this.NetTransform = this.GetComponentOrThrow<NetworkTransform>();
        }

        public override void OnStartClient()
        {
            base.OnStartClient();

            if (this.isServer)
                return;

            // owner player sync var should point to created game object, because player's game object is always created before
            // ped's game object
            // assign var in Player script
            if (m_net_playerOwnerGameObject != null)
                m_net_playerOwnerGameObject.GetComponent<Player>().OwnedPed = this;

            this.TryToLoadNewModel(m_net_pedId);

            // switch weapon
            F.RunExceptionSafe( () => this.WeaponHolder.SwitchWeapon(m_net_currentWeaponSlot) );

            this.ChangeStateBasedOnSyncData(new StateSyncData(){state = m_net_state, additionalData = m_net_additionalStateData});
        }

        void Start_Net()
        {
            this.ApplySyncRate(PedManager.Instance.pedSyncRate);
        }

        public void ApplySyncRate(float newSyncRate)
        {
            float newSyncInterval = 1.0f / newSyncRate;

            foreach (var comp in this.GetComponents<NetworkBehaviour>())
                comp.syncInterval = newSyncInterval;

            // also change it for NetworkTransform, because it can be disabled
            if (this.NetTransform != null)
                this.NetTransform.syncInterval = newSyncInterval;
        }

        void Update_Net()
        {
            
            if (NetStatus.IsServer)
            {
                // update syncvars

                if (this.PedDef != null && this.PedDef.Id != m_net_pedId)
                    m_net_pedId = this.PedDef.Id;

                string newStateName = this.CurrentState != null ? this.CurrentState.GetType().Name : "";
                if (newStateName != m_net_state)
                {
                    // state changed

                    //Debug.LogFormat("Updating state syncvar - ped {0}, new state {1}, old state {2}", this.netId, newStateName, m_net_state);

                    //m_net_stateData = new StateSyncData();

                    // obtain additional data from state
                    byte[] data = this.CurrentState != null ? this.CurrentState.GetAdditionalNetworkData() : null;
                    // assign additional data
                    m_net_additionalStateData = data != null ? System.Convert.ToBase64String(data) : "";
                    // assign new state
                    m_net_state = newStateName;
                }

                if (m_net_movementInput != this.Movement)
                    m_net_movementInput = this.Movement;

                if (m_net_heading != this.Heading)
                    m_net_heading = this.Heading;

                if (m_net_health != this.Health)
                    m_net_health = this.Health;

                Vector3 aimDir = this.AimDirection;
                if (m_net_aimDir != aimDir)
                    m_net_aimDir = aimDir;

                if (this.WeaponHolder.CurrentWeaponSlot != m_net_currentWeaponSlot)
                {
                    m_net_currentWeaponSlot = this.WeaponHolder.CurrentWeaponSlot;
                }

            }
            else
            {
                // apply syncvars

                if (!this.IsControlledByLocalPlayer)
                {
                    this.Movement = m_net_movementInput;
                    this.Heading = m_net_heading;
                }

                this.Health = m_net_health;

            }
            
            // send input to server
            if (!NetStatus.IsServer && this.IsControlledByLocalPlayer && PedSync.Local != null)
            {
                m_timeSinceSentInput += Time.unscaledDeltaTime;
                if (m_timeSinceSentInput >= m_inputSendInterval)
                {
                    m_timeSinceSentInput = 0f;
                    PedSync.Local.SendInput();
                }
            }
            
        }

        void FixedUpdate_Net()
        {
            
        }

        void TryToLoadNewModel(int newId)
        {

            if (this.PedDef != null && this.PedDef.Id == newId) // same id
                return;

            if (newId > 0)
                F.RunExceptionSafe( () => this.PlayerModel.Load(newId) );
            
        }

        void Net_OnIdChanged(int newId)
        {
            //Debug.LogFormat("ped (net id {0}) changed model id to {1}", this.netId, newId);
            
            if (this.isServer)
                return;
            
            this.TryToLoadNewModel(newId);
        }

        void Net_OnStateChanged(string newStateName)
        {
            if (this.isServer)
                return;

            StateSyncData newStateData = new StateSyncData(){state = newStateName, additionalData = m_net_additionalStateData};

            //Debug.LogFormat("Net_OnStateChanged(): ped {0} changed state to {1}", this.netId, newStateData.state);

            NumStateChangesReceived ++;

            this.ChangeStateBasedOnSyncData(newStateData);

        }

        void ChangeStateBasedOnSyncData(StateSyncData newStateData)
        {

            if (string.IsNullOrEmpty(newStateData.state))
            {
                // don't do anything, this only happens when creating the ped
                return;
            }

            // forcefully change the state

            F.RunExceptionSafe( () => {
                var newState = this.States.FirstOrDefault(state => state.GetType().Name == newStateData.state);
                if (null == newState)
                {
                    Debug.LogErrorFormat("New ped state '{0}' could not be found", newStateData.state);
                }
                else
                {
                    //Debug.LogFormat("Switching state based on sync data - ped: {0}, state: {1}", this.netId, newState.GetType().Name);
                    byte[] data = string.IsNullOrEmpty(newStateData.additionalData) ? null : System.Convert.FromBase64String(newStateData.additionalData);
                    newState.OnSwitchedStateByServer(data);
                }
            });
            
        }

        void Net_OnWeaponChanged(int newSlot)
        {

            if (NetStatus.IsServer)
                return;

            F.RunExceptionSafe( () => {

                //Debug.LogFormat("weapon slot changed for ped {0} to {1}", this.DescriptionForLogging, newSlot);

                if (this.CurrentState != null)
                {
                    //this.CurrentState.OnChangedWeaponByServer(newWeaponGameObject != null ? newWeaponGameObject.GetComponent<Weapon>() : null);
                    this.CurrentState.OnChangedWeaponByServer(newSlot);
                }

            });
            
        }

    }
}