/*
 * 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 Framework.GameMath;
using Game.Entities;
using Game.Movement;
using System.Collections.Generic;
using System.Linq;

namespace Game.Network.Packets
{
    public static class MovementExtensions
    {
        public static void ReadTransportInfo(WorldPacket data, ref MovementInfo.TransportInfo transportInfo)
        {
            transportInfo.guid = data.ReadPackedGuid();                 // Transport Guid
            transportInfo.pos.posX = data.ReadFloat();
            transportInfo.pos.posY = data.ReadFloat();
            transportInfo.pos.posZ = data.ReadFloat();
            transportInfo.pos.Orientation = data.ReadFloat();
            transportInfo.seat = data.ReadInt8();                 // VehicleSeatIndex
            transportInfo.time = data.ReadUInt32();                 // MoveTime

            bool hasPrevTime = data.HasBit();
            bool hasVehicleId = data.HasBit();

            if (hasPrevTime)
                transportInfo.prevTime = data.ReadUInt32();         // PrevMoveTime

            if (hasVehicleId)
                transportInfo.vehicleId = data.ReadUInt32();        // VehicleRecID
        }

        public static void WriteTransportInfo(WorldPacket data, MovementInfo.TransportInfo transportInfo)
        {
            bool hasPrevTime = transportInfo.prevTime != 0;
            bool hasVehicleId = transportInfo.vehicleId != 0;

            data.WritePackedGuid(transportInfo.guid);                 // Transport Guid
            data.WriteFloat(transportInfo.pos.GetPositionX());
            data.WriteFloat(transportInfo.pos.GetPositionY());
            data.WriteFloat(transportInfo.pos.GetPositionZ());
            data.WriteFloat(transportInfo.pos.GetOrientation());
            data.WriteInt8(transportInfo.seat);                 // VehicleSeatIndex
            data.WriteUInt32(transportInfo.time);                 // MoveTime

            data.WriteBit(hasPrevTime);
            data.WriteBit(hasVehicleId);
            data.FlushBits();

            if (hasPrevTime)
                data.WriteUInt32(transportInfo.prevTime);         // PrevMoveTime

            if (hasVehicleId)
                data.WriteUInt32(transportInfo.vehicleId);        // VehicleRecID
        }

        public static MovementInfo ReadMovementInfo(WorldPacket data)
        {
            var movementInfo = new MovementInfo();
            movementInfo.Guid = data.ReadPackedGuid();
            movementInfo.Time = data.ReadUInt32();
            movementInfo.Pos.posX = data.ReadFloat();
            movementInfo.Pos.posY = data.ReadFloat();
            movementInfo.Pos.posZ = data.ReadFloat();
            movementInfo.Pos.Orientation = data.ReadFloat();
            movementInfo.Pitch = data.ReadFloat();
            movementInfo.SplineElevation = data.ReadFloat();

            uint removeMovementForcesCount = data.ReadUInt32();

            uint moveIndex = data.ReadUInt32();

            for (uint i = 0; i < removeMovementForcesCount; ++i)
            {
                ObjectGuid guid = data.ReadPackedGuid();
            }

            // ResetBitReader

            movementInfo.SetMovementFlags((MovementFlag)data.ReadBits<uint>(30));
            movementInfo.SetMovementFlags2((MovementFlag2)data.ReadBits<uint>(18));

            bool hasTransport = data.HasBit();
            bool hasFall = data.HasBit();
            bool hasSpline = data.HasBit(); // todo 6.x read this infos

            data.ReadBit(); // HeightChangeFailed
            data.ReadBit(); // RemoteTimeValid

            if (hasTransport)
                ReadTransportInfo(data, ref movementInfo.transport);

            if (hasFall)
            {
                movementInfo.jump.fallTime = data.ReadUInt32();
                movementInfo.jump.zspeed = data.ReadFloat();

                // ResetBitReader

                bool hasFallDirection = data.HasBit();
                if (hasFallDirection)
                {
                    movementInfo.jump.sinAngle = data.ReadFloat();
                    movementInfo.jump.cosAngle = data.ReadFloat();
                    movementInfo.jump.xyspeed = data.ReadFloat();
                }
            }

            return movementInfo;
        }

        public static void WriteMovementInfo(WorldPacket data, MovementInfo movementInfo)
        {
            bool hasTransportData = !movementInfo.transport.guid.IsEmpty();
            bool hasFallDirection = movementInfo.HasMovementFlag(MovementFlag.Falling | MovementFlag.FallingFar);
            bool hasFallData = hasFallDirection || movementInfo.jump.fallTime != 0;
            bool hasSpline = false; // todo 6.x send this infos

            data.WritePackedGuid(movementInfo.Guid);
            data.WriteUInt32(movementInfo.Time);
            data.WriteFloat(movementInfo.Pos.GetPositionX());
            data.WriteFloat(movementInfo.Pos.GetPositionY());
            data.WriteFloat(movementInfo.Pos.GetPositionZ());
            data.WriteFloat(movementInfo.Pos.GetOrientation());
            data.WriteFloat(movementInfo.Pitch);
            data.WriteFloat(movementInfo.SplineElevation);

            uint removeMovementForcesCount = 0;
            data.WriteUInt32(removeMovementForcesCount);

            uint int168 = 0;
            data.WriteUInt32(int168);

            /*for (public uint i = 0; i < removeMovementForcesCount; ++i)
            {
                _worldPacket << ObjectGuid;
            }*/

            data.WriteBits((uint)movementInfo.GetMovementFlags(), 30);
            data.WriteBits((uint)movementInfo.GetMovementFlags2(), 18);

            data.WriteBit(hasTransportData);
            data.WriteBit(hasFallData);
            data.WriteBit(hasSpline);
            data.WriteBit(0); // HeightChangeFailed
            data.WriteBit(0); // RemoteTimeValid
            data.FlushBits();

            if (hasTransportData)
                WriteTransportInfo(data, movementInfo.transport);

            if (hasFallData)
            {
                data.WriteUInt32(movementInfo.jump.fallTime);
                data.WriteFloat(movementInfo.jump.zspeed);

                data.WriteBit(hasFallDirection);
                data.FlushBits();
                if (hasFallDirection)
                {
                    data.WriteFloat(movementInfo.jump.cosAngle);
                    data.WriteFloat(movementInfo.jump.sinAngle);
                    data.WriteFloat(movementInfo.jump.xyspeed);
                }
            }
        }

        public static void WriteCreateObjectSplineDataBlock(MoveSpline moveSpline, WorldPacket data)
        {
            data.WriteUInt32(moveSpline.GetId());                                         // ID

            if (!moveSpline.isCyclic())                                                 // Destination
                data.WriteVector3(moveSpline.FinalDestination());
            else
                data.WriteVector3(Vector3.Zero);

            bool hasSplineMove = data.WriteBit(!moveSpline.Finalized() && !moveSpline.splineIsFacingOnly);
            data.FlushBits();

            if (hasSplineMove)
            {
                data.WriteUInt32((uint)moveSpline.splineflags.Flags);   // SplineFlags
                data.WriteInt32(moveSpline.timePassed());               // Elapsed
                data.WriteUInt32(moveSpline.Duration());                // Duration
                data.WriteFloat(1.0f);                                  // DurationModifier
                data.WriteFloat(1.0f);                                  // NextDurationModifier
                data.WriteBits((byte)moveSpline.facing.type, 2);        // Face
                bool HasJumpGravity = data.WriteBit(moveSpline.splineflags.hasFlag(SplineFlag.Parabolic | SplineFlag.Animation));                                   // HasJumpGravity
                bool HasSpecialTime = data.WriteBit(moveSpline.splineflags.hasFlag(SplineFlag.Parabolic) && moveSpline.effect_start_time < moveSpline.Duration());  // HasSpecialTime
                data.WriteBits(moveSpline.getPath().Length, 16);
                data.WriteBits((byte)moveSpline.spline.m_mode, 2);      // Mode
                data.WriteBit(0);                                       // HasSplineFilter
                data.WriteBit(moveSpline.spell_effect_extra.HasValue);  // HasSpellEffectExtraData

                //if (HasSplineFilterKey)
                //{
                //    data << uint32(FilterKeysCount);
                //    for (var i = 0; i < FilterKeysCount; ++i)
                //    {
                //        data << float(In);
                //        data << float(Out);
                //    }

                //    data.WriteBits(FilterFlags, 2);
                //    data.FlushBits();
                //}

                switch (moveSpline.facing.type)
                {
                    case MonsterMoveType.FacingSpot:
                        data.WriteVector3(moveSpline.facing.f);         // FaceSpot
                        break;
                    case MonsterMoveType.FacingTarget:
                        data.WritePackedGuid(moveSpline.facing.target); // FaceGUID
                        break;
                    case MonsterMoveType.FacingAngle:
                        data.WriteFloat(moveSpline.facing.angle);       // FaceDirection
                        break;
                }

                if (HasJumpGravity)
                    data.WriteFloat(moveSpline.vertical_acceleration);  // JumpGravity

                if (HasSpecialTime)
                    data.WriteUInt32(moveSpline.effect_start_time);     // SpecialTime

                foreach (var vec in moveSpline.getPath())
                    data.WriteVector3(vec);

                if (moveSpline.spell_effect_extra.HasValue)
                {
                    data.WritePackedGuid(moveSpline.spell_effect_extra.Value.Target);
                    data.WriteUInt32(moveSpline.spell_effect_extra.Value.SpellVisualId);
                    data.WriteUInt32(moveSpline.spell_effect_extra.Value.ProgressCurveId);
                    data.WriteUInt32(moveSpline.spell_effect_extra.Value.ParabolicCurveId);
                }
            }
        }

        public static void WriteCreateObjectAreaTriggerSpline(Spline spline, WorldPacket data)
        {
            data.WriteBits(spline.getPoints().Length, 16);
            foreach (var point in spline.getPoints())
                data.WriteVector3(point);
        }
    }

    public class ClientPlayerMovement : ClientPacket
    {
        public ClientPlayerMovement(WorldPacket packet) : base(packet) { }

        public override void Read()
        {
            movementInfo = MovementExtensions.ReadMovementInfo(_worldPacket);
        }

        public MovementInfo movementInfo;
    }

    public class MoveUpdate : ServerPacket
    {
        public MoveUpdate() : base(ServerOpcodes.MoveUpdate, ConnectionType.Instance) { }

        public override void Write()
        {
            MovementExtensions.WriteMovementInfo(_worldPacket, movementInfo);
        }

        public MovementInfo movementInfo;
    }

    public class MonsterMove : ServerPacket
    {
        public MonsterMove() : base(ServerOpcodes.OnMonsterMove, ConnectionType.Instance)
        {
            SplineData = new MovementMonsterSpline();
        }

        public void InitializeSplineData(MoveSpline moveSpline)
        {
            SplineData.ID = moveSpline.GetId();
            MovementSpline movementSpline = SplineData.Move;

            MoveSplineFlag splineFlags = moveSpline.splineflags;
            splineFlags.SetUnsetFlag(SplineFlag.Cyclic, moveSpline.isCyclic());
            movementSpline.Flags = (uint)(splineFlags.Flags & ~SplineFlag.MaskNoMonsterMove);
            movementSpline.Face = moveSpline.facing.type;
            movementSpline.FaceDirection = moveSpline.facing.angle;
            movementSpline.FaceGUID = moveSpline.facing.target;
            movementSpline.FaceSpot = moveSpline.facing.f;

            if (splineFlags.hasFlag(SplineFlag.Animation))
            {
                movementSpline.AnimTier = splineFlags.getAnimationId();
                movementSpline.TierTransStartTime = (uint)moveSpline.effect_start_time;
            }

            movementSpline.MoveTime = (uint)moveSpline.Duration();

            if (splineFlags.hasFlag(SplineFlag.Parabolic))
            {
                movementSpline.JumpGravity = moveSpline.vertical_acceleration;
                movementSpline.SpecialTime = (uint)moveSpline.effect_start_time;
            }

            if (splineFlags.hasFlag(SplineFlag.FadeObject))
                movementSpline.SpecialTime = (uint)moveSpline.effect_start_time;

            if (moveSpline.spell_effect_extra.HasValue)
            {
                movementSpline.SpellEffectExtraData.HasValue = true;
                movementSpline.SpellEffectExtraData.Value.TargetGuid = moveSpline.spell_effect_extra.Value.Target;
                movementSpline.SpellEffectExtraData.Value.SpellVisualID = moveSpline.spell_effect_extra.Value.SpellVisualId;
                movementSpline.SpellEffectExtraData.Value.ProgressCurveID = moveSpline.spell_effect_extra.Value.ProgressCurveId;
                movementSpline.SpellEffectExtraData.Value.ParabolicCurveID = moveSpline.spell_effect_extra.Value.ParabolicCurveId;
            }

            Spline spline = moveSpline.spline;
            Vector3[] array = spline.getPoints();

            if (splineFlags.hasFlag(SplineFlag.UncompressedPath))
            {
                if (!splineFlags.hasFlag(SplineFlag.Cyclic))
                {
                    int count = spline.getPointCount() - 3;
                    for (uint i = 0; i < count; ++i)
                        movementSpline.Points.Add(array[i + 2]);
                }
                else
                {
                    int count = spline.getPointCount() - 3;
                    movementSpline.Points.Add(array[1]);
                    for (uint i = 0; i < count; ++i)
                        movementSpline.Points.Add(array[i + 1]);
                }
            }
            else
            {
                int lastIdx = spline.getPointCount() - 3;
                Vector3[] realPath = spline.getPoints().Skip(1).ToArray();

                movementSpline.Points.Add(realPath[lastIdx]);

                if (lastIdx > 1)
                {
                    Vector3 middle = (realPath[0] + realPath[lastIdx]) / 2.0f;

                    // first and last points already appended
                    for (uint i = 1; i < lastIdx; ++i)
                        movementSpline.PackedDeltas.Add(middle - realPath[i]);
                }
            }
        }

        public override void Write()
        {
            _worldPacket.WritePackedGuid(MoverGUID);
            _worldPacket.WriteVector3(Pos);
            SplineData.Write(_worldPacket);
        }

        public MovementMonsterSpline SplineData;
        public ObjectGuid MoverGUID;
        public Vector3 Pos;
    }

    public class MoveSplineSetSpeed : ServerPacket
    {
        public MoveSplineSetSpeed(ServerOpcodes opcode) : base(opcode, ConnectionType.Instance) { }

        public override void Write()
        {
            _worldPacket.WritePackedGuid(MoverGUID);
            _worldPacket.WriteFloat(Speed);
        }

        public ObjectGuid MoverGUID;
        public float Speed = 1.0f;
    }

    public class MoveSetSpeed : ServerPacket
    {
        public MoveSetSpeed(ServerOpcodes opcode) : base(opcode, ConnectionType.Instance) { }

        public override void Write()
        {
            _worldPacket.WritePackedGuid(MoverGUID);
            _worldPacket.WriteUInt32(SequenceIndex);
            _worldPacket.WriteFloat(Speed);
        }

        public ObjectGuid MoverGUID;
        public uint SequenceIndex = 0; // Unit movement packet index, incremented each time
        public float Speed = 1.0f;
    }

    public class MoveUpdateSpeed : ServerPacket
    {
        public MoveUpdateSpeed(ServerOpcodes opcode) : base(opcode, ConnectionType.Instance) { }

        public override void Write()
        {
            MovementExtensions.WriteMovementInfo(_worldPacket, movementInfo);
            _worldPacket.WriteFloat(Speed);
        }

        public MovementInfo movementInfo;
        public float Speed = 1.0f;
    }

    public class MoveSplineSetFlag : ServerPacket
    {
        public MoveSplineSetFlag(ServerOpcodes opcode) : base(opcode, ConnectionType.Instance) { }

        public override void Write()
        {
            _worldPacket.WritePackedGuid(MoverGUID);
        }

        public ObjectGuid MoverGUID;
    }

    public class MoveSetFlag : ServerPacket
    {
        public MoveSetFlag(ServerOpcodes opcode) : base(opcode, ConnectionType.Instance) { }

        public override void Write()
        {
            _worldPacket.WritePackedGuid(MoverGUID);
            _worldPacket.WriteUInt32(SequenceIndex);
        }

        public ObjectGuid MoverGUID;
        public uint SequenceIndex = 0; // Unit movement packet index, incremented each time
    }

    public class TransferPending : ServerPacket
    {
        public TransferPending() : base(ServerOpcodes.TransferPending)
        {
            Ship = new Optional<ShipTransferPending>();
            TransferSpellID = new Optional<int>();
        }

        public override void Write()
        {
            _worldPacket.WriteInt32(MapID);
            _worldPacket.WriteBit(Ship.HasValue);
            _worldPacket.WriteBit(TransferSpellID.HasValue);
            if (Ship.HasValue)
            {
                _worldPacket.WriteUInt32(Ship.Value.ID);
                _worldPacket.WriteInt32(Ship.Value.OriginMapID);
            }

            if (TransferSpellID.HasValue)
                _worldPacket.WriteInt32(TransferSpellID.Value);

            _worldPacket.FlushBits();
        }

        public int MapID = -1;
        public Optional<ShipTransferPending> Ship;
        public Optional<int> TransferSpellID;

        public struct ShipTransferPending
        {
            public uint ID;              // gameobject_template.entry of the transport the player is teleporting on
            public int OriginMapID;     // Map id the player is currently on (before teleport)
        }
    }

    public class TransferAborted : ServerPacket
    {
        public TransferAborted() : base(ServerOpcodes.TransferAborted) { }

        public override void Write()
        {
            _worldPacket.WriteUInt32(MapID);
            _worldPacket.WriteUInt8(Arg);
            _worldPacket.WriteInt32(MapDifficultyXConditionID);
            _worldPacket.WriteBits(TransfertAbort, 5);
            _worldPacket.FlushBits();
        }

        public uint MapID;
        public byte Arg;
        public int MapDifficultyXConditionID;
        public TransferAbortReason TransfertAbort;
    }

    public class NewWorld : ServerPacket
    {
        public NewWorld() : base(ServerOpcodes.NewWorld) { }

        public override void Write()
        {
            _worldPacket.WriteUInt32(MapID);
            _worldPacket.WriteXYZO(Pos);
            _worldPacket.WriteUInt32(Reason);
            _worldPacket.WriteXYZ(MovementOffset);
        }

        public uint MapID;
        public uint Reason;
        public Position Pos;
        public Position MovementOffset;    // Adjusts all pending movement events by this offset
    }

    public class WorldPortResponse : ClientPacket
    {
        public WorldPortResponse(WorldPacket packet) : base(packet) { }

        public override void Read() { }
    }

    public class MoveTeleport : ServerPacket
    {
        public MoveTeleport() : base(ServerOpcodes.MoveTeleport, ConnectionType.Instance) { }

        public override void Write()
        {
            _worldPacket.WritePackedGuid(MoverGUID);
            _worldPacket.WriteUInt32(SequenceIndex);
            _worldPacket.WriteXYZ(Pos);
            _worldPacket.WriteFloat(Facing);
            _worldPacket.WriteUInt8(PreloadWorld);

            _worldPacket.WriteBit(Vehicle.HasValue);
            _worldPacket.WriteBit(TransportGUID.HasValue);
            _worldPacket.FlushBits();

            if (Vehicle.HasValue)
            {
                _worldPacket.WriteUInt8(Vehicle.Value.VehicleSeatIndex);
                _worldPacket.WriteBit(Vehicle.Value.VehicleExitVoluntary);
                _worldPacket.WriteBit(Vehicle.Value.VehicleExitTeleport);
                _worldPacket.FlushBits();
            }

            if (TransportGUID.HasValue)
                _worldPacket.WritePackedGuid(TransportGUID.Value);
        }

        public Position Pos;
        public Optional<VehicleTeleport> Vehicle;
        public uint SequenceIndex;
        public ObjectGuid MoverGUID;
        public Optional<ObjectGuid> TransportGUID;
        public float Facing;
        public byte PreloadWorld;
    }

    public class MoveUpdateTeleport : ServerPacket
    {
        public MoveUpdateTeleport() : base(ServerOpcodes.MoveUpdateTeleport) { }

        public override void Write()
        {
            MovementExtensions.WriteMovementInfo(_worldPacket, movementInfo);

            _worldPacket.WriteInt32(MovementForces.Count);
            _worldPacket.WriteBit(WalkSpeed.HasValue);
            _worldPacket.WriteBit(RunSpeed.HasValue);
            _worldPacket.WriteBit(RunBackSpeed.HasValue);
            _worldPacket.WriteBit(SwimSpeed.HasValue);
            _worldPacket.WriteBit(SwimBackSpeed.HasValue);
            _worldPacket.WriteBit(FlightSpeed.HasValue);
            _worldPacket.WriteBit(FlightBackSpeed.HasValue);
            _worldPacket.WriteBit(TurnRate.HasValue);
            _worldPacket.WriteBit(PitchRate.HasValue);
            _worldPacket.FlushBits();

            foreach (MovementForce force in MovementForces)
                force.Write(_worldPacket);

            if (WalkSpeed.HasValue)
                _worldPacket.WriteFloat(WalkSpeed.Value);

            if (RunSpeed.HasValue)
                _worldPacket.WriteFloat(RunSpeed.Value);

            if (RunBackSpeed.HasValue)
                _worldPacket.WriteFloat(RunBackSpeed.Value);

            if (SwimSpeed.HasValue)
                _worldPacket.WriteFloat(SwimSpeed.Value);

            if (SwimBackSpeed.HasValue)
                _worldPacket.WriteFloat(SwimBackSpeed.Value);

            if (FlightSpeed.HasValue)
                _worldPacket.WriteFloat(FlightSpeed.Value);

            if (FlightBackSpeed.HasValue)
                _worldPacket.WriteFloat(FlightBackSpeed.Value);

            if (TurnRate.HasValue)
                _worldPacket.WriteFloat(TurnRate.Value);

            if (PitchRate.HasValue)
                _worldPacket.WriteFloat(PitchRate.Value);
        }

        public MovementInfo movementInfo;
        public List<MovementForce> MovementForces = new List<MovementForce>();
        public Optional<float> SwimBackSpeed;
        public Optional<float> FlightSpeed;
        public Optional<float> SwimSpeed;
        public Optional<float> WalkSpeed;
        public Optional<float> TurnRate;
        public Optional<float> RunSpeed;
        public Optional<float> FlightBackSpeed;
        public Optional<float> RunBackSpeed;
        public Optional<float> PitchRate;
    }

    class MoveUpdateApplyMovementForce : ServerPacket
    {
        public MoveUpdateApplyMovementForce() : base(ServerOpcodes.MoveUpdateApplyMovementForce) { }

        public override void Write()
        {
            MovementExtensions.WriteMovementInfo(_worldPacket, movementInfo);
            Force.Write(_worldPacket);
        }

        MovementInfo movementInfo = new MovementInfo();
        MovementForce Force = new MovementForce();
    }

    class MoveUpdateRemoveMovementForce : ServerPacket
    {
        public MoveUpdateRemoveMovementForce() : base(ServerOpcodes.MoveUpdateRemoveMovementForce) { }

        public override void Write()
        {
            MovementExtensions.WriteMovementInfo(_worldPacket, movementInfo);
            _worldPacket.WritePackedGuid(TriggerGUID);
        }

        MovementInfo movementInfo = new MovementInfo();
        ObjectGuid TriggerGUID;
    }

    class MoveTeleportAck : ClientPacket
    {
        public MoveTeleportAck(WorldPacket packet) : base(packet) { }

        public override void Read()
        {
            MoverGUID = _worldPacket.ReadPackedGuid();
            AckIndex = _worldPacket.ReadInt32();
            MoveTime = _worldPacket.ReadInt32();
        }

        public ObjectGuid MoverGUID;
        int AckIndex = 0;
        int MoveTime = 0;
    }

    public class MovementAckMessage : ClientPacket
    {
        public MovementAckMessage(WorldPacket packet) : base(packet) { }

        public override void Read()
        {
            Ack.Read(_worldPacket);
        }

        public MovementAck Ack;
    }

    public class MovementSpeedAck : ClientPacket
    {
        public MovementSpeedAck(WorldPacket packet) : base(packet) { }

        public override void Read()
        {
            Ack.Read(_worldPacket);
            Speed = _worldPacket.ReadFloat();
        }

        public MovementAck Ack;
        public float Speed;
    }

    public class SetActiveMover : ClientPacket
    {
        public ObjectGuid ActiveMover;

        public SetActiveMover(WorldPacket packet) : base(packet) { }

        public override void Read()
        {
            ActiveMover = _worldPacket.ReadPackedGuid();
        }
    }

    public class MoveSetActiveMover : ServerPacket
    {
        public ObjectGuid MoverGUID;

        public MoveSetActiveMover() : base(ServerOpcodes.MoveSetActiveMover) { }

        public override void Write()
        {
            _worldPacket.WritePackedGuid(MoverGUID);
        }
    }

    class MoveKnockBack : ServerPacket
    {
        public MoveKnockBack() : base(ServerOpcodes.MoveKnockBack, ConnectionType.Instance) { }

        public override void Write()
        {
            _worldPacket.WritePackedGuid(MoverGUID);
            _worldPacket.WriteUInt32(SequenceIndex);
            _worldPacket.WriteVector2(Direction);
            Speeds.Write(_worldPacket);
        }

        public ObjectGuid MoverGUID;
        public Vector2 Direction;
        public MoveKnockBackSpeeds Speeds;
        public uint SequenceIndex;
    }

    public class MoveUpdateKnockBack : ServerPacket
    {
        public MoveUpdateKnockBack() : base(ServerOpcodes.MoveUpdateKnockBack) { }

        public override void Write()
        {
            MovementExtensions.WriteMovementInfo(_worldPacket, movementInfo);
        }

        public MovementInfo movementInfo;
    }

    class MoveKnockBackAck : ClientPacket
    {
        public MoveKnockBackAck(WorldPacket packet) : base(packet) { }

        public override void Read()
        {
            Ack.Read(_worldPacket);
            if (_worldPacket.HasBit())
            {
                Speeds.HasValue = true;
                Speeds.Value.Read(_worldPacket);
            }
        }

        public MovementAck Ack;
        public Optional<MoveKnockBackSpeeds> Speeds;
    }

    class MoveSetCollisionHeight : ServerPacket
    {
        public MoveSetCollisionHeight() : base(ServerOpcodes.MoveSetCollisionHeight) { }

        public override void Write()
        {
            _worldPacket.WritePackedGuid(MoverGUID);
            _worldPacket.WriteUInt32(SequenceIndex);
            _worldPacket.WriteFloat(Height);
            _worldPacket.WriteFloat(Scale);
            _worldPacket.WriteUInt32(MountDisplayID);
            _worldPacket.WriteInt32(ScaleDuration);
            _worldPacket.WriteBits((uint)Reason, 2);
            _worldPacket.FlushBits();
        }

        public float Scale = 1.0f;
        public ObjectGuid MoverGUID;
        public uint MountDisplayID;
        public UpdateCollisionHeightReason Reason = UpdateCollisionHeightReason.Mount;
        public uint SequenceIndex;
        public int ScaleDuration;
        public float Height = 1.0f;
    }

    public class MoveUpdateCollisionHeight : ServerPacket
    {
        public MoveUpdateCollisionHeight() : base(ServerOpcodes.MoveUpdateCollisionHeight) { }

        public override void Write()
        {
            MovementExtensions.WriteMovementInfo(_worldPacket, movementInfo);
            _worldPacket.WriteFloat(Height);
            _worldPacket.WriteFloat(Scale);
        }

        public MovementInfo movementInfo;
        public float Scale = 1.0f;
        public float Height = 1.0f;
    }

    public class MoveSetCollisionHeightAck : ClientPacket
    {
        public MoveSetCollisionHeightAck(WorldPacket packet) : base(packet) { }

        public override void Read()
        {
            Data.Read(_worldPacket);
            Height = _worldPacket.ReadFloat();
            MountDisplayID = _worldPacket.ReadUInt32();
            Reason = (UpdateCollisionHeightReason)_worldPacket.ReadBits<uint>(2);
        }

        public MovementAck Data;
        public UpdateCollisionHeightReason Reason = UpdateCollisionHeightReason.Mount;
        public uint MountDisplayID;
        public float Height = 1.0f;
    }

    class MoveTimeSkipped : ClientPacket
    {
        public MoveTimeSkipped(WorldPacket packet) : base(packet) { }

        public override void Read()
        {
            MoverGUID = _worldPacket.ReadPackedGuid();
            TimeSkipped = _worldPacket.ReadUInt32();
        }

        public ObjectGuid MoverGUID;
        public uint TimeSkipped;
    }

    class SummonResponse : ClientPacket
    {
        public SummonResponse(WorldPacket packet) : base(packet) { }

        public override void Read()
        {
            SummonerGUID = _worldPacket.ReadPackedGuid();
            Accept = _worldPacket.HasBit();
        }

        public bool Accept;
        public ObjectGuid SummonerGUID;
    }

    public class ControlUpdate : ServerPacket
    {
        public ControlUpdate() : base(ServerOpcodes.ControlUpdate) { }

        public override void Write()
        {
            _worldPacket.WritePackedGuid(Guid);
            _worldPacket.WriteBit(On);
            _worldPacket.FlushBits();
        }

        public bool On;
        public ObjectGuid Guid;
    }

    class MoveSplineDone : ClientPacket
    {
        public MoveSplineDone(WorldPacket packet) : base(packet) { }

        public override void Read()
        {
            movementInfo = MovementExtensions.ReadMovementInfo(_worldPacket);
            SplineID = _worldPacket.ReadInt32();
        }

        public MovementInfo movementInfo;
        public int SplineID;
    }

    class SummonRequest : ServerPacket
    {
        public SummonRequest() : base(ServerOpcodes.SummonRequest, ConnectionType.Instance) { }

        public override void Write()
        {
            _worldPacket.WritePackedGuid(SummonerGUID);
            _worldPacket.WriteUInt32(SummonerVirtualRealmAddress);
            _worldPacket.WriteInt32(AreaID);
            _worldPacket.WriteUInt8(Reason);
            _worldPacket.WriteBit(SkipStartingArea);
            _worldPacket.FlushBits();
        }

        public ObjectGuid SummonerGUID;
        public uint SummonerVirtualRealmAddress;
        public int AreaID;
        public SummonReason Reason;
        public bool SkipStartingArea;

        public enum SummonReason
        {
            Spell = 0,
            Scenario = 1
        }
    }

    class SuspendToken : ServerPacket
    {
        public SuspendToken() : base(ServerOpcodes.SuspendToken, ConnectionType.Instance) { }

        public override void Write()
        {
            _worldPacket.WriteUInt32(SequenceIndex);
            _worldPacket.WriteBits(Reason, 2);
            _worldPacket.FlushBits();
        }

        public uint SequenceIndex = 1;
        public uint Reason = 1;
    }

    class SuspendTokenResponse : ClientPacket
    {
        public SuspendTokenResponse(WorldPacket packet) : base(packet) { }

        public override void Read()
        {
            SequenceIndex = _worldPacket.ReadUInt32();
        }

        public uint SequenceIndex;
    }

    class ResumeToken : ServerPacket
    {
        public ResumeToken() : base(ServerOpcodes.ResumeToken, ConnectionType.Instance) { }

        public override void Write()
        {
            _worldPacket.WriteUInt32(SequenceIndex);
            _worldPacket.WriteBits(Reason, 2);
            _worldPacket.FlushBits();
        }

        public uint SequenceIndex = 1;
        public uint Reason = 1;
    }

    class MoveSetCompoundState : ServerPacket
    {
        public MoveSetCompoundState() : base(ServerOpcodes.MoveSetCompoundState, ConnectionType.Instance) { }

        public override void Write()
        {
            _worldPacket.WritePackedGuid(MoverGUID);
            _worldPacket.WriteUInt32(StateChanges.Count);
            foreach (MoveStateChange stateChange in StateChanges)
                stateChange.Write(_worldPacket);
        }

        public ObjectGuid MoverGUID;
        public List<MoveStateChange> StateChanges = new List<MoveStateChange>();

        public struct CollisionHeightInfo
        {
            public float Height;
            public float Scale;
            public UpdateCollisionHeightReason Reason;
        }

        public struct KnockBackInfo
        {
            public float HorzSpeed;
            public Vector2 Direction;
            public float InitVertSpeed;
        }

        public class MoveStateChange
        {
            public MoveStateChange(ServerOpcodes messageId, uint sequenceIndex)
            {
                MessageID = messageId;
                SequenceIndex = sequenceIndex;
            }

            public void Write(WorldPacket data)
            {
                data.WriteUInt16(MessageID);
                data.WriteUInt32(SequenceIndex);
                data.WriteBit(Speed.HasValue);
                data.WriteBit(KnockBack.HasValue);
                data.WriteBit(VehicleRecID.HasValue);
                data.WriteBit(CollisionHeight.HasValue);
                data.WriteBit(MovementForce_.HasValue);
                data.WriteBit(Unknown.HasValue);
                data.FlushBits();

                if (CollisionHeight.HasValue)
                {
                    data.WriteFloat(CollisionHeight.Value.Height);
                    data.WriteFloat(CollisionHeight.Value.Scale);
                    data.WriteBits(CollisionHeight.Value.Reason, 2);
                    data.FlushBits();
                }

                if (Speed.HasValue)
                    data.WriteFloat(Speed.Value);

                if (KnockBack.HasValue)
                {
                    data.WriteFloat(KnockBack.Value.HorzSpeed);
                    data.WriteVector2(KnockBack.Value.Direction);
                    data.WriteFloat(KnockBack.Value.InitVertSpeed);
                }

                if (VehicleRecID.HasValue)
                    data.WriteInt32(VehicleRecID.Value);

                if (Unknown.HasValue)
                    data.WritePackedGuid(Unknown.Value);

                if (MovementForce_.HasValue)
                    MovementForce_.Value.Write(data);
            }

            public ServerOpcodes MessageID;
            public uint SequenceIndex;
            public Optional<float> Speed;
            public Optional<KnockBackInfo> KnockBack;
            public Optional<int> VehicleRecID;
            public Optional<CollisionHeightInfo> CollisionHeight;
            public Optional<MovementForce> MovementForce_;
            public Optional<ObjectGuid> Unknown;
        }
    }

    //Structs
    public struct MonsterSplineFilterKey
    {
        public void Write(WorldPacket data)
        {
            data.WriteInt16(Idx);
            data.WriteUInt16(Speed);
        }

        public short Idx;
        public ushort Speed;
    }

    public class MonsterSplineFilter
    {
        public void Write(WorldPacket data)
        {
            data.WriteUInt32(FilterKeys.Count);
            data.WriteFloat(BaseSpeed);
            data.WriteInt16(StartOffset);
            data.WriteFloat(DistToPrevFilterKey);
            data.WriteInt16(AddedToStart);

            FilterKeys.ForEach(p => p.Write(data));

            data.WriteBits(FilterFlags, 2);
            data.FlushBits();
        }

        public List<MonsterSplineFilterKey> FilterKeys = new List<MonsterSplineFilterKey>();
        public byte FilterFlags;
        public float BaseSpeed;
        public short StartOffset;
        public float DistToPrevFilterKey;
        public short AddedToStart;
    }

    public struct MonsterSplineSpellEffectExtraData
    {
        public void Write(WorldPacket data)
        {
            data.WritePackedGuid(TargetGuid);
            data.WriteUInt32(SpellVisualID);
            data.WriteUInt32(ProgressCurveID);
            data.WriteUInt32(ParabolicCurveID);
        }

        public ObjectGuid TargetGuid;
        public uint SpellVisualID;
        public uint ProgressCurveID;
        public uint ParabolicCurveID;
    }

    public class MovementSpline
    {
        public void Write(WorldPacket data)
        {
            data.WriteUInt32(Flags);
            data.WriteUInt8(AnimTier);
            data.WriteUInt32(TierTransStartTime);
            data.WriteInt32(Elapsed);
            data.WriteUInt32(MoveTime);
            data.WriteFloat(JumpGravity);
            data.WriteUInt32(SpecialTime);
            data.WriteUInt8(Mode);
            data.WriteUInt8(VehicleExitVoluntary);
            data.WritePackedGuid(TransportGUID);
            data.WriteInt8(VehicleSeat);
            data.WriteBits((byte)Face, 2);
            data.WriteBits(Points.Count, 16);
            data.WriteBits(PackedDeltas.Count, 16);
            data.WriteBit(SplineFilter.HasValue);
            data.WriteBit(SpellEffectExtraData.HasValue);
            data.FlushBits();

            if (SplineFilter.HasValue)
                SplineFilter.Value.Write(data);

            switch (Face)
            {
                case MonsterMoveType.FacingSpot:
                    data.WriteVector3(FaceSpot);
                    break;
                case MonsterMoveType.FacingTarget:
                    data.WriteFloat(FaceDirection);
                    data.WritePackedGuid(FaceGUID);
                    break;
                case MonsterMoveType.FacingAngle:
                    data.WriteFloat(FaceDirection);
                    break;
            }

            foreach (Vector3 pos in Points)
                data.WriteVector3(pos);

            foreach (Vector3 pos in PackedDeltas)
                data.WritePackXYZ(pos);

            if (SpellEffectExtraData.HasValue)
                SpellEffectExtraData.Value.Write(data);
        }

        public uint Flags; // Spline flags
        public MonsterMoveType Face; // Movement direction (see MonsterMoveType enum)
        public byte AnimTier;
        public uint TierTransStartTime;
        public int Elapsed;
        public uint MoveTime;
        public float JumpGravity;
        public uint SpecialTime;
        public List<Vector3> Points = new List<Vector3>(); // Spline path
        public byte Mode; // Spline mode - actually always 0 in this packet - Catmullrom mode appears only in SMSG_UPDATE_OBJECT. In this packet it is determined by flags
        public byte VehicleExitVoluntary;
        public ObjectGuid TransportGUID;
        public sbyte VehicleSeat = -1;
        public List<Vector3> PackedDeltas = new List<Vector3>();
        public Optional<MonsterSplineFilter> SplineFilter;
        public Optional<MonsterSplineSpellEffectExtraData> SpellEffectExtraData;
        public float FaceDirection;
        public ObjectGuid FaceGUID;
        public Vector3 FaceSpot;
    }

    public class MovementMonsterSpline
    {
        public MovementMonsterSpline()
        {
            Move = new MovementSpline();
        }

        public void Write(WorldPacket data)
        {
            data.WriteUInt32(ID);
            data.WriteVector3(Destination);
            data.WriteBit(CrzTeleport);
            data.WriteBits(StopDistanceTolerance, 3);

            Move.Write(data);
        }

        public uint ID;
        public Vector3 Destination;
        public bool CrzTeleport;
        public byte StopDistanceTolerance;    // Determines how far from spline destination the mover is allowed to stop in place 0, 0, 3.0, 2.76, numeric_limits<float>::max, 1.1, float(INT_MAX); default before this field existed was distance 3.0 (index 2)
        public MovementSpline Move;
    }

    public struct VehicleTeleport
    {
        public byte VehicleSeatIndex;
        public bool VehicleExitVoluntary;
        public bool VehicleExitTeleport;
    }

    public struct MovementForce
    {
        public void Write(WorldPacket data)
        {
            data.WritePackedGuid(ID);
            data.WriteVector3(Origin);
            data.WriteVector3(Direction);
            data.WriteUInt32(TransportID);
            data.WriteFloat(Magnitude);
            data.WriteBits(Type, 2);
            data.FlushBits();
        }

        public ObjectGuid ID;
        public Vector3 Origin;
        public Vector3 Direction;
        public uint TransportID;
        public float Magnitude;
        public byte Type;
    }

    public struct MovementAck
    {
        public void Read(WorldPacket data)
        {
            movementInfo = MovementExtensions.ReadMovementInfo(data);
            AckIndex = data.ReadInt32();
        }

        public MovementInfo movementInfo;
        public int AckIndex;
    }

    public struct MoveKnockBackSpeeds
    {
        public void Write(WorldPacket data)
        {
            data.WriteFloat(HorzSpeed);
            data.WriteFloat(VertSpeed);
        }

        public void Read(WorldPacket data)
        {
            HorzSpeed = data.ReadFloat();
            VertSpeed = data.ReadFloat();
        }

        public float HorzSpeed;
        public float VertSpeed;
    }
}
