/*
 $Author: All $
 $RCSfile: convert.cpp,v $
 $Date: 2005/06/28 20:17:48 $
 $Revision: 2.7 $
 */
#include "affect.h"
#include "db.h"
#include "db_file.h"
#include "extra.h"
#include "formatter.h"
#include "handler.h"
#include "nanny.h"
#include "pcsave.h"
#include "reception.h"
#include "textutil.h"
#include "utils.h"
#include "zon_basis.h"

#include <dirent.h>

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iostream>
#include <string>

#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>

namespace fs = boost::filesystem;

char player_directory[1024] = "\0";
int max_id = -1;
int top_id = 200000;
ubit8 *ids = nullptr; /** For checking duplicate players... */

#define OUTPUT_DIR "lib/"

int sel_name(const dirent *dptr)
{
    if (strchr(dptr->d_name, '.') == nullptr)
    {
        return 1;
    }
    return 0;
}

void convert_free_unit(unit_data *u)
{
    while (u->getUnitContains())
    {
        convert_free_unit(u->getUnitContains());
    }

    u->setUnitAffected(nullptr);
    u->setFunctionPointer(nullptr);

    unit_from_unit(u);
    remove_from_unit_list(u);

    delete u;
}

void free_inventory(unit_data *u)
{
    unit_data *tmp = nullptr;
    unit_data *nxt = nullptr;

    for (tmp = u; tmp; tmp = nxt)
    {
        nxt = tmp->getNext();
        convert_free_unit(tmp);
    }
}

int days_old(time_t last_logon)
{
    return (int)(difftime(time(nullptr), last_logon) / SECS_PER_REAL_DAY);
}

unit_data *convert_item(unit_data *u, unit_data *pc, int bList)
{
    if (bList)
    {
        if (u->getFileIndex()->getZone()->getName() == "treasure")
        {
            extra_descr_data *ed = u->getExtraList().m_pList;
            std::cout << std::endl << u->getFileIndexName() << "@treasure" << std::endl;
            if (ed)
            {
                bool found = false;
                for (; ed; ed = ed->next)
                {
                    if (ed->names.StrStr("$piname"))
                    {
                        if (found == false)
                        {
                            std::cout << "Owned by:  ";
                            found = true;
                        }

                        std::cout << ed->descr.c_str() << " ";
                    }
                }
                std::cout << std::endl;
            }
        }

        return u;
    }

    if (u->isObj())
    {
        u->setSize(pc->getSize());
    }

    return u;
}

void convert_inventory(unit_data *u, unit_data *pc, int bList = FALSE)
{
    unit_data *bla = nullptr;

    if (u == nullptr)
    {
        return;
    }

    convert_inventory(u->getUnitContains(), pc, bList);

    convert_inventory(u->getNext(), pc, bList);

    bla = convert_item(u, pc, bList);

    assert(!u->is_destructed());

    if (bla != u)
    {
        unit_data *tmpu = nullptr;

        assert(u->getUnitIn());

        unit_to_unit(bla, u->getUnitIn());

        while (u->getUnitContains())
        {
            tmpu = u->getUnitContains();
            unit_from_unit(tmpu);
            unit_to_unit(tmpu, bla);
        }

        unit_from_unit(u);
        convert_free_unit(u);
    }
}

/**
 * This procedure makes any conversion you might want on every player in
 * the playerfile, including affects and inventory
 */
void convert_player(unit_data *pc)
{
    ;
}

/**
 * @returns TRUE if Ok.
 */
int sanity_check(unit_data *u)
{
    if (g_nCorrupt == TRUE)
    {
        printf("Corrupted unit in READ.");
        return FALSE;
    }

    if (PC_ID(u) > top_id)
    {
        printf("Maximum ID exceeded: %d > top id %d", PC_ID(u), top_id);
        return FALSE;
    }

    if (PC_ID(u) < 0)
    {
        printf("Illegal ID < 0!");
        return FALSE;
    }

    if ((u->getCurrentHitpoints() > 10000) || (u->getMaximumHitpoints() > 10000))
    {
        printf("Corrupted UNIT HITPOINTS");
        return FALSE;
    }

    if (!u->isPC())
    {
        printf("Not a player!");
        return FALSE;
    }

    if (PC_TIME(u).getPlayerCharacterCreationTime() > time(nullptr))
    {
        printf("Corrupted creation time.");
        return FALSE;
    }

    if (PC_TIME(u).getPlayerLastConnectTime() > time(nullptr))
    {
        printf("Corrupted connect time.");
        return FALSE;
    }

    if (CHAR_RACE(u) >= PC_RACE_MAX)
    {
        printf("Corrupted RACE");
        return FALSE;
    }

    if (!u->getUnitContains() && (u->getWeight() != u->getBaseWeight()))
    {
        printf("Fixed illegal weight.");
        u->setWeight(u->getBaseWeight());
    }

    return TRUE;
}

int shall_delete(unit_data *pc)
{
    int days = 0;

    days = days_old(PC_TIME(pc).getPlayerLastConnectTime());

    /* Player which have paid at some point in time remain almost permanent. */
    if (PC_ACCOUNT(pc).getTotalCredit() > 0)
    {
        return FALSE;
    }

    if ((days > 60) && (CHAR_LEVEL(pc) <= START_LEVEL + 4))
    {
        std::cout << "2 month inactivity and less than lvl 5 Will delete ";
        return TRUE;
    }

    if ((days > 185) && (CHAR_LEVEL(pc) <= START_LEVEL + 8))
    {
        std::cout << "1/2 year inactivity and less than lvl 8 Will delete ";
        return TRUE;
    }

    return FALSE;
}

int shall_exclude(const char *name)
{
    char buf[256];
    int result = 0;

    result = _parse_name(name, buf);

    if (result == 3)
    {
        std::cout << "Name in Badnames will delete\n";
        return TRUE;
    }

    if (result == 2)
    {
        std::cout << "Name in Badstrings will delete\n";
        return TRUE;
    }

    if (result != 0)
    {
        std::cout << "Name illegal will delete\n";
        return TRUE;
    }

    return FALSE;
}

unit_data *convert_load_player(const char *name)
{
    unit_data *ch = nullptr;

    if (!player_exists(name))
    {
        /* printf("No such player.\n"); */
        return nullptr;
    }

    ch = load_player(name);

    if (ch == nullptr)
    {
        printf("NULL.\n");
        return nullptr;
    }

    insert_in_unit_list(ch);
    unit_to_unit(ch, g_destroy_room);

    if (shall_exclude(name))
    {
        printf("EXCLUDED.\n");
        convert_free_unit(ch);
        return nullptr;
    }

    if (!sanity_check(ch))
    {
        printf("SANITY ERROR.\n");
        convert_free_unit(ch);
        return nullptr;
    }

    if (PC_ID(ch) > top_id)
    {
        printf("TOP ID ERROR %d vs %d.\n", PC_ID(ch), top_id);
        convert_free_unit(ch);
        return nullptr;
    }

    if (PC_ID(ch) > max_id)
    {
        max_id = PC_ID(ch);
    }

    return ch;
}

const char *isodate(tm *t)
{
    static std::string isodate;

    isodate = diku::format_to_str("%04d-%02d-%02d", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday);
    return isodate.c_str();
}

std::string GetInfo(unit_data *pc)
{
    std::string s = "";
    extra_descr_data *e1 = PC_INFO(pc).find_raw("$email");

    if (e1)
        s = "email: " + e1->descr + ";";

    e1 = PC_INFO(pc).find_raw("$maiden");
    if (e1)
        s += "maiden: " + e1->descr + ";";

    return s;
}

void clist()
{
    void boot_global_dil(void);
    
    std::string ipath;
    /*
        std::cout << "\nEnter the full path to the root player directory for example '/home/mud/vme2.0/lib/ply' or \n<enter> for the one
    listed in the server.cfg file:  "; char cpath[1024]; cpath[0]=0; std::cin.ignore(); std::cin.getline(cpath,1024); ipath=cpath;
    */
    if (ipath.empty())
    {
        ipath = g_cServerConfig.getFileInLibDir("ply");
    }

    fs::path full_path(ipath);

    unsigned long file_count = 0;
    unsigned long dir_count = 0;
    unsigned long err_count = 0;
    time_info_data tid2;

    unit_data *pc = nullptr;
    unit_data *void_char = new_unit_data(UNIT_ST_NPC, nullptr); // Would be preferable to have a file_index_type passed here

    if (!fs::exists(full_path))
    {
        std::cout << "\nNot found: " << full_path << std::endl;
        return;
    }

    if (fs::is_directory(full_path))
    {
        std::cout << "\nIn directory: " << full_path << "\n\n";
        fs::directory_iterator end_iter;
        std::string path_end = "";

        std::cout << "name;id;Level;Admin;days since login;created;birth;" << std::endl;

        boot_global_dil();

        for (char c = 'a'; c <= 'z'; c++)
        {
            path_end = c;
            full_path = ipath + "/" + path_end;
            if (!fs::exists(full_path))
            {
                std::cout << "\nNot found: " << full_path << std::endl;
                continue;
            }

            if (!fs::is_directory(full_path))
            {
                continue;
            }
            // std::cout << "\nIn directory: " << full_path << "\n\n";

            for (fs::directory_iterator dir_itr(full_path); dir_itr != end_iter; ++dir_itr)
            {
                std::string temp;
                try
                {
                    if (fs::is_directory(*dir_itr))
                    {
                        ++dir_count;
                    }
                    else if (dir_itr->path().filename().string().find(".") == std::string::npos)
                    {
                        ++file_count;

                        std::cout << dir_itr->path() << ";";

                        temp = dir_itr->path().filename().string();
                        pc = convert_load_player(temp.c_str());
                        if (pc == nullptr)
                        {
                            std::cout << "ERROR: Corrupt\n";
                            continue;
                        }

                        std::cout << PC_ID(pc) << ";" << (int)CHAR_LEVEL(pc) << ";" << (IS_MORTAL(pc) ? " PLY  " : "ADMIN") << ";"
                                  << days_old(PC_TIME(pc).getPlayerLastConnectTime()) << ";";

                        std::cout << GetInfo(pc);

                        if (ids[PC_ID(pc)])
                        {
                            std::cout << "Duplicate ID! (" << (signed long)PC_ID(pc) << ")";
                        }
                        else
                        {
                            ids[PC_ID(pc)] = 1;
                        }

                        shall_exclude(pc->getNames().Name());
                        // shall_delete(pc);

                        void_char->setUnitContains(nullptr);
                        load_contents(temp.c_str(), void_char);

                        /*if (UNIT_CONTAINS(void_char))
                        {
                            std::cout << "  INV";
                            convert_inventory(UNIT_CONTAINS(void_char), pc, TRUE);
                            free_inventory(UNIT_CONTAINS(void_char));
                        }

                        std::cout << endl;*/

                        bool tmp = false;

                        const auto creation_time = PC_TIME(pc).getPlayerCharacterCreationTime();
                        tm *t = gmtime(&creation_time);
                        std::cout << isodate(t) << ";";

                        const auto birthday = PC_TIME(pc).getPlayerBirthday();
                        t = gmtime(&birthday);
                        std::cout << isodate(t) << ";";

                        time_t secs = static_cast<time_t>(PC_TIME(pc).getTotalTimePlayedInSeconds());

                        convert_free_unit(pc);

                        time_t val = 0;
                        val = secs / SECS_PER_REAL_HOUR;
                        tid2.setHours(val % 24);
                        tid2.setDay(val / 24);
                        tid2.setMonth(-1);
                        tid2.setYear(val / (365 * 24));

                        if (tid2.getYear() > 0)
                        {
                            std::cout << " " << tid2.getYear() << " years";
                            tmp = true;
                        }
                        if (tid2.getDay() > 0)
                        {
                            std::cout << " " << tid2.getDay() << " days";
                            tmp = true;
                        }
                        if (tid2.getHours() > 0)
                        {
                            std::cout << " " << tid2.getHours() << " hours";
                            tmp = true;
                        }
                        if (tmp)
                        {
                            std::cout << ".";
                        }
                        else
                        {
                            std::cout << " Less than an hour";
                        }

                        std::cout << "; " << std::endl;
                    }
                }
                catch (const std::exception &ex)
                {
                    ++err_count;
                    std::cout << dir_itr->path() << " " << ex.what() << std::endl;
                }
            }
        }

        std::cout << "Maximum ID was " << max_id << " / Top " << top_id << "\n";
        std::cout << "\n" << file_count << " files\n" << dir_count << " directories\n" << err_count << " errors\n";
    }
    else // must be a file
    {
        std::cout << "\nFound: " << full_path << "\n";
    }
}

void convert_file()
{
    std::string ipath{g_cServerConfig.getFileInLibDir("ply")};
    fs::path full_path(ipath);

    unsigned long file_count = 0;
    unsigned long dir_count = 0;
    unsigned long err_count = 0;

    unit_data *pc = nullptr;

    if (!fs::exists(full_path))
    {
        std::cout << "\nNot found: " << full_path << std::endl;
        return;
    }

    if (fs::is_directory(full_path))
    {
        std::cout << "\nIn directory: " << full_path << "\n\n";
        fs::directory_iterator end_iter;
        std::string path_end = "";
        for (char c = 'a'; c <= 'z'; c++)
        {
            path_end = c;
            full_path = ipath + "/" + path_end;
            if (!fs::exists(full_path))
            {
                std::cout << "\nNot found: " << full_path << std::endl;
                continue;
            }

            if (!fs::is_directory(full_path))
            {
                continue;
            }
            std::cout << "\nIn directory: " << full_path << "\n\n";

            for (fs::directory_iterator dir_itr(full_path); dir_itr != end_iter; ++dir_itr)
            {
                std::string temp;
                try
                {
                    if (fs::is_directory(*dir_itr))
                    {
                        ++dir_count;
                    }
                    else if (dir_itr->path().filename().string().find(".") == std::string::npos)
                    {
                        ++file_count;
                        std::cout << dir_itr->path() << "\n";

                        temp = dir_itr->path().filename().string();
                        pc = convert_load_player(temp.c_str());

                        if (pc == nullptr)
                        {
                            std::cout << "Corrupt Player ERASED." << std::endl;
                            delete_player(temp.c_str());
                            continue;
                        }

                        if (ids[PC_ID(pc)])
                        {
                            std::cout << "Duplicate ID! (" << (signed long)PC_ID(pc) << ")" << std::endl;
                        }
                        else
                        {
                            ids[PC_ID(pc)] = 1;
                        }

                        std::cout << pc->getNames().Name() << " Lvl [" << CHAR_LEVEL(pc) << "] " << (IS_MORTAL(pc) ? "   " : "ADMIN")
                                  << std::endl;

                        std::cout.flush();
                        load_contents(temp.c_str(), pc);
                        convert_player(pc);
                        save_player(pc);
                        save_player_contents(pc, TRUE);

                        convert_free_unit(pc);
                    }
                }
                catch (const std::exception &ex)
                {
                    ++err_count;
                    std::cout << dir_itr->path() << " " << ex.what() << std::endl;
                }
            }
        }

        std::cout << "\n" << file_count << " files\n" << dir_count << " directories\n" << err_count << " errors\n";
    }
    else // must be a file
    {
        std::cout << "\nFound: " << full_path << "\n";
    }
} /*end of convert_file*/

void cleanup()
{
    std::string ipath{g_cServerConfig.getFileInLibDir("ply")};

    fs::path full_path(ipath);

    unsigned long file_count = 0;
    unsigned long players_deleted = 0;
    unsigned long players_converted = 0;
    unsigned long dir_count = 0;
    unsigned long err_count = 0;

    unit_data *pc = nullptr;
    unit_data *void_char = new_unit_data(UNIT_ST_NPC, nullptr); // Would be preferable to have a file_index_type passed here

    if (!fs::exists(full_path))
    {
        std::cout << "\nNot found: " << full_path << std::endl;
        return;
    }

    if (fs::is_directory(full_path))
    {
        std::cout << "\nIn directory: " << full_path << "\n\n";
        fs::directory_iterator end_iter;
        std::string path_end = "";
        for (char c = 'a'; c <= 'z'; c++)
        {
            path_end = c;
            full_path = ipath + "/" + path_end;
            if (!fs::exists(full_path))
            {
                std::cout << "\nNot found: " << full_path << std::endl;
                continue;
            }

            if (!fs::is_directory(full_path))
            {
                continue;
            }
            std::cout << "\nIn directory: " << full_path << "\n\n";

            for (fs::directory_iterator dir_itr(full_path); dir_itr != end_iter; ++dir_itr)
            {
                std::string temp;
                try
                {
                    if (fs::is_directory(*dir_itr))
                    {
                        ++dir_count;
                    }
                    else if (dir_itr->path().filename().string().find(".") == std::string::npos)
                    {
                        ++file_count;
                        std::cout << dir_itr->path() << "\n";

                        temp = dir_itr->path().filename().string();
                        std::cout << temp;
                        pc = convert_load_player(temp.c_str());

                        if (pc == nullptr)
                        {
                            std::cout << "Corrupt Player ERASED." << std::endl;
                            delete_player(temp.c_str());
                            ++players_deleted;
                            continue;
                        }

                        if (str_ccmp(temp.c_str(), pc->getNames().Name()))
                        {
                            std::cout << "Name in file doesn't match filename." << std::endl;
                            convert_free_unit(pc);
                            delete_player(temp.c_str());
                            ++players_deleted;
                            continue;
                        }

                        std::cout << pc->getNames().Name() << "]  Lvl " << CHAR_LEVEL(pc) << "]  " << (IS_MORTAL(pc) ? "   " : "ADMIN");

                        if (shall_delete(pc))
                        {
                            convert_free_unit(pc);
                            delete_player(temp.c_str());
                            ++players_deleted;
                            continue;
                        }

                        std::cout.flush();

                        void_char->setUnitContains(nullptr);
                        load_contents(temp.c_str(), void_char);
                        if (void_char->getUnitContains())
                        {
                            std::cout << "  INV";
                            convert_inventory(void_char->getUnitContains(), pc, TRUE);
                            free_inventory(void_char->getUnitContains());
                        }

                        convert_free_unit(pc);
                        ++players_converted;
                    }
                }
                catch (const std::exception &ex)
                {
                    ++err_count;
                    std::cout << dir_itr->path() << " " << ex.what() << std::endl;
                }
            }
        }

        std::cout << "Players deleted:  " << players_deleted << std::endl << "Players converted:  " << players_converted << std::endl;
        std::cout << "\n" << file_count << " files\n" << dir_count << " directories\n" << err_count << " errors\n";
    }
    else // must be a file
    {
        std::cout << "\nFound: " << full_path << "\n";
    }

    return;
}

void cleanup_playerfile(int c)
{
    top_id = read_player_id();
    CREATE(ids, ubit8, top_id + 1);

    memset(ids, 0, top_id);

    g_entry_room = new_unit_data(UNIT_ST_ROOM, nullptr);   // Would be preferable to have a file_index_type passed here
    g_destroy_room = new_unit_data(UNIT_ST_ROOM, nullptr); // Would be preferable to have a file_index_type passed here

    if (c == 1)
    {
        convert_file();
    }
    else if (c == 2)
    {
        cleanup();
    }
    else
    {
        clist();
    }

    printf("\n\nFinished.\n");
}
