using UnityEngine;
using SanAndreasUnity.Utilities;
using SanAndreasUnity.Behaviours.Vehicles;

namespace SanAndreasUnity.Behaviours.Peds.States
{

	public class BaseVehicleState : BaseScriptState, IVehicleState
	{
		private Vehicle m_currentVehicle;
		public Vehicle CurrentVehicle { get { return m_currentVehicle; } protected set { m_currentVehicle = value; } }

		public Vehicle.Seat CurrentVehicleSeat { get => this.CurrentVehicle != null ? this.CurrentVehicle.GetSeat(this.CurrentVehicleSeatAlignment) : null; }
		public Vehicle.SeatAlignment CurrentVehicleSeatAlignment { get; protected set; }

		protected uint m_currentVehicleNetId = 0;



		public override void OnSwitchedStateByServer(byte[] data)
		{
			// we need to wait for end of frame, because vehicle may not be spawned yet
			//this.StartCoroutine(this.SwitchStateAtEndOfFrame(data));


			// check if this state was already activated
			// it can happen when, among other things, syncvar hooks get invoked twice when creating the ped
			if (this.IsActiveState)
				return;
			
			this.ReadNetworkData(data);

			m_ped.SwitchState(this.GetType());

			if (this.CurrentVehicle != null)
				this.OnVehicleAssigned();

		}

		protected void ReadNetworkData(byte[] data)
		{
			var reader = new Mirror.NetworkReader(data);
			this.ReadNetworkData(reader);
		}

		protected virtual void ReadNetworkData(Mirror.NetworkReader reader)
		{
			// first reset params
			this.CurrentVehicle = null;
			this.CurrentVehicleSeatAlignment = Vehicle.SeatAlignment.None;
			m_currentVehicleNetId = 0;

			// extract vehicle and seat from data

			int magicNumber = reader.ReadInt32();
			m_currentVehicleNetId = reader.ReadUInt32();
			this.CurrentVehicleSeatAlignment = (Vehicle.SeatAlignment) reader.ReadSByte();

			// assign current vehicle
			GameObject vehicleGo = Net.NetManager.GetNetworkObjectById(m_currentVehicleNetId);
			this.CurrentVehicle = vehicleGo != null ? vehicleGo.GetComponent<Vehicle>() : null;

			if (magicNumber != 123456789)
				Debug.LogErrorFormat("magicNumber {0}, m_currentVehicleNetId {1}, data size {2} - this should not happen", magicNumber, m_currentVehicleNetId, reader.Length);

		}

		public override byte[] GetAdditionalNetworkData()
		{
			var writer = new Mirror.NetworkWriter();
			this.GetAdditionalNetworkData(writer);
			return writer.ToArray();
		}

		protected virtual void GetAdditionalNetworkData(Mirror.NetworkWriter writer)
		{
			writer.Write((int)123456789);
			if (this.CurrentVehicle != null) {
				writer.Write((uint)this.CurrentVehicle.NetTransform.netId);
				writer.Write((sbyte)this.CurrentVehicleSeatAlignment);
			} else {
				writer.Write((uint)0);
				writer.Write((sbyte)Vehicle.SeatAlignment.None);
			}
		}

		System.Collections.IEnumerator SwitchStateAtEndOfFrame(byte[] data)
		{
			var oldState = m_ped.CurrentState;

			yield return new WaitForEndOfFrame();

			if (oldState != m_ped.CurrentState)
			{
				// state changed in the meantime
				// did server change it ? or syncvar hooks invoked twice ? either way, we should stop here

				// Debug.LogFormat("state changed in the meantime, old: {0}, new: {1}", oldState != null ? oldState.GetType().Name : "",
				// 	m_ped.CurrentState != null ? m_ped.CurrentState.GetType().Name : "");
				yield break;
			}

			// read current vehicle here - it should've been spawned by now
			this.ReadNetworkData(data);

		//	Debug.LogFormat("Switching to state {0}, vehicle: {1}, seat: {2}", this.GetType().Name, this.CurrentVehicle, this.CurrentVehicleSeat);

			// now we can enter this state
			m_ped.SwitchState(this.GetType());
		}

		protected virtual void OnVehicleAssigned()
		{

		}

		public static void PreparePedForVehicle(Ped ped, Vehicle vehicle, Vehicle.Seat seat)
		{

			seat.OccupyingPed = ped;

			ped.characterController.enabled = false;


			ped.transform.SetParent(seat.Parent);
			ped.transform.localPosition = Vector3.zero;
			ped.transform.localRotation = Quaternion.identity;

			ped.PlayerModel.IsInVehicle = true;

			if (!VehicleManager.Instance.syncPedTransformWhileInVehicle) {
				if (ped.NetTransform != null)
					ped.NetTransform.enabled = false;
			}

			F.RunExceptionSafe( () => vehicle.OnPedPreparedForVehicle(ped, seat) );

		}

		protected void Cleanup()
		{
			if (!m_ped.IsInVehicle)
			{
				m_ped.characterController.enabled = true;
				m_ped.transform.SetParent(null, true);
				m_model.IsInVehicle = false;
				// enable network transform
				if (m_ped.NetTransform != null)
					m_ped.NetTransform.enabled = true;
				if (this.CurrentVehicle != null)
					F.RunExceptionSafe( () => this.CurrentVehicle.OnPedRemovedFromVehicle(m_ped, this.CurrentVehicleSeat) );
			}

			if (this.CurrentVehicleSeat != null && this.CurrentVehicleSeat.OccupyingPed == m_ped)
				this.CurrentVehicleSeat.OccupyingPed = null;

			this.CurrentVehicle = null;
			this.CurrentVehicleSeatAlignment = Vehicle.SeatAlignment.None;
			m_currentVehicleNetId = 0;

		}


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

			if (!this.IsActiveState)
				return;
			
			if (Net.NetStatus.IsClientOnly)
			{
				if (null == this.CurrentVehicle)
				{
					// check if vehicle was spawned in the meantime
					GameObject vehicleGo = Net.NetManager.GetNetworkObjectById(m_currentVehicleNetId);
					if (vehicleGo != null)
					{
						// vehicle is spawned
						this.CurrentVehicle = vehicleGo.GetComponent<Vehicle>();
						this.OnVehicleAssigned();
					}
				}
			}

		}

		protected override void UpdateHeading()
		{
			
		}

		protected override void UpdateRotation()
		{
			
		}

		protected override void UpdateMovement()
		{
			
		}

		protected override void ConstrainRotation ()
		{
			
		}

		public bool CanEnterVehicle (Vehicle vehicle, Vehicle.SeatAlignment seatAlignment)
		{
			if (m_ped.IsInVehicle)
				return false;

			// this should be removed
			if (m_ped.IsAiming || m_ped.WeaponHolder.IsFiring)
				return false;

			var seat = vehicle.GetSeat (seatAlignment);
			if (null == seat)
				return false;

			// check if specified seat is taken
			if (seat.IsTaken)
				return false;

			// everything is ok, we can enter vehicle

			return true;
		}

		public override void KillPed()
		{
			if (m_ped.CurrentVehicle != null && m_ped.CurrentVehicle.ExplodedThisFrame)
			{
				base.KillPed();
				return;
			}
			
			// don't detach ragdoll, because it will collide with vehicle and vehicle will fly away
			Object.Destroy(m_ped.gameObject);
		}

	}

}
