/*
 * Copyright (C) 2011 mooege project
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using Mooege.Common.Helpers.Concurrency;
using Mooege.Core.GS.Actors;
using Mooege.Core.GS.Players;

namespace Mooege.Core.GS.Map.Debug
{
    public class DebugNavMesh
    {
        public World World { get; private set; }
        public Player Player { get; private set; }
        public Rect Bounds { get { return World.QuadTree.RootNode.Bounds; } }

        public ConcurrentList<Scene> MasterScenes { get; private set; }
        public ConcurrentList<Scene> SubScenes { get; private set; }
        public ConcurrentList<Rect> UnWalkableCells { get; private set; }
        public ConcurrentList<Rect> WalkableCells { get; private set; }
        public ConcurrentList<Player> Players { get; private set; }
        public ConcurrentList<Monster> Monsters { get; private set; }
        public ConcurrentList<NPC> NPCs { get; private set; }

        public bool DrawMasterScenes;
        public bool DrawSubScenes;
        public bool DrawWalkableCells;
        public bool DrawUnwalkableCells;
        public bool DrawMonsters;
        public bool DrawNPCs;
        public bool DrawPlayers;
        public bool PrintSceneLabels;
        public bool FillCells;
        public bool DrawPlayerProximityCircle;
        public bool DrawPlayerProximityRectangle;

        private readonly Pen _masterScenePen = new Pen(Color.Black, 1.0f);
        private readonly Pen _subScenePen = new Pen(Color.DarkGray, 1.0f);
        private readonly Brush _unwalkableBrush = Brushes.Red;
        private readonly Pen _unwalkablePen = new Pen(Color.Red, 1.0f);
        private readonly Brush _walkableBrush = Brushes.Blue;
        private readonly Pen _walkablePen = new Pen(Color.Blue, 1.0f);
        private readonly Pen _playerProximityPen = new Pen(Brushes.DarkViolet, 2.0f);
        private readonly Font _sceneFont = new Font("Verdana", 7);        

        public DebugNavMesh(World world, Player player = null)
        {
            this.World = world;
            this.Player = player;

            this._subScenePen.DashStyle = System.Drawing.Drawing2D.DashStyle.DashDot;

            this.MasterScenes = new ConcurrentList<Scene>();
            this.SubScenes = new ConcurrentList<Scene>();
            this.UnWalkableCells = new ConcurrentList<Rect>();
            this.WalkableCells = new ConcurrentList<Rect>();
            this.Players = new ConcurrentList<Player>();
            this.Monsters = new ConcurrentList<Monster>();
            this.NPCs = new ConcurrentList<NPC>();           
        }

        #region update

        public void Update(bool processObjectsInAllTheWorld)
        {
            this.MasterScenes.Clear();
            this.SubScenes.Clear();
            this.WalkableCells.Clear();
            this.UnWalkableCells.Clear();
            this.Players.Clear();
            this.Monsters.Clear();
            this.NPCs.Clear();

            var scenes = (processObjectsInAllTheWorld || this.Player == null)
                                        ? World.QuadTree.Query<Scene>(World.QuadTree.RootNode.Bounds)
                                        : this.Player.GetScenesInRegion();

            Parallel.ForEach(scenes, scene =>
            {
                if (scene.Parent == null)
                    this.MasterScenes.Add(scene);
                else
                    this.SubScenes.Add(scene);

                this.AnalyzeScene(scene);
            });

            var actors = (processObjectsInAllTheWorld || this.Player == null)
                        ? World.QuadTree.Query<Actor>(World.QuadTree.RootNode.Bounds)
                        : this.Player.GetActorsInRange();

            Parallel.ForEach(actors, actor =>
            {
                if (actor is Player)
                    this.Players.Add(actor as Player);
                else if (actor is NPC)
                    this.NPCs.Add(actor as NPC);
                else if (actor is Monster)
                    this.Monsters.Add(actor as Monster);
            });
        }

        private void AnalyzeScene(Scene scene)
        {
            Parallel.ForEach(scene.NavZone.NavCells, cell =>
            {
                float x = scene.Position.X + cell.Min.X;
                float y = scene.Position.Y + cell.Min.Y;

                float sizex = cell.Max.X - cell.Min.X;
                float sizey = cell.Max.Y - cell.Min.Y;

                var rect = new Rect(x, y, sizex, sizey);

                // TODO: Feature request: Also allow drawing of NavCellFlags.NOSpawn, NavCellFlags.LevelAreaBit0, NavCellFlags.LevelAreaBit1 cells. /raist.

                if ((cell.Flags & Mooege.Common.MPQ.FileFormats.Scene.NavCellFlags.AllowWalk) != Mooege.Common.MPQ.FileFormats.Scene.NavCellFlags.AllowWalk)
                    UnWalkableCells.Add(rect);
                else
                    WalkableCells.Add(rect);
            });
        }

        #endregion

        #region drawing 

        public Bitmap Draw()
        {
            // As quad-tree always has 4 quad-nodes beneath the root node, the quad node's area will be far larger then actual area covered by scenes.
            // We don't want to draw a bitmap that's as large as quad-tree's area, as it'll be consuming so much memory.
            // So instead find the rightMostScene and bottomMostScene and limit the drawed bitmaps size according. /raist.
            // TODO: We can even limit to leftMostScene and topMostScene because player-proximity rendering mode will be also containing large empty areas. /raist.

            Scene rightMostScene = null;
            Scene bottomMostScene = null;

            foreach (var scene in this.MasterScenes)
            {
                if (rightMostScene == null)
                    rightMostScene = scene;

                if (bottomMostScene == null)
                    bottomMostScene = scene;

                if (scene.Bounds.X + scene.Bounds.Width > rightMostScene.Bounds.X + rightMostScene.Bounds.Width)
                    rightMostScene = scene;

                if (scene.Bounds.Y + scene.Bounds.Height > bottomMostScene.Bounds.Y + bottomMostScene.Bounds.Height)
                    bottomMostScene = scene;
            }

            if (rightMostScene == null || bottomMostScene == null) 
                return null;

            var maxX = (int) (rightMostScene.Bounds.X + rightMostScene.Bounds.Width) + 1;
            var maxY = (int)(bottomMostScene.Bounds.Y + bottomMostScene.Bounds.Height) + 1;

            var bitmap = new Bitmap(maxX, maxY, System.Drawing.Imaging.PixelFormat.Format16bppRgb555);

            using (var graphics = Graphics.FromImage(bitmap))
            {
                graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
                graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed;
                graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighSpeed;
                graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Default;

                graphics.FillRectangle(Brushes.LightGray, 0, 0, bitmap.Width, bitmap.Height);

                this.DrawShapes(graphics);

                if (this.PrintSceneLabels)
                    this.DrawLabels(graphics);

                graphics.Save();
            }

            return bitmap;
        }

        private void DrawShapes(Graphics graphics)
        {
            if (this.DrawMasterScenes)
            {
                foreach (var scene in this.MasterScenes)
                {
                    this.DrawScene(graphics, scene);
                }
            }

            if (this.DrawSubScenes)
            {
                foreach (var scene in this.SubScenes)
                {
                    this.DrawScene(graphics, scene);
                }
            }

            if (this.DrawWalkableCells)
                this.DrawWalkables(graphics);

            if (this.DrawUnwalkableCells)
                this.DrawUnwalkables(graphics);

            if (this.DrawMonsters)
            {
                foreach (var monster in this.Monsters)
                {
                    this.DrawActor(monster, graphics, Brushes.Green, 7);
                }
            }

            if (this.DrawNPCs)
            {
                foreach (var npc in this.NPCs)
                {
                    this.DrawActor(npc, graphics, Brushes.Orange, 7);
                }
            }

            if (this.DrawPlayers)
            {
                foreach (var player in this.Players)
                {
                    this.DrawActor(player, graphics, Brushes.DarkViolet, 7);
                }
            }

            if(this.DrawPlayerProximityCircle)
                this.DrawProximityCircle(graphics);

            if(this.DrawPlayerProximityRectangle)
                this.DrawProximityRectangle(graphics);
        }

        private void DrawScene(Graphics graphics, Scene scene)
        {
            var rect = new Rectangle((int)scene.Bounds.Left, (int)scene.Bounds.Top, (int)scene.Bounds.Width, (int)scene.Bounds.Height);
            graphics.DrawRectangle(scene.Parent == null ? _masterScenePen : _subScenePen, rect);
        }

        private void DrawWalkables(Graphics graphics)
        {
            foreach (var cell in this.WalkableCells)
            {
                var rect = new Rectangle(new System.Drawing.Point((int)cell.Left, (int)cell.Top), new System.Drawing.Size((int)cell.Width, (int)cell.Height));
                
                if (this.FillCells)
                    graphics.FillRectangle(_walkableBrush, rect);
                else
                    graphics.DrawRectangle(_walkablePen, rect);
            }
        }

        private void DrawUnwalkables(Graphics graphics)
        {
            foreach (var cell in this.UnWalkableCells)
            {
                var rect = new Rectangle(new System.Drawing.Point((int)cell.Left, (int)cell.Top), new System.Drawing.Size((int)cell.Width, (int)cell.Height));
                
                if (this.FillCells)
                    graphics.FillRectangle(_unwalkableBrush, rect);
                else
                    graphics.DrawRectangle(_unwalkablePen, rect);
            }
        }

        private void DrawActor(Actor actor, Graphics graphics, Brush brush, int radius)
        {
            var rect = new Rectangle((int)actor.Bounds.X, (int)actor.Bounds.Y, (int)actor.Bounds.Width + radius, (int)actor.Bounds.Height + radius);
            graphics.FillEllipse(brush, rect);
        }

        private void DrawProximityCircle(Graphics graphics)
        {
            if (this.Player == null) 
                return;

            var rect = new RectangleF(this.Player.Position.X - Actor.DefaultQueryProximityRadius,
                                      this.Player.Position.Y - Actor.DefaultQueryProximityRadius,
                                      Actor.DefaultQueryProximityRadius * 2,
                                      Actor.DefaultQueryProximityRadius * 2);

            graphics.DrawEllipse(this._playerProximityPen, rect);
        }

        private void DrawProximityRectangle(Graphics graphics)
        {
            if (this.Player == null)
                return;

            graphics.DrawRectangle(this._playerProximityPen,
                                   this.Player.Position.X - Actor.DefaultQueryProximityLenght/2,
                                   this.Player.Position.Y - Actor.DefaultQueryProximityLenght/2,
                                   Actor.DefaultQueryProximityLenght,
                                   Actor.DefaultQueryProximityLenght);
        }

        private void DrawLabels(Graphics graphics)
        {
            if (this.DrawMasterScenes)
            {
                foreach (var scene in this.MasterScenes)
                {
                    this.DrawSceneLabel(graphics, scene);
                }
            }

            if (this.DrawSubScenes)
            {
                foreach (var scene in this.SubScenes)
                {
                    this.DrawSceneLabel(graphics, scene);
                }
            }
        }

        private void DrawSceneLabel(Graphics graphics, Scene scene)
        {
            var stringRectangle = new RectangleF((float)scene.Bounds.Left, (float)scene.Bounds.Top, (float)scene.Bounds.Width, (float)scene.Bounds.Height);
            var drawFormat = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };

            if (!string.IsNullOrEmpty(scene.SceneSNO.Name))
                graphics.DrawString(scene.SceneSNO.Name, _sceneFont, scene.Parent == null ? Brushes.Black : Brushes.Gray, stringRectangle, drawFormat);
        }

        #endregion
    }
}