/**
 * MaNGOS is a full featured server for World of Warcraft, supporting
 * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8
 *
 * Copyright (C) 2005-2022 MaNGOS <https://getmangos.eu>
 *
 * 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
 *
 * World of Warcraft, and all World of Warcraft or Warcraft art, images,
 * and lore are copyrighted by Blizzard Entertainment, Inc.
 */

#include "Common.h"
#include "DBCStores.h"
#include "WorldPacket.h"
#include "Player.h"
#include "Opcodes.h"
#include "Chat.h"
#include "Log.h"
#include "Unit.h"
#include "GossipDef.h"
#include "Language.h"
#include "BattleGround/BattleGroundMgr.h"
#include <fstream>
#include "ObjectMgr.h"
#include "ObjectGuid.h"
#include "SpellMgr.h"

/**********************************************************************
     CommandTable : debugCommandTable
/***********************************************************************/

bool ChatHandler::HandleDebugSendSpellFailCommand(char* args)
{
    if (!*args)
    {
        return false;
    }

    uint32 failnum;
    if (!ExtractUInt32(&args, failnum) || failnum > 255)
    {
        return false;
    }

    uint32 failarg1;
    if (!ExtractOptUInt32(&args, failarg1, 0))
    {
        return false;
    }

    uint32 failarg2;
    if (!ExtractOptUInt32(&args, failarg2, 0))
    {
        return false;
    }

    WorldPacket data(SMSG_CAST_FAILED, 4 + 1 + 1);
    data << uint32(133);
    data << uint8(2);
    data << uint8(failnum);
    if (failarg1 || failarg2)
    {
        data << uint32(failarg1);
    }
    if (failarg2)
    {
        data << uint32(failarg2);
    }

    m_session->SendPacket(&data);

    return true;
}

bool ChatHandler::HandleDebugSendPoiCommand(char* args)
{
    Player* pPlayer = m_session->GetPlayer();
    Unit* target = getSelectedUnit();
    if (!target)
    {
        SendSysMessage(LANG_SELECT_CHAR_OR_CREATURE);
        return true;
    }

    uint32 icon;
    if (!ExtractUInt32(&args, icon))
    {
        return false;
    }

    uint32 flags;
    if (!ExtractUInt32(&args, flags))
    {
        return false;
    }

    DETAIL_LOG("Command : POI, NPC = %u, icon = %u flags = %u", target->GetGUIDLow(), icon, flags);
    pPlayer->PlayerTalkClass->SendPointOfInterest(target->GetPositionX(), target->GetPositionY(), Poi_Icon(icon), flags, 30, "Test POI");
    return true;
}

bool ChatHandler::HandleDebugSendEquipErrorCommand(char* args)
{
    if (!*args)
    {
        return false;
    }

    uint8 msg = atoi(args);
    m_session->GetPlayer()->SendEquipError(InventoryResult(msg), NULL, NULL);
    return true;
}

bool ChatHandler::HandleDebugSendSellErrorCommand(char* args)
{
    if (!*args)
    {
        return false;
    }

    uint8 msg = atoi(args);
    m_session->GetPlayer()->SendSellError(SellResult(msg), 0, ObjectGuid(), 0);
    return true;
}

bool ChatHandler::HandleDebugSendBuyErrorCommand(char* args)
{
    if (!*args)
    {
        return false;
    }

    uint8 msg = atoi(args);
    m_session->GetPlayer()->SendBuyError(BuyResult(msg), 0, 0, 0);
    return true;
}

bool ChatHandler::HandleDebugRecvOpcodeCommand(char* /*args*/)
{
    Unit* unit = getSelectedUnit();
    if (!unit || (unit->GetTypeId() != TYPEID_PLAYER))
    {
        unit = m_session->GetPlayer();
    }

    std::ifstream stream("ropcode.txt");
    if (!stream.is_open())
    {
        return false;
    }

    uint32 opcode = 0;
    if (!(stream >> opcode))
    {
        stream.close();
        return false;
    }

    WorldPacket *data = new WorldPacket(opcode, 10);

    std::string type;
    while (stream >> type)
    {
        if (type.empty())
        {
            break;
        }

        if (type == "uint8")
        {
            uint16 value;
            stream >> value;
            *data << uint8(value);
        }
        else if (type == "uint16")
        {
            uint16 value;
            stream >> value;
            *data << value;
        }
        else if (type == "uint32")
        {
            uint32 value;
            stream >> value;
            *data << value;
        }
        else if (type == "uint64")
        {
            uint64 value;
            stream >> value;
            *data << value;
        }
        else if (type == "float")
        {
            float value;
            stream >> value;
            *data << value;
        }
        else if (type == "string")
        {
            std::string value;
            stream >> value;
            *data << value;
        }
        else if (type == "pguid")
        {
            *data << unit->GetPackGUID();
        }
        else if (type == "guid")
        {
            *data << unit->GetObjectGuid();
        }
        else if (type == "mypguid")
        {
            *data << m_session->GetPlayer()->GetPackGUID();
        }
        else if (type == "myguid")
        {
            *data << m_session->GetPlayer()->GetObjectGuid();
        }
        else if (type == "name")
        {
            *data << unit->GetName();
        }
        else if (type == "myname")
        {
            *data << m_session->GetPlayerName();
        }
        else
        {
            DEBUG_LOG("Sending opcode: unknown type '%s'", type.c_str());
            break;
        }
    }
    stream.close();

    DEBUG_LOG("Queued opcode %u, %s", data->GetOpcode(), data->GetOpcodeName());

    m_session->QueuePacket(data);

    PSendSysMessage(LANG_COMMAND_OPCODEGOT, data->GetOpcode(), unit->GetName());
    return true;
}

bool ChatHandler::HandleDebugSendOpcodeCommand(char* /*args*/)
{
    Unit* unit = getSelectedUnit();
    if (!unit || (unit->GetTypeId() != TYPEID_PLAYER))
    {
        unit = m_session->GetPlayer();
    }

    std::ifstream stream("opcode.txt");
    if (!stream.is_open())
    {
        return false;
    }

    uint32 opcode = 0;
    if (!(stream >> opcode))
    {
        stream.close();
        return false;
    }

    WorldPacket data(opcode, 0);

    std::string type;
    while (stream >> type)
    {
        if (type.empty())
        {
            break;
        }

        if (type == "uint8")
        {
            uint16 value;
            stream >> value;
            data << uint8(value);
        }
        else if (type == "uint16")
        {
            uint16 value;
            stream >> value;
            data << value;
        }
        else if (type == "uint32")
        {
            uint32 value;
            stream >> value;
            data << value;
        }
        else if (type == "uint64")
        {
            uint64 value;
            stream >> value;
            data << value;
        }
        else if (type == "float")
        {
            float value;
            stream >> value;
            data << value;
        }
        else if (type == "string")
        {
            std::string value;
            stream >> value;
            data << value;
        }
        else if (type == "pguid")
        {
            data << unit->GetPackGUID();
        }
        else if (type == "guid")
        {
            data << unit->GetObjectGuid();
        }
        else if(type == "mypguid")
        {
            data << m_session->GetPlayer()->GetPackGUID();
        }
        else if (type == "myguid")
        {
            data << m_session->GetPlayer()->GetObjectGuid();
        }
        else if (type == "name")
        {
            data << unit->GetName();
        }
        else if (type == "myname")
        {
            data << m_session->GetPlayerName();
        }
        else
        {
            DEBUG_LOG("Sending opcode: unknown type '%s'", type.c_str());
            break;
        }
    }
    stream.close();

    DEBUG_LOG("Sending opcode %u, %s", data.GetOpcode(), data.GetOpcodeName());

    data.hexlike();
    unit->ToPlayer()->SendDirectMessage(&data);

    PSendSysMessage(LANG_COMMAND_OPCODESENT, data.GetOpcode(), unit->GetName());

    return true;
}

bool ChatHandler::HandleDebugUpdateWorldStateCommand(char* args)
{
    uint32 world;
    if (!ExtractUInt32(&args, world))
    {
        return false;
    }

    uint32 state;
    if (!ExtractUInt32(&args, state))
    {
        return false;
    }

    m_session->GetPlayer()->SendUpdateWorldState(world, state);
    return true;
}

bool ChatHandler::HandleDebugPlayCinematicCommand(char* args)
{
    // USAGE: .debug play cinematic #cinematicid
    // #cinematicid - ID decimal number from CinemaicSequences.dbc (1st column)
    uint32 dwId;
    if (!ExtractUInt32(&args, dwId))
    {
        return false;
    }

    if (!sCinematicSequencesStore.LookupEntry(dwId))
    {
        PSendSysMessage(LANG_CINEMATIC_NOT_EXIST, dwId);
        SetSentErrorMessage(true);
        return false;
    }

    m_session->GetPlayer()->SendCinematicStart(dwId);
    return true;
}

// Play sound
bool ChatHandler::HandleDebugPlaySoundCommand(char* args)
{
    // USAGE: .debug playsound #soundid
    // #soundid - ID decimal number from SoundEntries.dbc (1st column)
    uint32 dwSoundId;
    if (!ExtractUInt32(&args, dwSoundId))
    {
        return false;
    }

    if (!sSoundEntriesStore.LookupEntry(dwSoundId))
    {
        PSendSysMessage(LANG_SOUND_NOT_EXIST, dwSoundId);
        SetSentErrorMessage(true);
        return false;
    }

    Unit* unit = getSelectedUnit();
    if (!unit)
    {
        SendSysMessage(LANG_SELECT_CHAR_OR_CREATURE);
        SetSentErrorMessage(true);
        return false;
    }

    if (m_session->GetPlayer()->GetSelectionGuid())
    {
        unit->PlayDistanceSound(dwSoundId, m_session->GetPlayer());
    }
    else
    {
        unit->PlayDirectSound(dwSoundId, m_session->GetPlayer());
    }

    PSendSysMessage(LANG_YOU_HEAR_SOUND, dwSoundId);
    return true;
}

// Send notification in channel
bool ChatHandler::HandleDebugSendChannelNotifyCommand(char* args)
{
    const char* name = "test";

    uint32 code;
    if (!ExtractUInt32(&args, code) || code > 255)
    {
        return false;
    }

    WorldPacket data(SMSG_CHANNEL_NOTIFY, (1 + 10));
    data << uint8(code);                                    // notify type
    data << name;                                           // channel name
    data << uint32(0);
    data << uint32(0);
    m_session->SendPacket(&data);
    return true;
}

// Send notification in chat
bool ChatHandler::HandleDebugSendChatMsgCommand(char* args)
{
    const char* msg = args;

    uint32 type;
    if (!ExtractUInt32(&args, type) || type > 255)
    {
        return false;
    }

    WorldPacket data;
    ChatHandler::BuildChatPacket(data, ChatMsg(type), msg, LANG_UNIVERSAL, CHAT_TAG_NONE, m_session->GetPlayer()->GetObjectGuid(), m_session->GetPlayerName());
    m_session->SendPacket(&data);
    return true;
}

bool ChatHandler::HandleDebugSendQuestPartyMsgCommand(char* args)
{
    uint32 msg;
    if (!ExtractUInt32(&args, msg))
    {
        return false;
    }
    if (msg > 0xFF)
    {
        return false;
    }

    m_session->GetPlayer()->SendPushToPartyResponse(m_session->GetPlayer(), uint8(msg));
    return true;
}

bool ChatHandler::HandleDebugGetLootRecipientCommand(char* /*args*/)
{
    Creature* target = getSelectedCreature();
    if (!target)
    {
        return false;
    }

    if (!target->HasLootRecipient())
    {
        SendSysMessage("loot recipient: no loot recipient");
    }
    else if (Player* recipient = target->GetLootRecipient())
        PSendSysMessage("loot recipient: %s with raw data %s from group %u",
                        recipient->GetGuidStr().c_str(),
                        target->GetLootRecipientGuid().GetString().c_str(),
                        target->GetLootGroupRecipientId());
    else
    {
        SendSysMessage("loot recipient: offline ");
    }

    return true;
}

bool ChatHandler::HandleDebugSendQuestInvalidMsgCommand(char* args)
{
    uint32 msg = atol(args);
    m_session->GetPlayer()->SendCanTakeQuestResponse(msg);
    return true;
}

bool ChatHandler::HandleDebugGetItemStateCommand(char* args)
{
    if (!*args)
    {
        return false;
    }

    ItemUpdateState state = ITEM_UNCHANGED;
    bool list_queue = false, check_all = false;

    std::string state_str;

    if (strncmp(args, "unchanged", strlen(args)) == 0)
    {
        state = ITEM_UNCHANGED;
        state_str = "unchanged";
    }
    else if (strncmp(args, "changed", strlen(args)) == 0)
    {
        state = ITEM_CHANGED;
        state_str = "changed";
    }
    else if (strncmp(args, "new", strlen(args)) == 0)
    {
        state = ITEM_NEW;
        state_str = "new";
    }
    else if (strncmp(args, "removed", strlen(args)) == 0)
    {
        state = ITEM_REMOVED;
        state_str = "removed";
    }
    else if (strncmp(args, "queue", strlen(args)) == 0)
    {
        list_queue = true;
    }
    else if (strncmp(args, "all", strlen(args)) == 0)
    {
        check_all = true;
    }
    else
    {
        return false;
    }

    Player* player = getSelectedPlayer();
    if (!player)
    {
        player = m_session->GetPlayer();
    }

    if (!list_queue && !check_all)
    {
        state_str = "The player has the following " + state_str + " items: ";
        SendSysMessage(state_str.c_str());
        for (uint8 i = PLAYER_SLOT_START; i < PLAYER_SLOT_END; ++i)
        {
            if (i >= BUYBACK_SLOT_START && i < BUYBACK_SLOT_END)
            {
                continue;
            }

            Item* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i);
            if (!item)
            {
                continue;
            }
            if (!item->IsBag())
            {
                if (item->GetState() == state)
                    PSendSysMessage("%s bag: 255 slot: %u owner: %s",
                                    item->GetGuidStr().c_str(),  item->GetSlot(), item->GetOwnerGuid().GetString().c_str());
            }
            else
            {
                Bag* bag = (Bag*)item;
                for (uint8 j = 0; j < bag->GetBagSize(); ++j)
                {
                    Item* item2 = bag->GetItemByPos(j);
                    if (item2 && item2->GetState() == state)
                        PSendSysMessage("%s bag: %u slot: %u owner: %s",
                                        item2->GetGuidStr().c_str(), item2->GetBagSlot(), item2->GetSlot(),
                                        item2->GetOwnerGuid().GetString().c_str());
                }
            }
        }
    }

    if (list_queue)
    {
        std::vector<Item*>& updateQueue = player->GetItemUpdateQueue();
        for (size_t i = 0; i < updateQueue.size(); ++i)
        {
            Item* item = updateQueue[i];
            if (!item)
            {
                continue;
            }

            Bag* container = item->GetContainer();
            uint8 bag_slot = container ? container->GetSlot() : uint8(INVENTORY_SLOT_BAG_0);

            std::string st;
            switch (item->GetState())
            {
                case ITEM_UNCHANGED: st = "unchanged"; break;
                case ITEM_CHANGED: st = "changed"; break;
                case ITEM_NEW: st = "new"; break;
                case ITEM_REMOVED: st = "removed"; break;
            }

            PSendSysMessage("%s bag: %u slot: %u - state: %s",
                            item->GetGuidStr().c_str(), bag_slot, item->GetSlot(), st.c_str());
        }
        if (updateQueue.empty())
        {
            PSendSysMessage("updatequeue empty");
        }
    }

    if (check_all)
    {
        bool error = false;
        std::vector<Item*>& updateQueue = player->GetItemUpdateQueue();
        for (uint8 i = PLAYER_SLOT_START; i < PLAYER_SLOT_END; ++i)
        {
            if (i >= BUYBACK_SLOT_START && i < BUYBACK_SLOT_END)
            {
                continue;
            }

            Item* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i);
            if (!item)
            {
                continue;
            }

            if (item->GetSlot() != i)
            {
                PSendSysMessage("%s at slot %u has an incorrect slot value: %d",
                                item->GetGuidStr().c_str(), i, item->GetSlot());
                error = true; continue;
            }

            if (item->GetOwnerGuid() != player->GetObjectGuid())
            {
                PSendSysMessage("%s at slot %u owner (%s) and inventory owner (%s) don't match!",
                                item->GetGuidStr().c_str(), item->GetSlot(),
                                item->GetOwnerGuid().GetString().c_str(), player->GetGuidStr().c_str());
                error = true; continue;
            }

            if (Bag* container = item->GetContainer())
            {
                PSendSysMessage("%s at slot %u has a container %s from slot %u but shouldnt!",
                                item->GetGuidStr().c_str(), item->GetSlot(),
                                container->GetGuidStr().c_str(), container->GetSlot());
                error = true; continue;
            }

            if (item->IsInUpdateQueue())
            {
                uint16 qp = item->GetQueuePos();
                if (qp > updateQueue.size())
                {
                    PSendSysMessage("%s at slot %u has a queuepos (%d) larger than the update queue size! ",
                                    item->GetGuidStr().c_str(), item->GetSlot(), qp);
                    error = true; continue;
                }

                if (updateQueue[qp] == NULL)
                {
                    PSendSysMessage("%s at slot %u has a queuepos (%d) that points to NULL in the queue!",
                                    item->GetGuidStr().c_str(), item->GetSlot(), qp);
                    error = true; continue;
                }

                if (updateQueue[qp] != item)
                {
                    PSendSysMessage("%s at slot %u has a queuepos (%d) that points to %s in the queue (bag %u, slot %u)",
                                    item->GetGuidStr().c_str(), item->GetSlot(), qp,
                                    updateQueue[qp]->GetGuidStr().c_str(), updateQueue[qp]->GetBagSlot(), updateQueue[qp]->GetSlot());
                    error = true; continue;
                }
            }
            else if (item->GetState() != ITEM_UNCHANGED)
            {
                PSendSysMessage("%s at slot %u is not in queue but should be (state: %d)!",
                                item->GetGuidStr().c_str(), item->GetSlot(), item->GetState());
                error = true; continue;
            }

            if (item->IsBag())
            {
                Bag* bag = (Bag*)item;
                for (uint8 j = 0; j < bag->GetBagSize(); ++j)
                {
                    Item* item2 = bag->GetItemByPos(j);
                    if (!item2)
                    {
                        continue;
                    }

                    if (item2->GetSlot() != j)
                    {
                        PSendSysMessage("%s in bag %u at slot %u has an incorrect slot value: %u",
                                        item2->GetGuidStr().c_str(), bag->GetSlot(), j, item2->GetSlot());
                        error = true; continue;
                    }

                    if (item2->GetOwnerGuid() != player->GetObjectGuid())
                    {
                        PSendSysMessage("%s in bag %u at slot %u owner (%s) and inventory owner (%s) don't match!",
                                        item2->GetGuidStr().c_str(), bag->GetSlot(), item2->GetSlot(),
                                        item2->GetOwnerGuid().GetString().c_str(), player->GetGuidStr().c_str());
                        error = true; continue;
                    }

                    Bag* container = item2->GetContainer();
                    if (!container)
                    {
                        PSendSysMessage("%s in bag %u at slot %u has no container!",
                                        item2->GetGuidStr().c_str(), bag->GetSlot(), item2->GetSlot());
                        error = true; continue;
                    }

                    if (container != bag)
                    {
                        PSendSysMessage("%s in bag %u at slot %u has a different container %s from slot %u!",
                                        item2->GetGuidStr().c_str(), bag->GetSlot(), item2->GetSlot(),
                                        container->GetGuidStr().c_str(), container->GetSlot());
                        error = true; continue;
                    }

                    if (item2->IsInUpdateQueue())
                    {
                        uint16 qp = item2->GetQueuePos();
                        if (qp > updateQueue.size())
                        {
                            PSendSysMessage("%s in bag %u at slot %u has a queuepos (%d) larger than the update queue size! ",
                                            item2->GetGuidStr().c_str(), bag->GetSlot(), item2->GetSlot(), qp);
                            error = true; continue;
                        }

                        if (updateQueue[qp] == NULL)
                        {
                            PSendSysMessage("%s in bag %u at slot %u has a queuepos (%d) that points to NULL in the queue!",
                                            item2->GetGuidStr().c_str(), bag->GetSlot(), item2->GetSlot(), qp);
                            error = true; continue;
                        }

                        if (updateQueue[qp] != item2)
                        {
                            PSendSysMessage("%s in bag %u at slot %u has a queuepos (%d) that points to %s in the queue (bag %u slot %u)",
                                            item2->GetGuidStr().c_str(), bag->GetSlot(), item2->GetSlot(), qp,
                                            updateQueue[qp]->GetGuidStr().c_str(), updateQueue[qp]->GetBagSlot(), updateQueue[qp]->GetSlot());
                            error = true; continue;
                        }
                    }
                    else if (item2->GetState() != ITEM_UNCHANGED)
                    {
                        PSendSysMessage("%s in bag %u at slot %u is not in queue but should be (state: %d)!",
                                        item2->GetGuidStr().c_str(), bag->GetSlot(), item2->GetSlot(), item2->GetState());
                        error = true; continue;
                    }
                }
            }
        }

        for (size_t i = 0; i < updateQueue.size(); ++i)
        {
            Item* item = updateQueue[i];
            if (!item)
            {
                continue;
            }

            if (item->GetOwnerGuid() != player->GetObjectGuid())
            {
                PSendSysMessage("queue(" SIZEFMTD "): %s has the owner (%s) and inventory owner (%s) don't match!",
                                i, item->GetGuidStr().c_str(),
                                item->GetOwnerGuid().GetString().c_str(), player->GetGuidStr().c_str());
                error = true; continue;
            }

            if (item->GetQueuePos() != i)
            {
                PSendSysMessage("queue(" SIZEFMTD "): %s has queuepos doesn't match it's position in the queue!",
                                i, item->GetGuidStr().c_str());
                error = true; continue;
            }

            if (item->GetState() == ITEM_REMOVED)
            {
                continue;
            }
            Item* test = player->GetItemByPos(item->GetBagSlot(), item->GetSlot());

            if (test == NULL)
            {
                PSendSysMessage("queue(" SIZEFMTD "): %s has incorrect (bag %u slot %u) values, the player doesn't have an item at that position!",
                                i, item->GetGuidStr().c_str(), item->GetBagSlot(), item->GetSlot());
                error = true; continue;
            }

            if (test != item)
            {
                PSendSysMessage("queue(" SIZEFMTD "): %s has incorrect (bag %u slot %u) values, the %s is there instead!",
                                i, item->GetGuidStr().c_str(), item->GetBagSlot(), item->GetSlot(),
                                test->GetGuidStr().c_str());
                error = true; continue;
            }
        }
        if (!error)
        {
            SendSysMessage("All OK!");
        }
    }

    return true;
}

bool ChatHandler::HandleDebugBattlegroundCommand(char* /*args*/)
{
    sBattleGroundMgr.ToggleTesting();
    return true;
}

bool ChatHandler::HandleDebugSpellCheckCommand(char* /*args*/)
{
    sLog.outString("Check expected in code spell properties base at table 'spell_check' content...");
    sSpellMgr.CheckUsedSpells("spell_check");
    return true;
}

// show animation
bool ChatHandler::HandleDebugAnimCommand(char* args)
{
    uint32 emote_id;
    if (!ExtractUInt32(&args, emote_id))
    {
        return false;
    }

    m_session->GetPlayer()->HandleEmoteCommand(emote_id);
    return true;
}

bool ChatHandler::HandleDebugSetAuraStateCommand(char* args)
{
    int32 state;
    if (!ExtractInt32(&args, state))
    {
        return false;
    }

    Unit* unit = getSelectedUnit();
    if (!unit)
    {
        SendSysMessage(LANG_SELECT_CHAR_OR_CREATURE);
        SetSentErrorMessage(true);
        return false;
    }

    if (!state)
    {
        // reset all states
        for (int i = 1; i <= 32; ++i)
        {
            unit->ModifyAuraState(AuraState(i), false);
        }
        return true;
    }

    unit->ModifyAuraState(AuraState(abs(state)), state > 0);
    return true;
}

bool ChatHandler::HandleSetValueHelper(Object* target, uint32 field, char* typeStr, char* valStr)
{
    ObjectGuid guid = target->GetObjectGuid();

    // not allow access to nonexistent or critical for work field
    if (field >= target->GetValuesCount() || field <= OBJECT_FIELD_ENTRY)
    {
        PSendSysMessage(LANG_TOO_BIG_INDEX, field, guid.GetString().c_str(), target->GetValuesCount());
        return false;
    }

    uint32 base;                                            // 0 -> float
    if (!typeStr)
    {
        base = 10;
    }
    else if (strncmp(typeStr, "int", strlen(typeStr)) == 0)
    {
        base = 10;
    }
    else if (strncmp(typeStr, "hex", strlen(typeStr)) == 0)
    {
        base = 16;
    }
    else if (strncmp(typeStr, "bit", strlen(typeStr)) == 0)
    {
        base = 2;
    }
    else if (strncmp(typeStr, "float", strlen(typeStr)) == 0)
    {
        base = 0;
    }
    else
    {
        return false;
    }

    if (base)
    {
        uint32 iValue;
        if (!ExtractUInt32Base(&valStr, iValue, base))
        {
            return false;
        }

        DEBUG_LOG(GetMangosString(LANG_SET_UINT), guid.GetString().c_str(), field, iValue);
        target->SetUInt32Value(field , iValue);
        PSendSysMessage(LANG_SET_UINT_FIELD, guid.GetString().c_str(), field, iValue);
    }
    else
    {
        float fValue;
        if (!ExtractFloat(&valStr, fValue))
        {
            return false;
        }

        DEBUG_LOG(GetMangosString(LANG_SET_FLOAT), guid.GetString().c_str(), field, fValue);
        target->SetFloatValue(field , fValue);
        PSendSysMessage(LANG_SET_FLOAT_FIELD, guid.GetString().c_str(), field, fValue);
    }

    return true;
}

bool ChatHandler::HandleDebugSetItemValueCommand(char* args)
{
    uint32 guid;
    if (!ExtractUInt32(&args, guid))
    {
        return false;
    }

    uint32 field;
    if (!ExtractUInt32(&args, field))
    {
        return false;
    }

    char* typeStr = ExtractOptNotLastArg(&args);
    if (!typeStr)
    {
        return false;
    }

    char* valStr = ExtractLiteralArg(&args);
    if (!valStr)
    {
        return false;
    }

    Item* item = m_session->GetPlayer()->GetItemByGuid(ObjectGuid(HIGHGUID_ITEM, guid));
    if (!item)
    {
        return false;
    }

    return HandleSetValueHelper(item, field, typeStr, valStr);
}

bool ChatHandler::HandleDebugSetValueCommand(char* args)
{
    Unit* target = getSelectedUnit();
    if (!target)
    {
        SendSysMessage(LANG_SELECT_CHAR_OR_CREATURE);
        SetSentErrorMessage(true);
        return false;
    }

    uint32 field;
    if (!ExtractUInt32(&args, field))
    {
        return false;
    }

    char* typeStr = ExtractOptNotLastArg(&args);
    if (!typeStr)
    {
        return false;
    }

    char* valStr = ExtractLiteralArg(&args);
    if (!valStr)
    {
        return false;
    }

    return HandleSetValueHelper(target, field, typeStr, valStr);
}

bool ChatHandler::HandleGetValueHelper(Object* target, uint32 field, char* typeStr)
{
    ObjectGuid guid = target->GetObjectGuid();

    if (field >= target->GetValuesCount())
    {
        PSendSysMessage(LANG_TOO_BIG_INDEX, field, guid.GetString().c_str(), target->GetValuesCount());
        return false;
    }

    uint32 base;                                            // 0 -> float
    if (!typeStr)
    {
        base = 10;
    }
    else if (strncmp(typeStr, "int", strlen(typeStr)) == 0)
    {
        base = 10;
    }
    else if (strncmp(typeStr, "hex", strlen(typeStr)) == 0)
    {
        base = 16;
    }
    else if (strncmp(typeStr, "bit", strlen(typeStr)) == 0)
    {
        base = 2;
    }
    else if (strncmp(typeStr, "float", strlen(typeStr)) == 0)
    {
        base = 0;
    }
    else
    {
        return false;
    }

    if (base)
    {
        uint32 iValue = target->GetUInt32Value(field);

        switch (base)
        {
            case 2:
            {
                // starting 0 if need as required bitstring format
                std::string res;
                res.reserve(1 + 32 + 1);
                res = (iValue & (1 << (32 - 1))) ? "0" : " ";
                for (int i = 32; i > 0; --i)
                {
                    res += (iValue & (1 << (i - 1))) ? "1" : "0";
                }
                DEBUG_LOG(GetMangosString(LANG_GET_BITSTR), guid.GetString().c_str(), field, res.c_str());
                PSendSysMessage(LANG_GET_BITSTR_FIELD, guid.GetString().c_str(), field, res.c_str());
                break;
            }
            case 16:
                DEBUG_LOG(GetMangosString(LANG_GET_HEX), guid.GetString().c_str(), field, iValue);
                PSendSysMessage(LANG_GET_HEX_FIELD, guid.GetString().c_str(), field, iValue);
                break;
            case 10:
            default:
                DEBUG_LOG(GetMangosString(LANG_GET_UINT), guid.GetString().c_str(), field, iValue);
                PSendSysMessage(LANG_GET_UINT_FIELD, guid.GetString().c_str(), field, iValue);
        }
    }
    else
    {
        float fValue = target->GetFloatValue(field);
        DEBUG_LOG(GetMangosString(LANG_GET_FLOAT), guid.GetString().c_str(), field, fValue);
        PSendSysMessage(LANG_GET_FLOAT_FIELD, guid.GetString().c_str(), field, fValue);
    }

    return true;
}

bool ChatHandler::HandleDebugGetItemValueCommand(char* args)
{
    uint32 guid;
    if (!ExtractUInt32(&args, guid))
    {
        return false;
    }

    uint32 field;
    if (!ExtractUInt32(&args, field))
    {
        return false;
    }

    char* typeStr = ExtractLiteralArg(&args);
    if (!typeStr && *args)                                  // optional arg but check format fail case
    {
        return false;
    }

    Item* item = m_session->GetPlayer()->GetItemByGuid(ObjectGuid(HIGHGUID_ITEM, guid));
    if (!item)
    {
        return false;
    }

    return HandleGetValueHelper(item, field, typeStr);
}

bool ChatHandler::HandleDebugGetValueCommand(char* args)
{
    Unit* target = getSelectedUnit();
    if (!target)
    {
        SendSysMessage(LANG_SELECT_CHAR_OR_CREATURE);
        SetSentErrorMessage(true);
        return false;
    }

    uint32 field;
    if (!ExtractUInt32(&args, field))
    {
        return false;
    }

    char* typeStr = ExtractLiteralArg(&args);
    if (!typeStr && *args)                                  // optional arg but check format fail case
    {
        return false;
    }

    return HandleGetValueHelper(target, field, typeStr);
}

bool ChatHandler::HandlerDebugModValueHelper(Object* target, uint32 field, char* typeStr, char* valStr)
{
    ObjectGuid guid = target->GetObjectGuid();

    // not allow access to nonexistent or critical for work field
    if (field >= target->GetValuesCount() || field <= OBJECT_FIELD_ENTRY)
    {
        PSendSysMessage(LANG_TOO_BIG_INDEX, field, guid.GetString().c_str(), target->GetValuesCount());
        return false;
    }

    uint32 type;                                            // 0 -> float 1 -> int add 2-> bit or 3 -> bit and  4 -> bit and not
    if (strncmp(typeStr, "int", strlen(typeStr)) == 0)
    {
        type = 1;
    }
    else if (strncmp(typeStr, "float", strlen(typeStr)) == 0)
    {
        type = 0;
    }
    else if (strncmp(typeStr, "|=", strlen("|=") + 1) == 0) // exactly copy
    {
        type = 2;
    }
    else if (strncmp(typeStr, "&=", strlen("&=") + 1) == 0) // exactly copy
    {
        type = 3;
    }
    else if (strncmp(typeStr, "&=~", strlen("&=~") + 1) == 0) // exactly copy
    {
        type = 4;
    }
    else
    {
        return false;
    }

    if (type)
    {
        uint32 iValue;
        if (!ExtractUInt32Base(&valStr, iValue, type == 1 ? 10 : 16))
        {
            return false;
        }

        uint32 value = target->GetUInt32Value(field);
        const char* guidString = guid.GetString().c_str();

        switch (type)
        {
            default:
            case 1:                                         // int +
                value = uint32(int32(value) + int32(iValue));
                DEBUG_LOG(GetMangosString(LANG_CHANGE_INT32), guidString, field, iValue, value, value);
                PSendSysMessage(LANG_CHANGE_INT32_FIELD, guidString, field, iValue, value, value);
                break;
            case 2:                                         // |= bit or
                value |= iValue;
                DEBUG_LOG(GetMangosString(LANG_CHANGE_HEX), guidString, field, typeStr, iValue, value);
                PSendSysMessage(LANG_CHANGE_HEX_FIELD, guidString, field, typeStr, iValue, value);
                break;
            case 3:                                         // &= bit and
                value &= iValue;
                DEBUG_LOG(GetMangosString(LANG_CHANGE_HEX), guidString, field, typeStr, iValue, value);
                PSendSysMessage(LANG_CHANGE_HEX_FIELD, guidString, field, typeStr, iValue, value);
                break;
            case 4:                                         // &=~ bit and not
                value &= ~iValue;
                DEBUG_LOG(GetMangosString(LANG_CHANGE_HEX), guidString, field, typeStr, iValue, value);
                PSendSysMessage(LANG_CHANGE_HEX_FIELD, guidString, field, typeStr, iValue, value);
                break;
        }

        target->SetUInt32Value(field, value);
    }
    else
    {
        float fValue;
        if (!ExtractFloat(&valStr, fValue))
        {
            return false;
        }

        float value = target->GetFloatValue(field);

        value += fValue;

        DEBUG_LOG(GetMangosString(LANG_CHANGE_FLOAT), guid.GetString().c_str(), field, fValue, value);
        PSendSysMessage(LANG_CHANGE_FLOAT_FIELD, guid.GetString().c_str(), field, fValue, value);

        target->SetFloatValue(field, value);
    }

    return true;
}

bool ChatHandler::HandleDebugModItemValueCommand(char* args)
{
    uint32 guid;
    if (!ExtractUInt32(&args, guid))
    {
        return false;
    }

    uint32 field;
    if (!ExtractUInt32(&args, field))
    {
        return false;
    }

    char* typeStr = ExtractLiteralArg(&args);
    if (!typeStr)
    {
        return false;
    }

    char* valStr = ExtractLiteralArg(&args);
    if (!valStr)
    {
        return false;
    }

    Item* item = m_session->GetPlayer()->GetItemByGuid(ObjectGuid(HIGHGUID_ITEM, guid));
    if (!item)
    {
        return false;
    }

    return HandlerDebugModValueHelper(item, field, typeStr, valStr);
}

bool ChatHandler::HandleDebugModValueCommand(char* args)
{
    Unit* target = getSelectedUnit();
    if (!target)
    {
        SendSysMessage(LANG_SELECT_CHAR_OR_CREATURE);
        SetSentErrorMessage(true);
        return false;
    }

    uint32 field;
    if (!ExtractUInt32(&args, field))
    {
        return false;
    }

    char* typeStr = ExtractLiteralArg(&args);
    if (!typeStr && *args)                                  // optional arg but check format fail case
    {
        return false;
    }

    char* valStr = ExtractLiteralArg(&args);
    if (!valStr)
    {
        return false;
    }

    return HandlerDebugModValueHelper(target, field, typeStr, valStr);
}

bool ChatHandler::HandleDebugSpellCoefsCommand(char* args)
{
    uint32 spellid = ExtractSpellIdFromLink(&args);
    if (!spellid)
    {
        return false;
    }

    SpellEntry const* spellEntry = sSpellStore.LookupEntry(spellid);
    if (!spellEntry)
    {
        return false;
    }

    SpellBonusEntry const* bonus = sSpellMgr.GetSpellBonusData(spellid);

    float direct_calc = CalculateDefaultCoefficient(spellEntry, SPELL_DIRECT_DAMAGE);
    float dot_calc = CalculateDefaultCoefficient(spellEntry, DOT);

    bool isDirectHeal = false;
    for (int i = 0; i < 3; ++i)
    {
        // Heals (Also count Mana Shield and Absorb effects as heals)
        if (spellEntry->Effect[i] == SPELL_EFFECT_HEAL || spellEntry->Effect[i] == SPELL_EFFECT_HEAL_MAX_HEALTH ||
            (spellEntry->Effect[i] == SPELL_EFFECT_APPLY_AURA && (spellEntry->EffectApplyAuraName[i] == SPELL_AURA_SCHOOL_ABSORB || spellEntry->EffectApplyAuraName[i] == SPELL_AURA_PERIODIC_HEAL)))
        {
            isDirectHeal = true;
            break;
        }
    }

    bool isDotHeal = false;
    for (int i = 0; i < 3; ++i)
    {
        // Periodic Heals
        if (spellEntry->Effect[i] == SPELL_EFFECT_APPLY_AURA && spellEntry->EffectApplyAuraName[i] == SPELL_AURA_PERIODIC_HEAL)
        {
            isDotHeal = true;
            break;
        }
    }

    char const* directHealStr = GetMangosString(LANG_DIRECT_HEAL);
    char const* directDamageStr = GetMangosString(LANG_DIRECT_DAMAGE);
    char const* dotHealStr = GetMangosString(LANG_DOT_HEAL);
    char const* dotDamageStr = GetMangosString(LANG_DOT_DAMAGE);

    PSendSysMessage(LANG_SPELLCOEFS, spellid, isDirectHeal ? directHealStr : directDamageStr,
                    direct_calc, direct_calc * 1.88f, bonus ? bonus->direct_damage : 0.0f, bonus ? bonus->ap_bonus : 0.0f);
    PSendSysMessage(LANG_SPELLCOEFS, spellid, isDotHeal ? dotHealStr : dotDamageStr,
                    dot_calc, dot_calc * 1.88f, bonus ? bonus->dot_damage : 0.0f, bonus ? bonus->ap_dot_bonus : 0.0f);

    return true;
}

bool ChatHandler::HandleDebugSpellModsCommand(char* args)
{
    char* typeStr = ExtractLiteralArg(&args);
    if (!typeStr)
    {
        return false;
    }

    uint16 opcode;
    if (strncmp(typeStr, "flat", strlen(typeStr)) == 0)
    {
        opcode = SMSG_SET_FLAT_SPELL_MODIFIER;
    }
    else if (strncmp(typeStr, "pct", strlen(typeStr)) == 0)
    {
        opcode = SMSG_SET_PCT_SPELL_MODIFIER;
    }
    else
    {
        return false;
    }

    uint32 effidx;
    if (!ExtractUInt32(&args, effidx) || effidx >= 64)
    {
        return false;
    }

    uint32 spellmodop;
    if (!ExtractUInt32(&args, spellmodop) || spellmodop >= MAX_SPELLMOD)
    {
        return false;
    }

    int32 value;
    if (!ExtractInt32(&args, value))
    {
        return false;
    }

    Player* chr = getSelectedPlayer();
    if (chr == NULL)
    {
        SendSysMessage(LANG_NO_CHAR_SELECTED);
        SetSentErrorMessage(true);
        return false;
    }

    // check online security
    if (HasLowerSecurity(chr))
    {
        return false;
    }

    PSendSysMessage(LANG_YOU_CHANGE_SPELLMODS, opcode == SMSG_SET_FLAT_SPELL_MODIFIER ? "flat" : "pct",
                    spellmodop, value, effidx, GetNameLink(chr).c_str());
    if (needReportToTarget(chr))
        ChatHandler(chr).PSendSysMessage(LANG_YOURS_SPELLMODS_CHANGED, GetNameLink().c_str(),
                                         opcode == SMSG_SET_FLAT_SPELL_MODIFIER ? "flat" : "pct", spellmodop, value, effidx);

    WorldPacket data(opcode, (1 + 1 + 2 + 2));
    data << uint8(effidx);
    data << uint8(spellmodop);
    data << int32(value);
    chr->GetSession()->SendPacket(&data);

    return true;
}
