/*
 * Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

using Framework.Constants;
using Framework.Dynamic;
using Game.BattleGrounds;
using Game.DataStorage;
using Game.Movement;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;

namespace Game.Entities
{
    public class Vehicle : ITransport, IDisposable
    {
        public Vehicle(Unit unit, VehicleRecord vehInfo, uint creatureEntry)
        {
            _me = unit;
            _vehicleInfo = vehInfo;
            _creatureEntry = creatureEntry;
            _status = Status.None;
            _lastShootPos = new Position();

            for (uint i = 0; i < SharedConst.MaxVehicleSeats; ++i)
            {
                uint seatId = _vehicleInfo.SeatID[i];
                if (seatId != 0)
                {
                    VehicleSeatRecord veSeat = CliDB.VehicleSeatStorage.LookupByKey(seatId);
                    if (veSeat != null)
                    {
                        Seats.Add((sbyte)i, new VehicleSeat(veSeat));
                        if (veSeat.CanEnterOrExit())
                            ++UsableSeatNum;
                    }
                }
            }

            // Set or remove correct flags based on available seats. Will overwrite db data (if wrong).
            if (UsableSeatNum != 0)
                _me.SetFlag64(UnitFields.NpcFlags, (_me.IsTypeId(TypeId.Player) ? NPCFlags.PlayerVehicle : NPCFlags.SpellClick));
            else
                _me.RemoveFlag64(UnitFields.NpcFlags, (_me.IsTypeId(TypeId.Player) ? NPCFlags.PlayerVehicle : NPCFlags.SpellClick));

            InitMovementInfoForBase();
        }

        public virtual void Dispose()
        {
            /// @Uninstall must be called before this.
            Contract.Assert(_status == Status.UnInstalling);
            foreach (var pair in Seats)
                Contract.Assert(pair.Value.IsEmpty());
        }

        public void Install()
        {
            if (_me.IsTypeId(TypeId.Unit))
            {
                PowerDisplayRecord powerDisplay = CliDB.PowerDisplayStorage.LookupByKey(_vehicleInfo.PowerDisplayID[0]);
                if (powerDisplay != null)
                    _me.setPowerType((PowerType)powerDisplay.PowerType);
                else if (_me.GetClass() == Class.Rogue)
                    _me.setPowerType(PowerType.Energy);
            }

            _status = Status.Installed;
            if (GetBase().IsTypeId(TypeId.Unit))
                Global.ScriptMgr.OnInstall(this);
        }

        public void InstallAllAccessories(bool evading)
        {
            if (GetBase().IsTypeId(TypeId.Player) || !evading)
                RemoveAllPassengers();   // We might have aura's saved in the DB with now invalid casters - remove

            List<VehicleAccessory> accessories = Global.ObjectMgr.GetVehicleAccessoryList(this);
            if (accessories == null)
                return;

            foreach (var acc in accessories)
                if (!evading || acc.IsMinion)  // only install minions on evade mode
                    InstallAccessory(acc.AccessoryEntry, acc.SeatId, acc.IsMinion, acc.SummonedType, acc.SummonTime);
        }

        public void Uninstall()
        {
            // @Prevent recursive uninstall call. (Bad script in OnUninstall/OnRemovePassenger/PassengerBoarded hook.)
            if (_status == Status.UnInstalling && !GetBase().HasUnitTypeMask(UnitTypeMask.Minion))
            {
                Log.outError(LogFilter.Vehicle, "Vehicle GuidLow: {0}, Entry: {1} attempts to uninstall, but already has STATUS_UNINSTALLING! " +
                    "Check Uninstall/PassengerBoarded script hooks for errors.", _me.GetGUID().ToString(), _me.GetEntry());
                return;
            }

            _status = Status.UnInstalling;
            Log.outDebug(LogFilter.Vehicle, "Vehicle.Uninstall Entry: {0}, GuidLow: {1}", _creatureEntry, _me.GetGUID().ToString());
            RemoveAllPassengers();

            if (GetBase().IsTypeId(TypeId.Unit))
                Global.ScriptMgr.OnUninstall(this);
        }

        public void Reset(bool evading = false)
        {
            if (!GetBase().IsTypeId(TypeId.Unit))
                return;

            Log.outDebug(LogFilter.Vehicle, "Vehicle.Reset (Entry: {0}, GuidLow: {1}, DBGuid: {2})", GetCreatureEntry(), _me.GetGUID().ToString(), _me.ToCreature().GetSpawnId());

            ApplyAllImmunities();
            InstallAllAccessories(evading);

            Global.ScriptMgr.OnReset(this);
        }

        void ApplyAllImmunities()
        {
            // This couldn't be done in DB, because some spells have MECHANIC_NONE

            // Vehicles should be immune on Knockback ...
            _me.ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.KnockBack, true);
            _me.ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.KnockBackDest, true);

            // Mechanical units & vehicles ( which are not Bosses, they have own immunities in DB ) should be also immune on healing ( exceptions in switch below )
            if (_me.IsTypeId(TypeId.Unit) && _me.ToCreature().GetCreatureTemplate().CreatureType == CreatureType.Mechanical && !_me.ToCreature().isWorldBoss())
            {
                // Heal & dispel ...
                _me.ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.Heal, true);
                _me.ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.HealPct, true);
                _me.ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.Dispel, true);
                _me.ApplySpellImmune(0, SpellImmunity.State, AuraType.PeriodicHeal, true);

                // ... Shield & Immunity grant spells ...
                _me.ApplySpellImmune(0, SpellImmunity.State, AuraType.SchoolImmunity, true);
                _me.ApplySpellImmune(0, SpellImmunity.State, AuraType.ModUnattackable, true);
                _me.ApplySpellImmune(0, SpellImmunity.State, AuraType.SchoolAbsorb, true);
                _me.ApplySpellImmune(0, SpellImmunity.Mechanic, Mechanics.Shield, true);
                _me.ApplySpellImmune(0, SpellImmunity.Mechanic, Mechanics.Immune_Shield, true);

                // ... Resistance, Split damage, Change stats ...
                _me.ApplySpellImmune(0, SpellImmunity.State, AuraType.DamageShield, true);
                _me.ApplySpellImmune(0, SpellImmunity.State, AuraType.SplitDamagePct, true);
                _me.ApplySpellImmune(0, SpellImmunity.State, AuraType.ModResistance, true);
                _me.ApplySpellImmune(0, SpellImmunity.State, AuraType.ModStat, true);
                _me.ApplySpellImmune(0, SpellImmunity.State, AuraType.ModDamagePercentTaken, true);
            }

            // Different immunities for vehicles goes below
            switch (GetVehicleInfo().Id)
            {
                // code below prevents a bug with movable cannons
                case 160: // Strand of the Ancients
                case 244: // Wintergrasp
                case 452: // Isle of Conquest
                case 510: // Isle of Conquest
                case 543: // Isle of Conquest
                    _me.SetControlled(true, UnitState.Root);
                    // why we need to apply this? we can simple add immunities to slow mechanic in DB
                    _me.ApplySpellImmune(0, SpellImmunity.State, AuraType.ModDecreaseSpeed, true);
                    break;
                default:
                    break;
            }
        }

        public void RemoveAllPassengers()
        {
            Log.outDebug(LogFilter.Vehicle, "Vehicle.RemoveAllPassengers. Entry: {0}, GuidLow: {1}", _creatureEntry, _me.GetGUID().ToString());

            // Setting to_Abort to true will cause @VehicleJoinEvent.Abort to be executed on next @Unit.UpdateEvents call
            // This will properly "reset" the pending join process for the passenger.
            {
                // Update vehicle in every pending join event - Abort may be called after vehicle is deleted
                Vehicle eventVehicle = _status != Status.UnInstalling ? this : null;

                while (!_pendingJoinEvents.Empty())
                {
                    VehicleJoinEvent e = _pendingJoinEvents.First();
                    e.ScheduleAbort();
                    e.Target = eventVehicle;
                    _pendingJoinEvents.Remove(_pendingJoinEvents.First());
                }
            }

            // Passengers always cast an aura with SPELL_AURA_CONTROL_VEHICLE on the vehicle
            // We just remove the aura and the unapply handler will make the target leave the vehicle.
            // We don't need to iterate over Seats
            _me.RemoveAurasByType(AuraType.ControlVehicle);
        }

        public bool HasEmptySeat(sbyte seatId)
        {
            var seat = Seats.LookupByKey(seatId);
            if (seat == null)
                return false;
            return seat.IsEmpty();
        }

        public Unit GetPassenger(sbyte seatId)
        {
            var seat = Seats.LookupByKey(seatId);
            if (seat == null)
                return null;

            return Global.ObjAccessor.GetUnit(GetBase(), seat.Passenger.Guid);
        }

        public VehicleSeat GetNextEmptySeat(sbyte seatId, bool next)
        {
            var seat = Seats.LookupByKey(seatId);
            if (seat == null)
                return seat;

            foreach (var sea in Seats)
            {
                if (!seat.IsEmpty() || (!seat.SeatInfo.CanEnterOrExit() && !seat.SeatInfo.IsUsableByOverride()))
                    continue;

                seat = sea.Value;
            }

            return seat;
        }

        void InstallAccessory(uint entry, sbyte seatId, bool minion, byte type, uint summonTime)
        {
            // @Prevent adding accessories when vehicle is uninstalling. (Bad script in OnUninstall/OnRemovePassenger/PassengerBoarded hook.)
            
            if (_status == Status.UnInstalling)
            {
                Log.outError(LogFilter.Vehicle, "Vehicle ({0}, Entry: {1}) attempts to install accessory (Entry: {2}) on seat {3} with STATUS_UNINSTALLING! " +
            "Check Uninstall/PassengerBoarded script hooks for errors.", _me.GetGUID().ToString(), GetCreatureEntry(), entry, seatId);
                return;
            }

            Log.outDebug(LogFilter.Vehicle, "Vehicle ({0}, Entry {1}): installing accessory (Entry: {2}) on seat: {3}", _me.GetGUID().ToString(), GetCreatureEntry(), entry, seatId);

            TempSummon accessory = _me.SummonCreature(entry, _me, (TempSummonType)type, summonTime);
            Contract.Assert(accessory);

            if (minion)
                accessory.AddUnitTypeMask(UnitTypeMask.Accessory);

            _me.HandleSpellClick(accessory, seatId);

            // If for some reason adding accessory to vehicle fails it will unsummon in
            // @VehicleJoinEvent.Abort
        }

        public bool AddPassenger(Unit unit, sbyte seatId)
        {
            // @Prevent adding passengers when vehicle is uninstalling. (Bad script in OnUninstall/OnRemovePassenger/PassengerBoarded hook.)
            if (_status == Status.UnInstalling)
            {
                Log.outError(LogFilter.Vehicle, "Passenger GuidLow: {0}, Entry: {1}, attempting to board vehicle GuidLow: {2}, Entry: {3} during uninstall! SeatId: {4}",
                    unit.GetGUID().ToString(), unit.GetEntry(), _me.GetGUID().ToString(), _me.GetEntry(), seatId);
                return false;
            }

            Log.outDebug(LogFilter.Vehicle, "Unit {0} scheduling enter vehicle (entry: {1}, vehicleId: {2}, guid: {3} (dbguid: {4}) on seat {5}",
                unit.GetName(), _me.GetEntry(), _vehicleInfo.Id, _me.GetGUID().ToString(),
                (_me.IsTypeId(TypeId.Unit) ? _me.ToCreature().GetSpawnId() : 0), seatId);

            // The seat selection code may kick other passengers off the vehicle.
            // While the validity of the following may be arguable, it is possible that when such a passenger
            // exits the vehicle will dismiss. That's why the actual adding the passenger to the vehicle is scheduled
            // asynchronously, so it can be cancelled easily in case the vehicle is uninstalled meanwhile.
            VehicleJoinEvent e = new VehicleJoinEvent(this, unit);
            unit.m_Events.AddEvent(e, unit.m_Events.CalculateTime(0));

            KeyValuePair<sbyte, VehicleSeat> seat = new KeyValuePair<sbyte, VehicleSeat>();
            if (seatId < 0) // no specific seat requirement
            {
                foreach (var _seat in Seats)
                {
                    seat = _seat;
                    if (seat.Value.IsEmpty() && (_seat.Value.SeatInfo.CanEnterOrExit() || _seat.Value.SeatInfo.IsUsableByOverride()))
                        break;
                }

                if (seat.Value == null) // no available seat
                {
                    e.ScheduleAbort();
                    return false;
                }

                e.Seat = seat;
                _pendingJoinEvents.Add(e);
            }
            else
            {
                seat = new KeyValuePair<sbyte, VehicleSeat>(seatId, Seats.LookupByKey(seatId));
                if (seat.Value == null)
                {
                    e.ScheduleAbort();
                    return false;
                }

                e.Seat = seat;
                _pendingJoinEvents.Add(e);
                if (!seat.Value.IsEmpty())
                {
                    Unit passenger = Global.ObjAccessor.GetUnit(GetBase(), seat.Value.Passenger.Guid);
                    Contract.Assert(passenger != null);
                    passenger.ExitVehicle();
                }

                Contract.Assert(seat.Value.IsEmpty());
            }

            return true;
        }

        public Vehicle RemovePassenger(Unit unit)
        {
            if (unit.GetVehicle() != this)
                return null;

            var seat = GetSeatKeyValuePairForPassenger(unit);
            Contract.Assert(seat.Value != null);

            Log.outDebug( LogFilter.Vehicle, "Unit {0} exit vehicle entry {1} id {2} dbguid {3} seat {4}",
                unit.GetName(), _me.GetEntry(), _vehicleInfo.Id, _me.GetGUID().ToString(), seat.Key);

            if (seat.Value.SeatInfo.CanEnterOrExit() && ++UsableSeatNum != 0)
                _me.SetFlag64(UnitFields.NpcFlags, (_me.IsTypeId(TypeId.Player) ? NPCFlags.PlayerVehicle : NPCFlags.SpellClick));

            // Remove UNIT_FLAG_NOT_SELECTABLE if passenger did not have it before entering vehicle
            if (seat.Value.SeatInfo.Flags[0].HasAnyFlag((uint)VehicleSeatFlags.PassengerNotSelectable) && !seat.Value.Passenger.IsUnselectable)
                unit.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable);

            seat.Value.Passenger.Reset();

            if (_me.IsTypeId(TypeId.Unit) && unit.IsTypeId(TypeId.Player) && seat.Value.SeatInfo.Flags[0].HasAnyFlag((uint)VehicleSeatFlags.CanControl))
                _me.RemoveCharmedBy(unit);

            if (_me.IsInWorld)
                unit.m_movementInfo.ResetTransport();

            // only for flyable vehicles
            if (unit.IsFlying())
                _me.CastSpell(unit, SharedConst.VehicleSpellParachute, true);

            if (_me.IsTypeId(TypeId.Unit) && _me.ToCreature().IsAIEnabled)
                _me.ToCreature().GetAI().PassengerBoarded(unit, seat.Key, false);

            if (GetBase().IsTypeId(TypeId.Unit))
               Global.ScriptMgr.OnRemovePassenger(this, unit);

            unit.SetVehicle(null);
            return this;
        }

        public void RelocatePassengers()
        {
            Contract.Assert(_me.GetMap() != null);

            List<Tuple<Unit, Position>> seatRelocation = new List<Tuple<Unit, Position>>();

            // not sure that absolute position calculation is correct, it must depend on vehicle pitch angle
            foreach (var pair in Seats)
            {
                Unit passenger = Global.ObjAccessor.GetUnit(GetBase(), pair.Value.Passenger.Guid);
                if (passenger != null)
                {
                    Contract.Assert(passenger.IsInWorld);

                    float px, py, pz, po;
                    passenger.m_movementInfo.transport.pos.GetPosition(out px, out py, out pz, out po);
                    CalculatePassengerPosition(ref px, ref py, ref pz, ref po);

                    seatRelocation.Add(Tuple.Create(passenger, new Position(px, py, pz, po)));
                }
            }

            foreach (var pair in seatRelocation)
                pair.Item1.UpdatePosition(pair.Item2);
        }

        public bool IsVehicleInUse()
        {
            foreach (var pair in Seats)
                if (!pair.Value.IsEmpty())
                    return true;

            return false;
        }

        void InitMovementInfoForBase()
        {
            VehicleFlags vehicleFlags = (VehicleFlags)GetVehicleInfo().Flags;

            if (vehicleFlags.HasAnyFlag(VehicleFlags.NoStrafe))
                _me.AddUnitMovementFlag2(MovementFlag2.NoStrafe);
            if (vehicleFlags.HasAnyFlag(VehicleFlags.NoJumping))
                _me.AddUnitMovementFlag2(MovementFlag2.NoJumping);
            if (vehicleFlags.HasAnyFlag(VehicleFlags.Fullspeedturning))
                _me.AddUnitMovementFlag2(MovementFlag2.FullSpeedTurning);
            if (vehicleFlags.HasAnyFlag(VehicleFlags.AllowPitching))
                _me.AddUnitMovementFlag2(MovementFlag2.AlwaysAllowPitching);
            if (vehicleFlags.HasAnyFlag(VehicleFlags.Fullspeedpitching))
                _me.AddUnitMovementFlag2(MovementFlag2.FullSpeedPitching);
        }

        public VehicleSeatRecord GetSeatForPassenger(Unit passenger)
        {
            foreach (var pair in Seats)
                if (pair.Value.Passenger.Guid == passenger.GetGUID())
                    return pair.Value.SeatInfo;

            return null;
        }

        KeyValuePair<sbyte, VehicleSeat> GetSeatKeyValuePairForPassenger(Unit passenger)
        {
            foreach (var pair in Seats)
                if (pair.Value.Passenger.Guid == passenger.GetGUID())
                    return pair;

            return Seats.Last();
        }

        public byte GetAvailableSeatCount()
        {
            byte ret = 0;
            foreach (var pair in Seats)
                if (pair.Value.IsEmpty() && (pair.Value.SeatInfo.CanEnterOrExit() || pair.Value.SeatInfo.IsUsableByOverride()))
                    ++ret;

            return ret;
        }

        public void CalculatePassengerPosition(ref float x, ref float y, ref float z, ref float o)
        {
            TransportPosHelper.CalculatePassengerPosition(ref x, ref y, ref z, ref o, 
                GetBase().GetPositionX(), GetBase().GetPositionY(), 
                GetBase().GetPositionZ(), GetBase().GetOrientation());
        }

        public void CalculatePassengerOffset(ref float x, ref float y, ref float z, ref float o)
        {
            TransportPosHelper.CalculatePassengerOffset(ref x, ref y, ref z, ref o,
                GetBase().GetPositionX(), GetBase().GetPositionY(),
                GetBase().GetPositionZ(), GetBase().GetOrientation());
        }

        public void RemovePendingEvent(VehicleJoinEvent e)
        {
            foreach (var Event in _pendingJoinEvents)
            {
                if (Event == e)
                {
                    _pendingJoinEvents.Remove(Event);
                    break;
                }
            }
        }

        public void RemovePendingEventsForSeat(sbyte seatId)
        {
            foreach (var Event in _pendingJoinEvents.ToList())
            {
                if (Event.Seat.Key == seatId)
                {
                    Event.ScheduleAbort();
                    _pendingJoinEvents.Remove(Event);
                }
            }
        }

        public void RemovePendingEventsForPassenger(Unit passenger)
        {
            foreach (var Event in _pendingJoinEvents.ToList())
            {
                if (Event.Passenger == passenger)
                {
                    Event.ScheduleAbort();
                    _pendingJoinEvents.Remove(Event);
                }
            }
        }

        public Unit GetBase() { return _me; }
        public VehicleRecord GetVehicleInfo() { return _vehicleInfo; }
        public uint GetCreatureEntry() { return _creatureEntry; }

        public void SetLastShootPos(Position pos) { _lastShootPos.Relocate(pos); }
        Position GetLastShootPos() { return _lastShootPos; }

        Unit _me;
        VehicleRecord _vehicleInfo;                   //< DBC data for vehicle
        List<ulong> vehiclePlayers = new List<ulong>();

        uint _creatureEntry;                              //< Can be different than the entry of _me in case of players
        Status _status;                                     //< Internal variable for sanity checks
        Position _lastShootPos;

        List<VehicleJoinEvent> _pendingJoinEvents = new List<VehicleJoinEvent>();
        public Dictionary<sbyte, VehicleSeat> Seats = new Dictionary<sbyte, VehicleSeat>();
        public uint UsableSeatNum;    //< Number of seats that match VehicleSeatEntry.UsableByPlayer, used for proper display flags

        public static implicit operator bool(Vehicle vehicle)
        {
            return vehicle != null;
        }

        public enum Status
        {
            None,
            Installed,
            UnInstalling,
        }
    }

    public class VehicleJoinEvent : BasicEvent
    {
        public VehicleJoinEvent(Vehicle v, Unit u)
        {
            Target = v;
            Passenger = u;
            Seat = Target.Seats.Last();
        }

        public override bool Execute(ulong e_time, uint p_time)
        {
            Contract.Assert(Passenger.IsInWorld);
            Contract.Assert(Target != null && Target.GetBase().IsInWorld);
            Contract.Assert(Target.GetBase().HasAuraTypeWithCaster(AuraType.ControlVehicle, Passenger.GetGUID()));

            Target.RemovePendingEventsForSeat(Seat.Key);
            Target.RemovePendingEventsForPassenger(Passenger);

            Passenger.SetVehicle(Target);
            Seat.Value.Passenger.Guid = Passenger.GetGUID();
            Seat.Value.Passenger.IsUnselectable = Passenger.HasFlag(UnitFields.Flags, UnitFlags.NotSelectable);
            if (Seat.Value.SeatInfo.CanEnterOrExit())
            {
                Contract.Assert(Target.UsableSeatNum != 0);
                --Target.UsableSeatNum;
                if (Target.UsableSeatNum == 0)
                {
                    if (Target.GetBase().IsTypeId(TypeId.Player))
                        Target.GetBase().RemoveFlag64(UnitFields.NpcFlags, NPCFlags.PlayerVehicle);
                    else
                        Target.GetBase().RemoveFlag64(UnitFields.NpcFlags, NPCFlags.SpellClick);
                }
            }

            Passenger.InterruptNonMeleeSpells(false);
            Passenger.RemoveAurasByType(AuraType.Mounted);

            VehicleSeatRecord veSeat = Seat.Value.SeatInfo;

            Player player = Passenger.ToPlayer();
            if (player != null)
            {
                // drop flag
                Battleground bg = player.GetBattleground();
                if (bg)
                    bg.EventPlayerDroppedFlag(player);

                player.StopCastingCharm();
                player.StopCastingBindSight();
                player.SendOnCancelExpectedVehicleRideAura();
                if (!veSeat.Flags[1].HasAnyFlag((uint)VehicleSeatFlagsB.KeepPet))
                    player.UnsummonPetTemporaryIfAny();
            }

            if (Seat.Value.SeatInfo.Flags[0].HasAnyFlag((uint)VehicleSeatFlags.PassengerNotSelectable))
                Passenger.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable);

            Passenger.m_movementInfo.transport.pos.Relocate(veSeat.AttachmentOffset.X, veSeat.AttachmentOffset.Y, veSeat.AttachmentOffset.Z);
            Passenger.m_movementInfo.transport.time = 0;
            Passenger.m_movementInfo.transport.seat = Seat.Key;
            Passenger.m_movementInfo.transport.guid = Target.GetBase().GetGUID();

            if (Target.GetBase().IsTypeId(TypeId.Unit) && Passenger.IsTypeId(TypeId.Player) &&
                Seat.Value.SeatInfo.Flags[0].HasAnyFlag((uint)VehicleSeatFlags.CanControl))
                Contract.Assert(Target.GetBase().SetCharmedBy(Passenger, CharmType.Vehicle));  // SMSG_CLIENT_CONTROL

            Passenger.SendClearTarget();                            // SMSG_BREAK_TARGET
            Passenger.SetControlled(true, UnitState.Root);         // SMSG_FORCE_ROOT - In some cases we send SMSG_SPLINE_MOVE_ROOT here (for creatures)
            // also adds MOVEMENTFLAG_ROOT

            MoveSplineInit init = new MoveSplineInit(Passenger);
            init.DisableTransportPathTransformations();
            init.MoveTo(veSeat.AttachmentOffset.X, veSeat.AttachmentOffset.Y, veSeat.AttachmentOffset.Z, false, true);
            init.SetFacing(0.0f);
            init.SetTransportEnter();
            init.Launch();

            Creature creature = Target.GetBase().ToCreature();
            if (creature != null)
            {
                if (creature.IsAIEnabled)
                    creature.GetAI().PassengerBoarded(Passenger, Seat.Key, true);

                Global.ScriptMgr.OnAddPassenger(Target, Passenger, Seat.Key);

                // Actually quite a redundant hook. Could just use OnAddPassenger and check for unit typemask inside script.
                if (Passenger.HasUnitTypeMask(UnitTypeMask.Accessory))
                    Global.ScriptMgr.OnInstallAccessory(Target, Passenger.ToCreature());
            }

            return true;
        }

        public override void Abort(ulong e_time)
        {
            // Check if the Vehicle was already uninstalled, in which case all auras were removed already
            if (Target != null)
            {
                Log.outDebug(LogFilter.Vehicle, "Passenger GuidLow: {0}, Entry: {1}, board on vehicle GuidLow: {2}, Entry: {3} SeatId: {4} cancelled",
                    Passenger.GetGUID().ToString(), Passenger.GetEntry(), Target.GetBase().GetGUID().ToString(), Target.GetBase().GetEntry(), Seat.Key);

                /// Remove the pending event when Abort was called on the event directly
                Target.RemovePendingEvent(this);

                // @SPELL_AURA_CONTROL_VEHICLE auras can be applied even when the passenger is not (yet) on the vehicle.
                // When this code is triggered it means that something went wrong in @Vehicle.AddPassenger, and we should remove
                // the aura manually.
                Target.GetBase().RemoveAurasByType(AuraType.ControlVehicle, Passenger.GetGUID());
            }
            else
                Log.outDebug(LogFilter.Vehicle, "Passenger GuidLow: {0}, Entry: {1}, board on uninstalled vehicle SeatId: {2} cancelled",
                    Passenger.GetGUID().ToString(), Passenger.GetEntry(), Seat.Key);

            if (Passenger.IsInWorld && Passenger.HasUnitTypeMask(UnitTypeMask.Accessory))
                Passenger.ToCreature().DespawnOrUnsummon();
        }

        public Vehicle Target;
        public Unit Passenger;
        public KeyValuePair<sbyte, VehicleSeat> Seat;
    }

    public struct PassengerInfo
    {
        public ObjectGuid Guid;
        public bool IsUnselectable;

        public void Reset()
        {
            Guid = ObjectGuid.Empty;
            IsUnselectable = false;
        }
    }

    public class VehicleSeat
    {
        public VehicleSeat(VehicleSeatRecord seatInfo)
        {
            SeatInfo = seatInfo;
            Passenger.Reset();
        }

        public bool IsEmpty() { return Passenger.Guid.IsEmpty(); }

        public VehicleSeatRecord SeatInfo;
        public PassengerInfo Passenger;
    }

    public struct VehicleAccessory
    {
        public VehicleAccessory(uint entry, sbyte seatId, bool isMinion, byte summonType, uint summonTime)
        {
            AccessoryEntry = entry;
            IsMinion = isMinion;
            SummonTime = summonTime;
            SeatId = seatId;
            SummonedType = summonType;
        }
        public uint AccessoryEntry;
        public bool IsMinion;
        public uint SummonTime;
        public sbyte SeatId;
        public byte SummonedType;
    }
}
