/*
 * 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.Database;
using Framework.IO;
using Game.DataStorage;
using Game.Entities;
using Game.Maps;
using Game.SupportSystem;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;

namespace Game.Chat.Commands
{
    [CommandGroup("go", RBACPermissions.CommandGo)]
    class GoCommands
    {
        [Command("creature", RBACPermissions.CommandGoCreature)]
        static bool HandleGoCreatureCommand(StringArguments args, CommandHandler handler)
        {
            if (args.Empty())
                return false;

            Player player = handler.GetSession().GetPlayer();

            // "id" or number or [name] Shift-click form |color|Hcreature_entry:creature_id|h[name]|h|r
            string param1 = handler.extractKeyFromLink(args, "Hcreature");
            if (string.IsNullOrEmpty(param1))
                return false;

            string whereClause = "";

            // User wants to teleport to the NPC's template entry
            if (param1.Equals("id"))
            {
                // Get the "creature_template.entry"
                // number or [name] Shift-click form |color|Hcreature_entry:creature_id|h[name]|h|r
                string idStr = handler.extractKeyFromLink(args, "Hcreature_entry");
                if (string.IsNullOrEmpty(idStr))
                    return false;

                int entry = int.Parse(idStr);
                if (entry == 0)
                    return false;

                whereClause += "WHERE id = '" + entry + '\'';
            }
            else
            {
                ulong guidLow = ulong.Parse(param1);

                // Number is invalid - maybe the user specified the mob's name
                if (guidLow == 0)
                {
                    string name = param1;
                    whereClause += ", creature_template WHERE creature.id = creature_template.entry AND creature_template.name LIKE '" + name + '\'';
                }
                else
                    whereClause += "WHERE guid = '" + guidLow + '\'';
            }

            SQLResult result = DB.World.Query("SELECT position_x, position_y, position_z, orientation, map FROM creature {0}", whereClause);
            if (result.IsEmpty())
            {
                handler.SendSysMessage(CypherStrings.CommandGocreatnotfound);
                return false;
            }
            if (result.GetRowCount() > 1)
                handler.SendSysMessage(CypherStrings.CommandGocreatmultiple);

            float x = result.Read<float>(0);
            float y = result.Read<float>(1);
            float z = result.Read<float>(2);
            float o = result.Read<float>(3);
            uint mapId = result.Read<ushort>(4);

            if (!GridDefines.IsValidMapCoord(mapId, x, y, z, o) || Global.ObjectMgr.IsTransportMap(mapId))
            {
                handler.SendSysMessage(CypherStrings.InvalidTargetCoord, x, y, mapId);
                return false;
            }

            // stop flight if need
            if (player.IsInFlight())
            {
                player.GetMotionMaster().MovementExpired();
                player.CleanupAfterTaxiFlight();
            }
            // save only in non-flight case
            else
                player.SaveRecallPosition();

            player.TeleportTo(mapId, x, y, z, o);

            return true;
        }

        [Command("graveyard", RBACPermissions.CommandGoGraveyard)]
        static bool HandleGoGraveyardCommand(StringArguments args, CommandHandler handler)
        {
            Player player = handler.GetSession().GetPlayer();

            if (args.Empty())
                return false;

            uint graveyardId = args.NextUInt32();
            if (graveyardId == 0)
                return false;

            WorldSafeLocsRecord gy = CliDB.WorldSafeLocsStorage.LookupByKey(graveyardId);
            if (gy == null)
            {
                handler.SendSysMessage(CypherStrings.CommandGraveyardnoexist, graveyardId);
                return false;
            }

            if (!GridDefines.IsValidMapCoord(gy.MapID, gy.Loc.X, gy.Loc.Y, gy.Loc.Z))
            {
                handler.SendSysMessage(CypherStrings.InvalidTargetCoord, gy.Loc.X, gy.Loc.Y, gy.MapID);
                return false;
            }

            // stop flight if need
            if (player.IsInFlight())
            {
                player.GetMotionMaster().MovementExpired();
                player.CleanupAfterTaxiFlight();
            }
            // save only in non-flight case
            else
                player.SaveRecallPosition();

            player.TeleportTo(gy.MapID, gy.Loc.X, gy.Loc.Y, gy.Loc.Z, (gy.Facing * MathFunctions.PI) / 180); // Orientation is initially in degrees
            return true;
        }

        //teleport to grid
        [Command("grid", RBACPermissions.CommandGoGrid)]
        static bool HandleGoGridCommand(StringArguments args, CommandHandler handler)
        {
            if (args.Empty())
                return false;

            Player player = handler.GetSession().GetPlayer();

            string gridX = args.NextString();
            string gridY = args.NextString();
            string id = args.NextString();

            if (string.IsNullOrEmpty(gridX) || string.IsNullOrEmpty(gridY))
                return false;

            uint mapId = !string.IsNullOrEmpty(id) ? uint.Parse(id) : player.GetMapId();

            // center of grid
            float x = (float.Parse(gridX) - MapConst.CenterGridId + 0.5f) * MapConst.SizeofGrids;
            float y = (float.Parse(gridY) - MapConst.CenterGridId + 0.5f) * MapConst.SizeofGrids;

            if (!GridDefines.IsValidMapCoord(mapId, x, y))
            {
                handler.SendSysMessage(CypherStrings.InvalidTargetCoord, x, y, mapId);
                return false;
            }

            // stop flight if need
            if (player.IsInFlight())
            {
                player.GetMotionMaster().MovementExpired();
                player.CleanupAfterTaxiFlight();
            }
            // save only in non-flight case
            else
                player.SaveRecallPosition();

            Map map = Global.MapMgr.CreateBaseMap(mapId);
            float z = Math.Max(map.GetHeight(x, y, MapConst.MaxHeight), map.GetWaterLevel(x, y));

            player.TeleportTo(mapId, x, y, z, player.GetOrientation());
            return true;
        }

        //teleport to gameobject
        [Command("object", RBACPermissions.CommandGoObject)]
        static bool HandleGoObjectCommand(StringArguments args, CommandHandler handler)
        {
            if (args.Empty())
                return false;

            Player player = handler.GetSession().GetPlayer();

            // number or [name] Shift-click form |color|Hgameobject:go_guid|h[name]|h|r
            string id = handler.extractKeyFromLink(args, "Hgameobject");
            if (string.IsNullOrEmpty(id))
                return false;

            ulong guidLow = ulong.Parse(id);
            if (guidLow == 0)
                return false;

            float x, y, z, o;
            uint mapId;

            // by DB guid
            GameObjectData goData = Global.ObjectMgr.GetGOData(guidLow);
            if (goData != null)
            {
                x = goData.posX;
                y = goData.posY;
                z = goData.posZ;
                o = goData.orientation;
                mapId = goData.mapid;
            }
            else
            {
                handler.SendSysMessage(CypherStrings.CommandGoobjnotfound);
                return false;
            }

            if (!GridDefines.IsValidMapCoord(mapId, x, y, z, o) || Global.ObjectMgr.IsTransportMap(mapId))
            {
                handler.SendSysMessage(CypherStrings.InvalidTargetCoord, x, y, mapId);
                return false;
            }

            // stop flight if need
            if (player.IsInFlight())
            {
                player.GetMotionMaster().MovementExpired();
                player.CleanupAfterTaxiFlight();
            }
            // save only in non-flight case
            else
                player.SaveRecallPosition();

            player.TeleportTo(mapId, x, y, z, o);
            return true;
        }

        [Command("quest", RBACPermissions.CommandGoQuest)]
        static bool HandleGoQuestCommand(StringArguments args, CommandHandler handler)
        {
            if (args.Empty())
                return false;

            Player player = handler.GetSession().GetPlayer();

            string id = handler.extractKeyFromLink(args, "Hquest");
            if (string.IsNullOrEmpty(id))
                return false;

            uint questID = uint.Parse(id);
            if (questID == 0)
                return false;

            if (Global.ObjectMgr.GetQuestTemplate(questID) == null)
            {
                handler.SendSysMessage(CypherStrings.CommandQuestNotfound, questID);
                return false;
            }

            float x, y, z = 0;
            uint mapId = 0;

            var poiData = Global.ObjectMgr.GetQuestPOIList(questID);
            if (poiData != null)
            {
                var data = poiData[0];

                mapId = (uint)data.MapID;

                x = data.points[0].X;
                y = data.points[0].Y;
            }
            else
            {
                handler.SendSysMessage(CypherStrings.CommandQuestNotfound, questID);
                return false;
            }

            if (!GridDefines.IsValidMapCoord(mapId, x, y) || Global.ObjectMgr.IsTransportMap(mapId))
            {
                handler.SendSysMessage(CypherStrings.InvalidTargetCoord, x, y, mapId);
                return false;
            }

            // stop flight if need
            if (player.IsInFlight())
            {
                player.GetMotionMaster().MovementExpired();
                player.CleanupAfterTaxiFlight();
            }
            // save only in non-flight case
            else
                player.SaveRecallPosition();

            Map map = Global.MapMgr.CreateBaseMap(mapId);
            z = Math.Max(map.GetHeight(x, y, MapConst.MaxHeight), map.GetWaterLevel(x, y));

            player.TeleportTo(mapId, x, y, z, 0.0f);
            return true;
        }

        [Command("taxinode", RBACPermissions.CommandGoTaxinode)]
        static bool HandleGoTaxinodeCommand(StringArguments args, CommandHandler handler)
        {
            Player player = handler.GetSession().GetPlayer();

            if (args.Empty())
                return false;

            string id = handler.extractKeyFromLink(args, "Htaxinode");
            if (string.IsNullOrEmpty(id))
                return false;

            uint nodeId = uint.Parse(id);
            if (nodeId == 0)
                return false;

            TaxiNodesRecord node = CliDB.TaxiNodesStorage.LookupByKey(nodeId);
            if (node == null)
            {
                handler.SendSysMessage(CypherStrings.CommandGotaxinodenotfound, nodeId);
                return false;
            }

            if ((node.Pos.X == 0.0f && node.Pos.Y == 0.0f && node.Pos.Z == 0.0f) ||
                !GridDefines.IsValidMapCoord(node.MapID, node.Pos.X, node.Pos.Y, node.Pos.Z))
            {
                handler.SendSysMessage(CypherStrings.InvalidTargetCoord, node.Pos.X, node.Pos.Y, node.MapID);
                return false;
            }

            // stop flight if need
            if (player.IsInFlight())
            {
                player.GetMotionMaster().MovementExpired();
                player.CleanupAfterTaxiFlight();
            }
            // save only in non-flight case
            else
                player.SaveRecallPosition();

            player.TeleportTo(node.MapID, node.Pos.X, node.Pos.Y, node.Pos.Z, player.GetOrientation());
            return true;
        }

        [Command("trigger", RBACPermissions.CommandGoTrigger)]
        static bool HandleGoTriggerCommand(StringArguments args, CommandHandler handler)
        {
            Player player = handler.GetSession().GetPlayer();

            if (args.Empty())
                return false;

            uint areaTriggerId = args.NextUInt32();
            if (areaTriggerId == 0)
                return false;

            AreaTriggerRecord at = CliDB.AreaTriggerStorage.LookupByKey(areaTriggerId);
            if (at == null)
            {
                handler.SendSysMessage(CypherStrings.CommandGoareatrnotfound, areaTriggerId);
                return false;
            }

            if (!GridDefines.IsValidMapCoord(at.MapID, at.Pos.X, at.Pos.Y, at.Pos.Z))
            {
                handler.SendSysMessage(CypherStrings.InvalidTargetCoord, at.Pos.X, at.Pos.Y, at.MapID);
                return false;
            }

            // stop flight if need
            if (player.IsInFlight())
            {
                player.GetMotionMaster().MovementExpired();
                player.CleanupAfterTaxiFlight();
            }
            // save only in non-flight case
            else
                player.SaveRecallPosition();

            player.TeleportTo(at.MapID, at.Pos.X, at.Pos.Y, at.Pos.Z, player.GetOrientation());
            return true;
        }

        //teleport at coordinates
        [Command("zonexy", RBACPermissions.CommandGoZonexy)]
        static bool HandleGoZoneXYCommand(StringArguments args, CommandHandler handler)
        {
            if (args.Empty())
                return false;

            Player player = handler.GetSession().GetPlayer();

            string zoneX = args.NextString();
            string zoneY = args.NextString();

            string id = handler.extractKeyFromLink(args, "Harea");       // string or [name] Shift-click form |color|Harea:area_id|h[name]|h|r

            if (string.IsNullOrEmpty(zoneX) || string.IsNullOrEmpty(zoneY))
                return false;

            float x = float.Parse(zoneX);
            float y = float.Parse(zoneY);

            // prevent accept wrong numeric args
            if ((x == 0.0f && zoneX[0] != '0') || (y == 0.0f && zoneY[0] != '0'))
                return false;

            uint areaId = !string.IsNullOrEmpty(id) ? uint.Parse(id) : player.GetZoneId();

            AreaTableRecord areaEntry = CliDB.AreaTableStorage.LookupByKey(areaId);

            if (x < 0 || x > 100 || y < 0 || y > 100 || areaEntry == null)
            {
                handler.SendSysMessage(CypherStrings.InvalidZoneCoord, x, y, areaId);
                return false;
            }

            // update to parent zone if exist (client map show only zones without parents)
            AreaTableRecord zoneEntry = areaEntry.ParentAreaID != 0 ? CliDB.AreaTableStorage.LookupByKey(areaEntry.ParentAreaID) : areaEntry;
            Contract.Assert(zoneEntry != null);

            Map map = Global.MapMgr.CreateBaseMap(zoneEntry.MapId);

            if (map.Instanceable())
            {
                handler.SendSysMessage(CypherStrings.InvalidZoneMap, areaId, areaEntry.AreaName[handler.GetSessionDbcLocale()], map.GetId(), map.GetMapName());
                return false;
            }

            Global.DB2Mgr.Zone2MapCoordinates(areaEntry.ParentAreaID != 0 ? areaEntry.ParentAreaID : areaId, ref x, ref y);

            if (!GridDefines.IsValidMapCoord(zoneEntry.MapId, x, y))
            {
                handler.SendSysMessage(CypherStrings.InvalidTargetCoord, x, y, zoneEntry.MapId);
                return false;
            }

            // stop flight if need
            if (player.IsInFlight())
            {
                player.GetMotionMaster().MovementExpired();
                player.CleanupAfterTaxiFlight();
            }
            // save only in non-flight case
            else
                player.SaveRecallPosition();

            float z = Math.Max(map.GetHeight(x, y, MapConst.MaxHeight), map.GetWaterLevel(x, y));

            player.TeleportTo(zoneEntry.MapId, x, y, z, player.GetOrientation());
            return true;
        }

        //teleport at coordinates, including Z and orientation
        [Command("xyz", RBACPermissions.CommandGoXyz)]
        static bool HandleGoXYZCommand(StringArguments args, CommandHandler handler)
        {
            if (args.Empty())
                return false;

            Player player = handler.GetSession().GetPlayer();

            string goX = args.NextString();
            string goY = args.NextString();
            string goZ = args.NextString();
            string id = args.NextString();
            string port = args.NextString();

            if (goX.IsEmpty() || goY.IsEmpty())
                return false;

            float x = float.Parse(goX);
            float y = float.Parse(goY);
            float z;
            float ort = !port.IsEmpty() ? float.Parse(port) : player.GetOrientation();
            uint mapId = !id.IsEmpty() ? uint.Parse(id) : player.GetMapId();

            if (!goZ.IsEmpty())
            {
                z = float.Parse(goZ);
                if (!GridDefines.IsValidMapCoord(mapId, x, y, z))
                {
                    handler.SendSysMessage(CypherStrings.InvalidTargetCoord, x, y, mapId);
                    return false;
                }
            }
            else
            {
                if (!GridDefines.IsValidMapCoord(mapId, x, y))
                {
                    handler.SendSysMessage(CypherStrings.InvalidTargetCoord, x, y, mapId);
                    return false;
                }
                Map map = Global.MapMgr.CreateBaseMap(mapId);
                z = Math.Max(map.GetHeight(x, y, MapConst.MaxHeight), map.GetWaterLevel(x, y));
            }

            // stop flight if need
            if (player.IsInFlight())
            {
                player.GetMotionMaster().MovementExpired();
                player.CleanupAfterTaxiFlight();
            }
            // save only in non-flight case
            else
                player.SaveRecallPosition();

            player.TeleportTo(mapId, x, y, z, ort);
            return true;
        }

        [Command("bugticket", RBACPermissions.CommandGoBugTicket)]
        static bool HandleGoBugTicketCommand(StringArguments args, CommandHandler handler)
        {
            return HandleGoTicketCommand<BugTicket>(args, handler);
        }

        [Command("complaintticket", RBACPermissions.CommandGoComplaintTicket)]
        static bool HandleGoComplaintTicketCommand(StringArguments args, CommandHandler handler)
        {
            return HandleGoTicketCommand<ComplaintTicket>(args, handler);
        }

        [Command("suggestionticket", RBACPermissions.CommandGoSuggestionTicket)]
        static bool HandleGoSuggestionTicketCommand(StringArguments args, CommandHandler handler)
        {
            return HandleGoTicketCommand<SuggestionTicket>(args, handler);
        }

        static bool HandleGoTicketCommand<T>(StringArguments args, CommandHandler handler)where T : Ticket
        {
            if (args.Empty())
                return false;

            uint ticketId = args.NextUInt32();
            if (ticketId == 0)
                return false;

            T ticket = Global.SupportMgr.GetTicket<T>(ticketId);
            if (ticket == null)
            {
                handler.SendSysMessage(CypherStrings.CommandTicketnotexist);
                return true;
            }

            Player player = handler.GetSession().GetPlayer();
            if (player.IsInFlight())
            {
                player.GetMotionMaster().MovementExpired();
                player.CleanupAfterTaxiFlight();
            }
            else
                player.SaveRecallPosition();

            ticket.TeleportTo(player);
            return true;
        }

        [Command("offset", RBACPermissions.CommandGoOffset)]
        static bool HandleGoOffsetCommand(StringArguments args, CommandHandler handler)
        {
            if (args.Empty())
                return false;

            Player player = handler.GetSession().GetPlayer();

            string goX = args.NextString();
            string goY = args.NextString();
            string goZ = args.NextString();
            string id = args.NextString();
            string port = args.NextString();

            float x, y, z, o;
            player.GetPosition(out x, out y, out z, out o);
            if (!goX.IsEmpty())
                x += float.Parse(goX);
            if (!goY.IsEmpty())
                y += float.Parse(goY);
            if (!goZ.IsEmpty())
                z += float.Parse(goZ);
            if (!port.IsEmpty())
                o += float.Parse(port);

            if (!GridDefines.IsValidMapCoord(x, y, z, o))
            {
                handler.SendSysMessage(CypherStrings.InvalidTargetCoord, x, y, player.GetMapId());
                return false;
            }

            // stop flight if need
            if (player.IsInFlight())
            {
                player.GetMotionMaster().MovementExpired();
                player.CleanupAfterTaxiFlight();
            }
            // save only in non-flight case
            else
                player.SaveRecallPosition();

            player.TeleportTo(player.GetMapId(), x, y, z, o);
            return true;
        }
    }
}
