/*
 * 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 Game.Entities;
using Game.Maps;
using System;

namespace Game.Movement
{
    public class RandomMovementGenerator<T> : MovementGeneratorMedium<T> where T : Creature
    {
        public RandomMovementGenerator(float spawn_dist = 0.0f)
        {
            i_nextMoveTime = new TimeTrackerSmall();
            wander_distance = spawn_dist;
        }

        TimeTrackerSmall i_nextMoveTime;
        float wander_distance;

        public override MovementGeneratorType GetMovementGeneratorType()
        {
            return MovementGeneratorType.Random;
        }

        public override void DoInitialize(T creature)
        {
            if (!creature.IsAlive())
                return;

            if (wander_distance == 0)
                wander_distance = creature.GetRespawnRadius();

            if (wander_distance == 0)//Temp fix
                wander_distance = 50.0f;

            creature.AddUnitState(UnitState.Roaming | UnitState.RoamingMove);
            _setRandomLocation(creature);

        }
        public override void DoFinalize(T creature)
        {
            creature.ClearUnitState(UnitState.Roaming | UnitState.RoamingMove);
            creature.SetWalk(false);
        }
        public override void DoReset(T creature)
        {
            DoInitialize(creature);
        }

        public override bool DoUpdate(T creature, uint diff)
        {
            if (!creature || !creature.IsAlive())
                return false;

            if (creature.HasUnitState(UnitState.Root | UnitState.Stunned | UnitState.Distracted))
            {
                i_nextMoveTime.Reset(0);  // Expire the timer
                creature.ClearUnitState(UnitState.RoamingMove);
                return true;
            }

            if (creature.moveSpline.Finalized())
            {
                i_nextMoveTime.Update((int)diff);
                if (i_nextMoveTime.Passed())
                    _setRandomLocation(creature);
            }
            return true;
        }

        public void _setRandomLocation(T creature)
        {
            if (creature.HasUnitState(UnitState.Casting) && !creature.CanMoveDuringChannel())
            {
                creature.CastStop();
                return;
            }

            float respX, respY, respZ, respO, destX, destY, destZ, travelDistZ;
            creature.GetHomePosition(out respX, out respY, out respZ, out respO);
            Map map = creature.GetMap();

            bool is_air_ok = creature.CanFly();

            float angle = (float)(RandomHelper.NextDouble() * MathFunctions.TwoPi);
            float range = (float)(RandomHelper.NextDouble() * wander_distance);
            float distanceX = (float)(range * Math.Cos(angle));
            float distanceY = (float)(range * Math.Sin(angle));

            destX = respX + distanceX;
            destY = respY + distanceY;

            // prevent invalid coordinates generation
            GridDefines.NormalizeMapCoord(ref destX);
            GridDefines.NormalizeMapCoord(ref destY);

            travelDistZ = range;   // sin^2+cos^2=1, so travelDistZ=range^2; no need for sqrt below

            if (is_air_ok)                                          // 3D system above ground and above water (flying mode)
            {
                // Limit height change
                float distanceZ = (float)(RandomHelper.NextDouble() * travelDistZ / 2.0f);
                destZ = respZ + distanceZ;
                float levelZ = map.GetWaterOrGroundLevel(creature.GetPhases(), destX, destY, destZ - 2.5f);

                // Problem here, we must fly above the ground and water, not under. Let's try on next tick
                if (levelZ >= destZ)
                    return;
            }
            else                                                    // 2D only
            {
                // 10.0 is the max that vmap high can check (MAX_CAN_FALL_DISTANCE)
                travelDistZ = travelDistZ >= 10.0f ? 10.0f : travelDistZ;

                // The fastest way to get an accurate result 90% of the time.
                // Better result can be obtained like 99% accuracy with a ray light, but the cost is too high and the code is too long.
                destZ = map.GetHeight(creature.GetPhases(), destX, destY, respZ + travelDistZ - 2.0f, false);

                if (Math.Abs(destZ - respZ) > travelDistZ)              // Map check
                {
                    // Vmap Horizontal or above
                    destZ = map.GetHeight(creature.GetPhases(), destX, destY, respZ - 2.0f, true);

                    if (Math.Abs(destZ - respZ) > travelDistZ)
                    {
                        // Vmap Higher
                        destZ = map.GetHeight(creature.GetPhases(), destX, destY, respZ + travelDistZ - 2.0f, true);

                        // let's forget this bad coords where a z cannot be find and retry at next tick
                        if (Math.Abs(destZ - respZ) > travelDistZ)
                            return;
                    }
                }
            }

            if (is_air_ok)
                i_nextMoveTime.Reset(0);
            else
            {
                if (RandomHelper.randChance(50))
                    i_nextMoveTime.Reset(RandomHelper.IRand(5000, 10000));
                else
                    i_nextMoveTime.Reset(RandomHelper.IRand(50, 400));
            }

            creature.AddUnitState(UnitState.RoamingMove);
            if (destZ < 25f)
            {

            }
            MoveSplineInit init = new MoveSplineInit(creature);
            init.MoveTo(destX, destY, destZ);
            init.SetWalk(true);
            init.Launch();

            //Call for creature group update
            if (creature.GetFormation() != null && creature.GetFormation().getLeader() == creature)
                creature.GetFormation().LeaderMoveTo(destX, destY, destZ);
        }

        public virtual bool GetResetPosition(Creature creature, float x, float y, float z) { return false; }
    }
}
