
using FFXIVClassic.Common;
using FFXIVClassic_Map_Server.actors.area;
using FFXIVClassic_Map_Server.actors.group;
using FFXIVClassic_Map_Server.Actors.Chara;
using FFXIVClassic_Map_Server.packets.send.actor;
using FFXIVClassic_Map_Server.utils;
using FFXIVClassic_Map_Server.actors.chara.ai;
using System;

namespace FFXIVClassic_Map_Server.Actors
{
    class Character:Actor
    {
        public const int SIZE = 0;
        public const int COLORINFO = 1;
        public const int FACEINFO = 2;
        public const int HIGHLIGHT_HAIR = 3;
        public const int VOICE = 4;
        public const int MAINHAND = 5;
        public const int OFFHAND = 6;
        public const int SPMAINHAND = 7;
        public const int SPOFFHAND = 8;
        public const int THROWING = 9;
        public const int PACK = 10;
        public const int POUCH = 11;
        public const int HEADGEAR = 12;
        public const int BODYGEAR = 13;
        public const int LEGSGEAR = 14;
        public const int HANDSGEAR = 15;
        public const int FEETGEAR = 16;
        public const int WAISTGEAR = 17;
        public const int NECKGEAR = 18;
        public const int L_EAR = 19;
        public const int R_EAR = 20;
        public const int R_WRIST = 21;
        public const int L_WRIST = 22;
        public const int R_RINGFINGER = 23;
        public const int L_RINGFINGER = 24;
        public const int R_INDEXFINGER = 25;
        public const int L_INDEXFINGER = 26;
        public const int UNKNOWN = 27;

        public bool isStatic = false;

        public bool isMovingToSpawn = false;

        public uint modelId;
        public uint[] appearanceIds = new uint[28];

        public uint animationId = 0;

        public uint currentTarget = 0xC0000000;
        public uint currentLockedTarget = 0xC0000000;

        public uint currentActorIcon = 0;

        public Work work = new Work();
        public CharaWork charaWork = new CharaWork();

        public Group currentParty = null;
        public ContentGroup currentContentGroup = null;

        public DateTime lastAiUpdate;

        public AIContainer aiContainer;

        public Character(uint actorID) : base(actorID)
        {            
            //Init timer array to "notimer"
            for (int i = 0; i < charaWork.statusShownTime.Length; i++)
                charaWork.statusShownTime[i] = 0xFFFFFFFF;
        }

        public SubPacket CreateAppearancePacket(uint playerActorId)
        {
            SetActorAppearancePacket setappearance = new SetActorAppearancePacket(modelId, appearanceIds);
            return setappearance.BuildPacket(actorId, playerActorId);
        }

        public SubPacket CreateInitStatusPacket(uint playerActorId)
        {
            return (SetActorStatusAllPacket.BuildPacket(actorId, playerActorId, charaWork.status));                      
        }

        public SubPacket CreateSetActorIconPacket(uint playerActorId)
        {
            return SetActorIconPacket.BuildPacket(actorId, playerActorId, currentActorIcon);
        }

        public SubPacket CreateIdleAnimationPacket(uint playerActorId)
        {
            return SetActorSubStatPacket.BuildPacket(actorId, playerActorId, 0, 0, 0, 0, 0, 0, animationId);
        }

        public void SetQuestGraphic(Player player, int graphicNum)
        {
            player.QueuePacket(SetActorQuestGraphicPacket.BuildPacket(player.actorId, actorId, graphicNum));
        }

        public void SetCurrentContentGroup(ContentGroup group, Player player = null)
        {
            if (group != null)
                charaWork.currentContentGroup = group.GetTypeId();
            else
                charaWork.currentContentGroup = 0;

            currentContentGroup = group;

            if (player != null)
            {
                ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("charaWork/currentContentGroup", this, actorId);
                propPacketUtil.AddProperty("charaWork.currentContentGroup");
                player.QueuePackets(propPacketUtil.Done());
            }
        }     
   
        public void PlayAnimation(uint animId)
        {            
            zone.BroadcastPacketAroundActor(this, PlayAnimationOnActorPacket.BuildPacket(actorId, actorId, animId));
        }

        public void PathTo(float x, float y, float z, float stepSize = 0.70f, int maxPath = 40, float polyRadius = 0.0f)
        {
            var pos = new Vector3(positionX, positionY, positionZ);
            var dest = new Vector3(x, y, z);

            var sw = new System.Diagnostics.Stopwatch();
            sw.Start();

            var path = utils.NavmeshUtils.GetPath(((Zone)GetZone()), pos, dest, stepSize, maxPath, polyRadius);

            if (path != null)
            {
                if (oldPositionX == 0.0f && oldPositionY == 0.0f && oldPositionZ == 0.0f)
                {
                    oldPositionX = positionX;
                    oldPositionY = positionY;
                    oldPositionZ = positionZ;
                }

                // todo: something went wrong
                if (path.Count == 0)
                {
                    positionX = oldPositionX;
                    positionY = oldPositionY;
                    positionZ = oldPositionZ;
                }

                positionUpdates = path;

                this.hasMoved = true;
                this.isAtSpawn = false;

                sw.Stop();
                ((Zone)zone).pathCalls++;
                ((Zone)zone).pathCallTime += sw.ElapsedMilliseconds;

                if (path.Count == 1)
                    Program.Log.Info($"mypos: {positionX} {positionY} {positionZ} | targetPos: {x} {y} {z} | step {stepSize} | maxPath {maxPath} | polyRadius {polyRadius}");

                Program.Log.Error("[{0}][{1}] Created {2} points in {3} milliseconds", actorId, actorName, path.Count, sw.ElapsedMilliseconds);
            }
        }

        public void FollowTarget(Actor target, float stepSize = 1.2f, int maxPath = 25, float radius = 0.0f)
        {
            var player = target as Player;

            if (player != null)
            {
                if (this.target != player)
                {
                    #region super important performance critical code

                    this.ChangeState(SetActorStatePacket.MAIN_STATE_MOUNTED);

                    var chatMode = Program.Random.Next(13);
                    var emphasis = Program.Random.Next(9);
                    var drag = Program.Random.Next(7);

                    chatMode = chatMode.Clamp(1, 12);

                    string oni = "ONI";
                    string chan = "CHA";

                    for (var i = 0; i < emphasis; ++i)
                        oni += "I";

                    for (var i = 0; i < drag; ++i)
                        chan += "A";

                    oni += "-";
                    chan += "N";

                    // imouto aggro
                    player.SendMessage((uint)chatMode, "Rowena", oni + chan);
                    // sing for onii
                    this.PlayAnimation(Program.Random.Next(0, 2) == 1 ? (uint)67111904 : (uint)67108902);

                    #endregion

                    this.target = target;
                }
                this.moveState = player.moveState;
                this.moveSpeeds = player.moveSpeeds;

                PathTo(player.positionX, player.positionY, player.positionZ, stepSize, maxPath, radius);
            }
        }

        public void OnPath(ref Vector3 point)
        {
            if (positionUpdates != null && positionUpdates.Count > 0)
            {
                if (point == positionUpdates[positionUpdates.Count - 1])
                {
                    var myPos = new Vector3(positionX, positionY, positionZ);
                    //point = NavmeshUtils.GetPath((Zone)zone, myPos, point, 0.35f, 1, 0.000001f, true)?[0];
                }
            }
        }

        public void Update(DateTime tick)
        {
            // todo: actual ai controllers
            // todo: mods to control different params instead of hardcode
            // todo: other ai helpers

            // time elapsed since last ai update

            if (aiContainer != null)
            {
                this.aiContainer.Update(tick);
            }

            /*
            var diffTime = (tick - lastAiUpdate);

            if (this is Player)
            {
                // todo: handle player stuff here
            }
            else
            {
                // todo: handle mobs only?
                //if (this.isStatic)
                //    return;
                
                // todo: this too
                if (diffTime.Milliseconds >= 10)
                {
                    bool foundActor = false;

                    // leash back to spawn
                    if (!isMovingToSpawn && this.oldPositionX != 0.0f && this.oldPositionY != 0.0f && this.oldPositionZ != 0.0f)
                    {
                        //var spawnDistanceSq = Utils.DistanceSquared(positionX, positionY, positionZ, oldPositionX, oldPositionY, oldPositionZ);

                        // todo: actual spawn leash and modifiers read from table
                        // set a leash to path back to spawn even if have target
                        // (50 yalms)
                        if (Utils.DistanceSquared(positionX, positionY, positionZ, oldPositionX, oldPositionY, oldPositionZ) >= 3025)
                        {
                            this.isMovingToSpawn = true;
                            this.target = null;
                            this.lastMoveUpdate = this.lastMoveUpdate.AddSeconds(-5);
                            this.hasMoved = false;
                            ClearPositionUpdates();
                        }
                    }

                    // check if player
                    if (target != null && target is Player)
                    {
                        var player = target as Player;

                        // deaggro if zoning/logging
                        // todo: player.isZoning seems to be busted
                        if (player.playerSession.isUpdatesLocked)
                        {
                            target = null;
                            ClearPositionUpdates();
                        }
                    }

                    Player closestPlayer = null;
                    float closestPlayerDistanceSq = 1000.0f;

                    // dont bother checking for any in-range players if going back to spawn
                    if (!this.isMovingToSpawn)
                    {
                        foreach (var actor in zone.GetActorsAroundActor(this, 65))
                        {
                            if (actor is Player && actor != this)
                            {
                                var player = actor as Player;

                                // skip if zoning/logging
                                // todo: player.isZoning seems to be busted
                                if (player != null && player.playerSession.isUpdatesLocked)
                                    continue;

                                // find distance between self and target
                                var distanceSq = Utils.DistanceSquared(positionX, positionY, positionZ, player.positionX, player.positionY, player.positionZ);

                                int maxDistanceSq = player == target ? 900 : 100;

                                // check target isnt too far
                                // todo: create cone thing for IsFacing
                                if (distanceSq <= maxDistanceSq && distanceSq <= closestPlayerDistanceSq && (IsFacing(player) || true))
                                {
                                    closestPlayerDistanceSq = distanceSq;
                                    closestPlayer = player;
                                    foundActor = true;
                                }
                            }
                        }

                        // found a target
                        if (foundActor)
                        {
                            // make sure we're not already moving so we dont spam packets
                            if (!hasMoved)
                            {
                                // todo: include model size and mob specific distance checks
                                if (closestPlayerDistanceSq >= 9)
                                {
                                    FollowTarget(closestPlayer, 2.5f, 4);
                                }
                                // too close, spread out
                                else if (closestPlayerDistanceSq <= 0.85f)
                                {
                                    QueuePositionUpdate(target.FindRandomPointAroundActor(0.65f, 0.85f));
                                }

                                // we have a target, face them
                                if (target != null)
                                {
                                    LookAt(target);
                                }
                            }
                        }
                    }

                    // time elapsed since last move update
                    var diffMove = (tick - lastMoveUpdate);

                    // todo: modifier for DelayBeforeRoamToSpawn
                    // player disappeared
                    if (!foundActor && diffMove.Seconds >= 5)
                    {
                        // dont path if havent moved before
                        if (!hasMoved && oldPositionX != 0.0f && oldPositionY != 0.0f && oldPositionZ != 0.0f)
                        {
                            // check within spawn radius
                            this.isAtSpawn = Utils.DistanceSquared(positionX, positionY, positionZ, oldPositionX, oldPositionY, oldPositionZ) <= 625.0f;

                            // make sure we have no target
                            if (this.target == null)
                            {
                                // path back to spawn
                                if (!this.isAtSpawn)
                                {
                                    PathTo(oldPositionX, oldPositionY, oldPositionZ, 2.8f);
                                }
                                // within spawn range, find a random point
                                else if (diffMove.Seconds >= 15)
                                {
                                    // todo: polyRadius isnt euclidean distance..
                                    // pick a random point within 10 yalms of spawn
                                    PathTo(oldPositionX, oldPositionY, oldPositionZ, 2.5f, 7, 2.5f);

                                    // face destination
                                    if (positionUpdates.Count > 0)
                                    {
                                        var destinationPos = positionUpdates[positionUpdates.Count - 1];
                                        LookAt(destinationPos.X, destinationPos.Y);
                                    }
                                    if (this.isMovingToSpawn)
                                    {
                                        this.isMovingToSpawn = false;
                                        this.ResetMoveSpeedsToDefault();
                                        this.ChangeState(SetActorStatePacket.MAIN_STATE_DEAD2);
                                    }
                                }
                            }
                        }
                        // todo: this is retarded. actually no it isnt, i didnt deaggro if out of range..
                        target = null;
                    }
                    // update last ai update time to now
                    lastAiUpdate = DateTime.Now;
                }
            }
            */
        }
        
    }

}
