/*
 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
 *
 * 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, see .
 */
#include "PlayerDump.h"
#include "AccountMgr.h"
#include "CharacterCache.h"
#include "Common.h"
#include "DatabaseEnv.h"
#include "Log.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "World.h"
#include 
#include 
// static data
enum GuidType : uint8
{
    // 32 bit long guids
    GUID_TYPE_ACCOUNT,
    GUID_TYPE_MAIL,
    // 64 bit long guids
    GUID_TYPE_CHAR,
    GUID_TYPE_EQUIPMENT_SET,
    GUID_TYPE_ITEM,
    GUID_TYPE_PET,
    // special types
    GUID_TYPE_NULL // set to null
};
// for RAII
struct FileCloser
{
    void operator()(FILE* f) const
    {
        if (f)
            fclose(f);
    }
};
typedef std::unique_ptr FileHandle;
inline FileHandle GetFileHandle(char const* path, char const* mode)
{
    return FileHandle(fopen(path, mode), FileCloser());
}
struct BaseTable
{
    char const* TableName;
    char const* PrimaryKey;
    char const* PlayerGuid;
    GuidType StoredType;
};
BaseTable const BaseTables[] =
{
    { "character_pet",              "id",      "owner",      GUID_TYPE_PET           },
    { "mail",                       "id",      "receiver",   GUID_TYPE_MAIL          },
    { "item_instance",              "guid",    "owner_guid", GUID_TYPE_ITEM          },
    { "character_equipmentsets",    "setguid", "guid",       GUID_TYPE_EQUIPMENT_SET },
    { "character_transmog_outfits", "setguid", "guid",       GUID_TYPE_EQUIPMENT_SET }
};
struct DumpTable
{
    char const* Name;
    DumpTableType Type;
};
DumpTable const DumpTables[] =
{
    { "characters",                       DTT_CHARACTER  },
    { "character_account_data",           DTT_CHAR_TABLE },
    { "character_achievement",            DTT_CHAR_TABLE },
    { "character_achievement_progress",   DTT_CHAR_TABLE },
    { "character_action",                 DTT_CHAR_TABLE },
    { "character_aura",                   DTT_CHAR_TABLE },
    { "character_aura_effect",            DTT_CHAR_TABLE },
    { "character_cuf_profiles",           DTT_CHAR_TABLE },
    { "character_currency",               DTT_CURRENCY   },
    { "character_declinedname",           DTT_CHAR_TABLE },
    { "character_favorite_auctions",      DTT_CHAR_TABLE },
    { "character_fishingsteps",           DTT_CHAR_TABLE },
    { "character_garrison",               DTT_CHAR_TABLE },
    { "character_garrison_blueprints",    DTT_CHAR_TABLE },
    { "character_garrison_buildings",     DTT_CHAR_TABLE },
    /// @todo: character_garrison_follower_abilities
    /// @todo: character_garrison_followers
    { "character_glyphs",                 DTT_CHAR_TABLE },
    { "character_homebind",               DTT_CHAR_TABLE },
    { "character_inventory",              DTT_INVENTORY  },
    { "character_pet",                    DTT_PET        },
    { "character_pet_declinedname",       DTT_PET        },
    { "character_pvp_talent",             DTT_CHAR_TABLE },
    { "character_queststatus",            DTT_CHAR_TABLE },
    { "character_queststatus_daily",      DTT_CHAR_TABLE },
    { "character_queststatus_monthly",    DTT_CHAR_TABLE },
    { "character_queststatus_objectives", DTT_CHAR_TABLE },
    { "character_queststatus_objectives_criteria", DTT_CHAR_TABLE },
    { "character_queststatus_objectives_criteria_progress", DTT_CHAR_TABLE },
    { "character_queststatus_rewarded",   DTT_CHAR_TABLE },
    { "character_queststatus_seasonal",   DTT_CHAR_TABLE },
    { "character_queststatus_weekly",     DTT_CHAR_TABLE },
    { "character_reputation",             DTT_CHAR_TABLE },
    { "character_skills",                 DTT_CHAR_TABLE },
    { "character_spell",                  DTT_CHAR_TABLE },
    { "character_spell_charges",          DTT_CHAR_TABLE },
    { "character_spell_cooldown",         DTT_CHAR_TABLE },
    { "character_talent",                 DTT_CHAR_TABLE },
    { "character_transmog_outfits",       DTT_CHAR_TRANSMOG },
    /// @todo: character_void_storage
    { "mail",                             DTT_MAIL       },
    { "mail_items",                       DTT_MAIL_ITEM  }, // must be after mail
    { "pet_aura",                         DTT_PET_TABLE  }, // must be after character_pet
    { "pet_aura_effect",                  DTT_PET_TABLE  }, // must be after character_pet
    { "pet_spell",                        DTT_PET_TABLE  }, // must be after character_pet
    { "pet_spell_charges",                DTT_PET_TABLE  }, // must be after character_pet
    { "pet_spell_cooldown",               DTT_PET_TABLE  }, // must be after character_pet
    { "item_instance",                    DTT_ITEM       }, // must be after character_inventory and mail_items
    { "character_equipmentsets",          DTT_EQSET_TABLE}, // must be after item_instance
    { "character_gifts",                  DTT_ITEM_GIFT  }, // must be after item_instance
    { "item_instance_artifact",           DTT_ITEM_TABLE }, // must be after item_instance
    { "item_instance_artifact_powers",    DTT_ITEM_TABLE }, // must be after item_instance
    { "item_instance_azerite",            DTT_ITEM_TABLE }, // must be after item_instance
    { "item_instance_azerite_empowered",  DTT_ITEM_TABLE }, // must be after item_instance
    { "item_instance_azerite_milestone_power", DTT_ITEM_TABLE }, // must be after item_instance
    { "item_instance_azerite_unlocked_essence", DTT_ITEM_TABLE }, // must be after item_instance
    { "item_instance_gems",               DTT_ITEM_TABLE }, // must be after item_instance
    { "item_instance_modifiers",          DTT_ITEM_TABLE }, // must be after item_instance
    { "item_instance_transmog",           DTT_ITEM_TABLE }, // must be after item_instance
};
uint32 const DUMP_TABLE_COUNT = std::extent::value;
// helper class to dump sql queries to a printable string
class StringTransaction
{
    public:
        StringTransaction() : _buf() { }
        void Append(char const* sql)
        {
            std::ostringstream oss;
            oss << sql << '\n';
            _buf += oss.str();
        }
        char const* GetBuffer() const
        {
            return _buf.c_str();
        }
    private:
        std::string _buf;
};
// dynamic data, loaded at startup
struct TableField
{
    std::string FieldName;
    GuidType FieldGuidType = GUID_TYPE_ACCOUNT;
    bool IsDependentField = false;
    bool IsBinaryField = false;
};
struct TableStruct
{
    std::string TableName;
    std::string WhereFieldName;
    std::vector TableFields;
    // for lookup
    std::unordered_map FieldIndices;
};
std::vector CharacterTables;
inline bool StringsEqualCaseInsensitive(std::string const& left, std::string const& right)
{
    std::string upperLeftString = left;
    ASSERT(Utf8ToUpperOnlyLatin(upperLeftString));
    std::string upperRightString = right;
    ASSERT(Utf8ToUpperOnlyLatin(upperRightString));
    return upperLeftString == upperRightString;
}
inline auto FindColumnByName(TableStruct& tableStruct, std::string const& columnName) -> decltype(tableStruct.TableFields.begin())
{
    return std::find_if(tableStruct.TableFields.begin(), tableStruct.TableFields.end(), [columnName](TableField const& tableField) -> bool
    {
        return StringsEqualCaseInsensitive(tableField.FieldName, columnName);
    });
}
inline int32 GetColumnIndexByName(TableStruct const& tableStruct, std::string const& columnName)
{
    auto itr = tableStruct.FieldIndices.find(columnName);
    if (itr == tableStruct.FieldIndices.end())
        return -1;
    return itr->second;
}
inline void MarkDependentColumn(TableStruct& tableStruct, std::string const& columnName, GuidType dependentType)
{
    auto itr = FindColumnByName(tableStruct, columnName);
    if (itr == tableStruct.TableFields.end())
    {
        TC_LOG_FATAL("server.loading", "Column `%s` declared in table `%s` marked as dependent but doesn't exist, PlayerDump will not work properly, please update table definitions",
            columnName.c_str(), tableStruct.TableName.c_str());
        ABORT();
        return;
    }
    if (itr->IsDependentField)
    {
        TC_LOG_FATAL("server.loading", "Attempt to mark column `%s` in table `%s` as dependent column but already marked! please check your code.",
            columnName.c_str(), tableStruct.TableName.c_str());
        ABORT();
        return;
    }
    itr->IsDependentField = true;
    itr->FieldGuidType = dependentType;
}
inline void MarkWhereField(TableStruct& tableStruct, std::string const& whereField)
{
    ASSERT(tableStruct.WhereFieldName.empty());
    auto whereFieldItr = FindColumnByName(tableStruct, whereField);
    if (whereFieldItr == tableStruct.TableFields.end())
    {
        TC_LOG_FATAL("server.loading", "Column name `%s` set as 'WHERE' column for table `%s` doesn't exist. PlayerDump won't work properly",
            whereField.c_str(), tableStruct.TableName.c_str());
        ABORT();
        return;
    }
    tableStruct.WhereFieldName = whereField;
}
inline void AssertBaseTable(BaseTable const& baseTable)
{
    auto itr = std::find_if(CharacterTables.begin(), CharacterTables.end(), [baseTable](TableStruct const& tableStruct) -> bool
    {
        return StringsEqualCaseInsensitive(tableStruct.TableName, baseTable.TableName);
    });
    ASSERT(itr != CharacterTables.end());
    auto columnItr = FindColumnByName(*itr, baseTable.PrimaryKey);
    ASSERT(columnItr != itr->TableFields.end());
    columnItr = FindColumnByName(*itr, baseTable.PlayerGuid);
    ASSERT(columnItr != itr->TableFields.end());
}
void PlayerDump::InitializeTables()
{
    uint32 oldMSTime = getMSTime();
    for (DumpTable const& dumpTable : DumpTables)
    {
        TableStruct t;
        t.TableName = dumpTable.Name;
        QueryResult result = CharacterDatabase.PQuery("DESC %s", dumpTable.Name);
        // prepared statement is correct (checked at startup) so table must exist
        ASSERT(result);
        int32 i = 0;
        do
        {
            std::string columnName = (*result)[0].GetString();
            std::string typeName = (*result)[1].GetString();
            t.FieldIndices.emplace(columnName, i++);
            TableField f;
            f.FieldName = columnName;
            f.IsBinaryField = !boost::ifind_first(typeName, "binary").empty() || !boost::ifind_first(typeName, "blob").empty();
            ASSERT(Utf8ToUpperOnlyLatin(columnName));
            t.TableFields.emplace_back(std::move(f));
        } while (result->NextRow());
        switch (dumpTable.Type)
        {
            case DTT_CHARACTER:
                MarkWhereField(t, "guid");
                MarkDependentColumn(t, "guid", GUID_TYPE_CHAR);
                MarkDependentColumn(t, "account", GUID_TYPE_ACCOUNT);
                MarkDependentColumn(t, "deleteInfos_Account", GUID_TYPE_NULL);
                MarkDependentColumn(t, "deleteInfos_Name", GUID_TYPE_NULL);
                MarkDependentColumn(t, "deleteDate", GUID_TYPE_NULL);
                break;
            case DTT_CHAR_TABLE:
                MarkWhereField(t, "guid");
                MarkDependentColumn(t, "guid", GUID_TYPE_CHAR);
                break;
            case DTT_CURRENCY:
                MarkWhereField(t, "CharacterGuid");
                MarkDependentColumn(t, "CharacterGuid", GUID_TYPE_CHAR);
                break;
            case DTT_EQSET_TABLE:
                MarkWhereField(t, "guid");
                MarkDependentColumn(t, "guid", GUID_TYPE_CHAR);
                MarkDependentColumn(t, "setguid", GUID_TYPE_EQUIPMENT_SET);
                // item0 - item18
                for (uint32 j = 0; j < EQUIPMENT_SLOT_END; ++j)
                {
                    std::string itColumn = Trinity::StringFormat("item%u", j);
                    MarkDependentColumn(t, itColumn, GUID_TYPE_ITEM);
                }
                break;
            case DTT_INVENTORY:
                MarkWhereField(t, "guid");
                MarkDependentColumn(t, "guid", GUID_TYPE_CHAR);
                MarkDependentColumn(t, "bag", GUID_TYPE_ITEM);
                MarkDependentColumn(t, "item", GUID_TYPE_ITEM);
                break;
            case DTT_CHAR_TRANSMOG:
                MarkWhereField(t, "guid");
                MarkDependentColumn(t, "guid", GUID_TYPE_CHAR);
                MarkDependentColumn(t, "setguid", GUID_TYPE_EQUIPMENT_SET);
                break;
            case DTT_MAIL:
                MarkWhereField(t, "receiver");
                MarkDependentColumn(t, "id", GUID_TYPE_MAIL);
                MarkDependentColumn(t, "receiver", GUID_TYPE_CHAR);
                break;
            case DTT_MAIL_ITEM:
                MarkWhereField(t, "mail_id");
                MarkDependentColumn(t, "mail_id", GUID_TYPE_MAIL);
                MarkDependentColumn(t, "item_guid", GUID_TYPE_ITEM);
                MarkDependentColumn(t, "receiver", GUID_TYPE_CHAR);
                break;
            case DTT_ITEM:
                MarkWhereField(t, "guid");
                MarkDependentColumn(t, "guid", GUID_TYPE_ITEM);
                MarkDependentColumn(t, "owner_guid", GUID_TYPE_CHAR);
                break;
            case DTT_ITEM_GIFT:
                MarkWhereField(t, "item_guid");
                MarkDependentColumn(t, "guid", GUID_TYPE_CHAR);
                MarkDependentColumn(t, "item_guid", GUID_TYPE_ITEM);
                break;
            case DTT_ITEM_TABLE:
                MarkWhereField(t, "itemGuid");
                MarkDependentColumn(t, "itemGuid", GUID_TYPE_ITEM);
                break;
            case DTT_PET:
                MarkWhereField(t, "owner");
                MarkDependentColumn(t, "id", GUID_TYPE_PET);
                MarkDependentColumn(t, "owner", GUID_TYPE_CHAR);
                break;
            case DTT_PET_TABLE:
                MarkWhereField(t, "guid");
                MarkDependentColumn(t, "guid", GUID_TYPE_PET);
                break;
            default:
                TC_LOG_FATAL("server.loading", "Wrong dump table type %u, probably added a new table type without updating code", uint32(dumpTable.Type));
                ABORT();
                return;
        }
        CharacterTables.emplace_back(std::move(t));
    }
    // perform some sanity checks
    for (TableStruct const& tableStruct : CharacterTables)
    {
        if (tableStruct.WhereFieldName.empty())
        {
            TC_LOG_FATAL("server.loading", "Table `%s` defined in player dump doesn't have a WHERE query field", tableStruct.TableName.c_str());
            ABORT();
        }
    }
    for (BaseTable const& baseTable : BaseTables)
        AssertBaseTable(baseTable);
    ASSERT(CharacterTables.size() == DUMP_TABLE_COUNT);
    TC_LOG_INFO("server.loading", ">> Initialized tables for PlayerDump in %u ms.", GetMSTimeDiffToNow(oldMSTime));
}
// Low level functions
inline bool FindColumn(TableStruct const& ts, std::string const& str, std::string const& column, std::string::size_type& s, std::string::size_type& e)
{
    int32 columnIndex = GetColumnIndexByName(ts, column);
    if (columnIndex == -1)
        return false;
    // array indices start at 0, compensate
    ++columnIndex;
    s = str.find("VALUES (");
    if (s == std::string::npos)
        return false;
    s += 8;
    e = s;
    bool isQuoted = str[s] == '\'';
    if (isQuoted)
    {
        ++s;
        ++e;
        // find first unescaped quote
        do
        {
            e = str.find('\'', e);
            if (e == std::string::npos)
                return false;
            if (str[e - 1] == '\\')
                continue;
            if (e + 1 < str.length() && str[e + 1] == '\'')
            {
                ++e;
                continue;
            }
            break;
        } while (true);
    }
    else
        e = str.find_first_of(",)", e);
    for (int32 i = 1; i < columnIndex; ++i)
    {
        // if previous value was quoted, move old e to comma
        if (isQuoted)
            ++e;
        // move past ", "
        s = e + 2;
        e = s;
        isQuoted = str[s] == '\'';
        if (isQuoted)
        {
            ++s;
            ++e;
            // find first unescaped quote
            do
            {
                e = str.find('\'', e);
                if (e == std::string::npos)
                    return false;
                if (str[e - 1] == '\\')
                    continue;
                if (e + 1 < str.length() && str[e + 1] == '\'')
                {
                    ++e;
                    continue;
                }
                break;
            } while (str[e - 1] == '\\');
        }
        else
            e = str.find_first_of(",)", e);
    }
    return true;
}
inline std::string GetTableName(std::string const& str)
{
    // length of "INSERT INTO `"
    static std::string::size_type const s = 13;
    std::string::size_type e = str.find('`', s);
    if (e == std::string::npos)
        return "";
    return str.substr(s, e - s);
}
inline bool ValidateFields(TableStruct const& ts, std::string const& str, size_t lineNumber)
{
    std::string::size_type s = str.find("` VALUES (");
    if (s != std::string::npos) // old dump format (no column names)
        return true;
    // new format has insert with columns, need validation else we risk executing an invalid query
    s = str.find("` (`");
    if (s == std::string::npos)
    {
        TC_LOG_ERROR("misc", "LoadPlayerDump: (line " UI64FMTD ") dump format not recognized.", lineNumber);
        return false;
    }
    s += 4;
    std::string::size_type valPos = str.find("VALUES ('");
    std::string::size_type e = str.find('`', s);
    if (e == std::string::npos || valPos == std::string::npos)
    {
        TC_LOG_ERROR("misc", "LoadPlayerDump: (line " UI64FMTD ") unexpected end of line", lineNumber);
        return false;
    }
    do
    {
        std::string column = str.substr(s, e - s);
        int32 columnIndex = GetColumnIndexByName(ts, column);
        if (columnIndex == -1)
        {
            TC_LOG_ERROR("misc", "LoadPlayerDump: (line " UI64FMTD ") unknown column name `%s` for table `%s`, aborting due to incompatible DB structure.", lineNumber, column.c_str(), ts.TableName.c_str());
            return false;
        }
        // length of "`, `"
        s = e + 4;
        e = str.find('`', s);
    } while (e < valPos);
    return true;
}
inline bool ChangeColumn(TableStruct const& ts, std::string& str, std::string const& column, std::string const& with, bool allowZero = false)
{
    std::string::size_type s, e;
    if (!FindColumn(ts, str, column, s, e))
        return false;
    if (allowZero && str.substr(s, e - s) == "0")
        return true;                                        // not an error
    str.replace(s, e - s, with);
    return true;
}
inline std::string GetColumn(TableStruct const& ts, std::string& str, std::string const& column)
{
    std::string::size_type s, e;
    if (!FindColumn(ts, str, column, s, e))
        return "";
    return str.substr(s, e - s);
}
template  class MapType, class... Rest>
inline T RegisterNewGuid(T oldGuid, MapType& guidMap, T guidOffset)
{
    auto itr = guidMap.find(oldGuid);
    if (itr != guidMap.end())
        return itr->second;
    T newguid = guidOffset + T(guidMap.size());
    guidMap.emplace(oldGuid, newguid);
    return newguid;
}
template  class MapType, class... Rest>
inline bool ChangeGuid(TableStruct const& ts, std::string& str, std::string const& column, MapType& guidMap, T guidOffset, bool allowZero = false)
{
    T oldGuid(atoull(GetColumn(ts, str, column).c_str()));
    if (allowZero && !oldGuid)
        return true;                                        // not an error
    std::string chritem;
    T newGuid = RegisterNewGuid(oldGuid, guidMap, guidOffset);
    chritem = std::to_string(newGuid);
    return ChangeColumn(ts, str, column, chritem, allowZero);
}
inline void AppendTableDump(StringTransaction& trans, TableStruct const& tableStruct, QueryResult result)
{
    if (!result)
        return;
    do
    {
        std::ostringstream ss;
        ss << "INSERT INTO `" << tableStruct.TableName << "` (";
        for (auto itr = tableStruct.TableFields.begin(); itr != tableStruct.TableFields.end();)
        {
            ss << '`' << itr->FieldName << '`';
            ++itr;
            if (itr != tableStruct.TableFields.end())
                ss << ", ";
        }
        ss << ") VALUES (";
        uint32 const fieldSize = uint32(tableStruct.TableFields.size());
        Field* fields = result->Fetch();
        for (uint32 i = 0; i < fieldSize;)
        {
            if (fields[i].IsNull())
                ss << "'NULL'";
            else
            {
                if (!tableStruct.TableFields[i].IsBinaryField)
                {
                    std::string s(fields[i].GetString());
                    CharacterDatabase.EscapeString(s);
                    ss << '\'' << s << '\'';
                }
                else
                {
                    std::vector b(fields[i].GetBinary());
                    ss << "0x" << ByteArrayToHexStr(b);
                }
            }
            ++i;
            if (i != fieldSize)
                ss << ", ";
        }
        ss << ");";
        trans.Append(ss.str().c_str());
    } while (result->NextRow());
}
inline std::string GenerateWhereStr(std::string const& field, ObjectGuid::LowType guid)
{
    std::ostringstream whereStr;
    whereStr << field << " = '" << guid << '\'';
    return whereStr.str();
}
template  class SetType, class... Rest>
inline std::string GenerateWhereStr(std::string const& field, SetType const& guidSet)
{
    std::ostringstream whereStr;
    whereStr << field << " IN ('";
    for (auto itr = guidSet.begin(); itr != guidSet.end();)
    {
        whereStr << *itr;
        ++itr;
        if (whereStr.str().size() > MAX_QUERY_LEN - 50)      // near to max query
            break;
        if (itr != guidSet.end())
            whereStr << "','";
    }
    whereStr << "')";
    return whereStr.str();
}
// Writing - High-level functions
void PlayerDumpWriter::PopulateGuids(ObjectGuid::LowType guid)
{
    for (BaseTable const& baseTable : BaseTables)
    {
        switch (baseTable.StoredType)
        {
            case GUID_TYPE_ITEM:
            case GUID_TYPE_MAIL:
            case GUID_TYPE_PET:
            case GUID_TYPE_EQUIPMENT_SET:
                break;
            default:
                return;
        }
        std::string whereStr = GenerateWhereStr(baseTable.PlayerGuid, guid);
        QueryResult result = CharacterDatabase.PQuery("SELECT %s FROM %s WHERE %s", baseTable.PrimaryKey, baseTable.TableName, whereStr.c_str());
        if (!result)
            continue;
        do
        {
            switch (baseTable.StoredType)
            {
                case GUID_TYPE_ITEM:
                    if (ObjectGuid::LowType guid = (*result)[0].GetUInt64())
                        _items.insert(guid);
                    break;
                case GUID_TYPE_MAIL:
                    if (uint32 guid = (*result)[0].GetUInt32())
                        _mails.insert(guid);
                    break;
                case GUID_TYPE_PET:
                    if (uint32 guid = (*result)[0].GetUInt32())
                        _pets.insert(guid);
                    break;
                case GUID_TYPE_EQUIPMENT_SET:
                    if (uint64 eqSetId = (*result)[0].GetUInt64())
                        _itemSets.insert(eqSetId);
                    break;
                default:
                    break;
            }
        } while (result->NextRow());
    }
}
bool PlayerDumpWriter::AppendTable(StringTransaction& trans, ObjectGuid::LowType guid, TableStruct const& tableStruct, DumpTable const& dumpTable)
{
    std::string whereStr;
    switch (dumpTable.Type)
    {
        case DTT_ITEM:
        case DTT_ITEM_GIFT:
        case DTT_ITEM_TABLE:
            if (_items.empty())
                return true;
            whereStr = GenerateWhereStr(tableStruct.WhereFieldName, _items);
            break;
        case DTT_PET_TABLE:
            if (_pets.empty())
                return true;
            whereStr = GenerateWhereStr(tableStruct.WhereFieldName, _pets);
            break;
        case DTT_MAIL_ITEM:
            if (_mails.empty())
                return true;
            whereStr = GenerateWhereStr(tableStruct.WhereFieldName, _mails);
            break;
        case DTT_EQSET_TABLE:
        case DTT_CHAR_TRANSMOG:
            if (_itemSets.empty())
                return true;
            whereStr = GenerateWhereStr(tableStruct.WhereFieldName, _itemSets);
            break;
        default:
            // not set case, get single guid string
            whereStr = GenerateWhereStr(tableStruct.WhereFieldName, guid);
            break;
    }
    QueryResult result = CharacterDatabase.PQuery("SELECT * FROM %s WHERE %s", dumpTable.Name, whereStr.c_str());
    switch (dumpTable.Type)
    {
        case DTT_CHARACTER:
            if (result)
            {
                // characters.deleteInfos_Account - if filled error
                int32 index = GetColumnIndexByName(tableStruct, "deleteInfos_Account");
                ASSERT(index != -1); // checked at startup
                if ((*result)[index].GetUInt32())
                    return false;
            }
            break;
        default:
            break;
    }
    AppendTableDump(trans, tableStruct, result);
    return true;
}
bool PlayerDumpWriter::GetDump(ObjectGuid::LowType guid, std::string& dump)
{
    dump =  "IMPORTANT NOTE: THIS DUMPFILE IS MADE FOR USE WITH THE 'PDUMP' COMMAND ONLY - EITHER THROUGH INGAME CHAT OR ON CONSOLE!\n";
    dump += "IMPORTANT NOTE: DO NOT apply it directly - it will irreversibly DAMAGE and CORRUPT your database! You have been warned!\n\n";
    StringTransaction trans;
    // collect guids
    PopulateGuids(guid);
    for (uint32 i = 0; i < DUMP_TABLE_COUNT; ++i)
        if (!AppendTable(trans, guid, CharacterTables[i], DumpTables[i]))
            return false;
    dump += trans.GetBuffer();
    /// @todo Add instance/group..
    /// @todo Add a dump level option to skip some non-important tables
    return true;
}
DumpReturn PlayerDumpWriter::WriteDump(std::string const& file, ObjectGuid::LowType guid)
{
    if (sWorld->getBoolConfig(CONFIG_PDUMP_NO_PATHS))
        if (strchr(file.c_str(), '\\') || strchr(file.c_str(), '/'))
            return DUMP_FILE_OPEN_ERROR;
    if (sWorld->getBoolConfig(CONFIG_PDUMP_NO_OVERWRITE))
    {
        // check if file exists already
        if (GetFileHandle(file.c_str(), "r"))
            return DUMP_FILE_OPEN_ERROR;
    }
    FileHandle fout = GetFileHandle(file.c_str(), "w");
    if (!fout)
        return DUMP_FILE_OPEN_ERROR;
    DumpReturn ret = DUMP_SUCCESS;
    std::string dump;
    if (!GetDump(guid, dump))
        ret = DUMP_CHARACTER_DELETED;
    fprintf(fout.get(), "%s", dump.c_str());
    return ret;
}
// Reading - High-level functions
inline void FixNULLfields(std::string& line)
{
    static std::string const NullString("'NULL'");
    size_t pos = line.find(NullString);
    while (pos != std::string::npos)
    {
        line.replace(pos, NullString.length(), "NULL");
        pos = line.find(NullString);
    }
}
DumpReturn PlayerDumpReader::LoadDump(std::string const& file, uint32 account, std::string name, ObjectGuid::LowType guid)
{
    uint32 charcount = AccountMgr::GetCharactersCount(account);
    if (charcount >= sWorld->getIntConfig(CONFIG_CHARACTERS_PER_REALM))
        return DUMP_TOO_MANY_CHARS;
    FileHandle fin = GetFileHandle(file.c_str(), "r");
    if (!fin)
        return DUMP_FILE_OPEN_ERROR;
    std::string newguid, chraccount;
    // make sure the same guid doesn't already exist and is safe to use
    bool incHighest = true;
    if (guid && guid < sObjectMgr->GetGenerator().GetNextAfterMaxUsed())
    {
        CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHECK_GUID);
        stmt->setUInt64(0, guid);
        if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
            guid = sObjectMgr->GetGenerator().GetNextAfterMaxUsed();                     // use first free if exists
        else
            incHighest = false;
    }
    else
        guid = sObjectMgr->GetGenerator().GetNextAfterMaxUsed();
    // normalize the name if specified and check if it exists
    if (!normalizePlayerName(name))
        name.clear();
    if (ObjectMgr::CheckPlayerName(name, sWorld->GetDefaultDbcLocale(), true) == CHAR_NAME_SUCCESS)
    {
        CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHECK_NAME);
        stmt->setString(0, name);
        if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
            name.clear();                                       // use the one from the dump
    }
    else
        name.clear();
    // name encoded or empty
    newguid = std::to_string(guid);
    chraccount = std::to_string(account);
    std::map items;
    ObjectGuid::LowType itemLowGuidOffset = sObjectMgr->GetGenerator().GetNextAfterMaxUsed();
    std::map mails;
    uint32 mailLowGuidOffset = sObjectMgr->_mailId;
    std::map petIds;
    uint32 petLowGuidOffset = sObjectMgr->_hiPetNumber;
    std::map equipmentSetIds;
    uint64 equipmentSetGuidOffset = sObjectMgr->_equipmentSetGuid;
    static size_t const BUFFER_SIZE = 32000;
    char buf[BUFFER_SIZE] = { };
    uint8 gender = GENDER_NONE;
    uint8 race = RACE_NONE;
    uint8 playerClass = CLASS_NONE;
    uint8 level = 1;
    // for logs
    size_t lineNumber = 0;
    CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
    while (!feof(fin.get()))
    {
        if (!fgets(buf, BUFFER_SIZE, fin.get()))
        {
            if (feof(fin.get()))
                break;
            return DUMP_FILE_BROKEN;
        }
        std::string line;
        line.assign(buf);
        ++lineNumber;
        // skip empty strings
        size_t nw_pos = line.find_first_not_of(" \t\n\r\7");
        if (nw_pos == std::string::npos)
            continue;
        // skip the important notes
        static std::string const SkippedLine = "IMPORTANT NOTE:";
        if (line.substr(nw_pos, SkippedLine.size()) == SkippedLine)
            continue;
        // determine table name and load type
        std::string tn = GetTableName(line);
        if (tn.empty())
        {
            TC_LOG_ERROR("misc", "LoadPlayerDump: (line " UI64FMTD ") Can't extract table name!", lineNumber);
            return DUMP_FILE_BROKEN;
        }
        DumpTableType type = DTT_CHARACTER;
        uint32 i;
        for (i = 0; i < DUMP_TABLE_COUNT; ++i)
        {
            if (tn == DumpTables[i].Name)
            {
                type = DumpTables[i].Type;
                break;
            }
        }
        if (i == DUMP_TABLE_COUNT)
        {
            TC_LOG_ERROR("misc", "LoadPlayerDump: (line " UI64FMTD ") Unknown table: `%s`!", lineNumber, tn.c_str());
            return DUMP_FILE_BROKEN;
        }
        TableStruct const& ts = CharacterTables[i];
        if (!ValidateFields(ts, line, lineNumber))
            return DUMP_FILE_BROKEN;
        // per field guid offsetting
        for (TableField const& field : ts.TableFields)
        {
            if (!field.IsDependentField)
                continue;
            switch (field.FieldGuidType)
            {
                case GUID_TYPE_ACCOUNT:
                    if (!ChangeColumn(ts, line, field.FieldName, chraccount))
                        return DUMP_FILE_BROKEN;
                    break;
                case GUID_TYPE_CHAR:
                    if (!ChangeColumn(ts, line, field.FieldName, newguid))
                        return DUMP_FILE_BROKEN;
                    break;
                case GUID_TYPE_PET:
                    if (!ChangeGuid(ts, line, field.FieldName, petIds, petLowGuidOffset))
                        return DUMP_FILE_BROKEN;
                    break;
                case GUID_TYPE_MAIL:
                    if (!ChangeGuid(ts, line, field.FieldName, mails, mailLowGuidOffset))
                        return DUMP_FILE_BROKEN;
                    break;
                case GUID_TYPE_ITEM:
                    if (!ChangeGuid(ts, line, field.FieldName, items, itemLowGuidOffset, true))
                        return DUMP_FILE_BROKEN;
                    break;
                case GUID_TYPE_EQUIPMENT_SET:
                    if (!ChangeGuid(ts, line, field.FieldName, equipmentSetIds, equipmentSetGuidOffset))
                        return DUMP_FILE_BROKEN;
                    break;
                case GUID_TYPE_NULL:
                {
                    static std::string const NullString("NULL");
                    if (!ChangeColumn(ts, line, field.FieldName, NullString))
                        return DUMP_FILE_BROKEN;
                    break;
                }
            }
        }
        // extra modifications for other tables
        switch (type)
        {
            case DTT_CHARACTER:
            {
                race = uint8(atoul(GetColumn(ts, line, "race").c_str()));
                playerClass = uint8(atoul(GetColumn(ts, line, "class").c_str()));
                gender = uint8(atoul(GetColumn(ts, line, "gender").c_str()));
                level = uint8(atoul(GetColumn(ts, line, "level").c_str()));
                if (name.empty())
                {
                    // generate a temporary name
                    std::string guidPart = Trinity::StringFormat("%X", guid);
                    std::size_t maxCharsFromOriginalName = MAX_PLAYER_NAME - guidPart.length();
                    name = GetColumn(ts, line, "name").substr(0, maxCharsFromOriginalName) + guidPart;
                    // characters.at_login set to "rename on login"
                    if (!ChangeColumn(ts, line, "name", name))
                        return DUMP_FILE_BROKEN;
                    if (!ChangeColumn(ts, line, "at_login", "1"))
                        return DUMP_FILE_BROKEN;
                }
                else if (!ChangeColumn(ts, line, "name", name.c_str())) // characters.name
                    return DUMP_FILE_BROKEN;
                break;
            }
            default:
                break;
        }
        FixNULLfields(line);
        trans->Append(line.c_str());
    }
    CharacterDatabase.CommitTransaction(trans);
    // in case of name conflict player has to rename at login anyway
    sCharacterCache->AddCharacterCacheEntry(ObjectGuid::Create(guid), account, name, gender, race, playerClass, level, false);
    sObjectMgr->GetGenerator().Set(sObjectMgr->GetGenerator().GetNextAfterMaxUsed() + items.size());
    sObjectMgr->_mailId += mails.size();
    sObjectMgr->_hiPetNumber += petIds.size();
    sObjectMgr->_equipmentSetGuid += equipmentSetIds.size();
    if (incHighest)
        sObjectMgr->GetGenerator().Generate();
    return DUMP_SUCCESS;
}