#include <csignal>
#include <cstdlib>
#include <Datei.h>
#include <fstream>
#include <Globals.h>
#include <iostream>
#include <Klient.h>
#ifndef _WINDOWS
#    include <sys/resource.h>
#else
#    define NO_MAIN
#    include <main.h>
#endif
#include <AsynchronCall.h>
#include <Console.h>
#include <Logging.h>
#include <Text.h>
#include <Zeit.h>

#include "Chat.h"
#include "ChunkMap.h"
#include "Server.h"
#include "WorldGenerator.h"

FactoryCraftServer* mserver = 0;

#ifdef _WINDOWS
LONG WINAPI exceptionHandler(struct _EXCEPTION_POINTERS* apExceptionInfo)
{
    Sleep(10000);
    Logging::error() << "Creating dump";
    createMinidump(apExceptionInfo);
    if (mserver)
    {
        Logging::error()
            << "The server terminated unexpectedly. Trying to save game "
               "progress.";
        mserver->close();
    }
    return EXCEPTION_CONTINUE_SEARCH;
}
#endif

void onError(int i)
{
    Sleep(10000);
    Logging::error() << "Creating dump";
    createMinidump(0);
    if (mserver)
    {
        Logging::error()
            << "The server terminated unexpectedly. Trying to save game "
               "progress.";
        mserver->close();
    }
}

void onExit()
{
    Sleep(10000);
    Logging::info() << "Programm exited";
    onError(0);
}

bool exited = false;

class StatusBar : public StickyConsoleContent
{
public:
    StatusBar()
        : StickyConsoleContent()
    {}

    int print() const override
    {
        if (!exited && Game::INSTANCE)
        {
            std::cout << "\r\33[0K"; // erase current line
            int tps = Game::INSTANCE->getTicksPerSecond();
            std::cout << "Players: " << Game::INSTANCE->getPlayerCount()
                      << "\tChunks: " << Game::INSTANCE->getChunkCount()
                      << "\tChunks/s: "
                      << (Game::INSTANCE->zGenerator()
                                 ? Game::INSTANCE->zGenerator()
                                       ->getGeneratedChunksPerSecond()
                                 : 0)
                      << "\ttps: ";
            if (tps < 15)
            { // red
                std::cout << "\033[1;31m";
            }
            else if (tps < 20)
            { // yellow
                std::cout << "\033[1;33m";
            }
            else
            { // green
                std::cout << "\033[1;32m";
            }
            std::cout << tps << /* reset color */ "\033[0m"
                      << "\tAverage Tick Time: "
                      << Game::INSTANCE->getAverageTickTime();
            return 1;
        }
        return 0;
    }
};

int main()
{
#ifdef _WINDOWS
    SetUnhandledExceptionFilter(exceptionHandler);
#endif
    Framework::initFramework();

    Game::consoleHandler = new ConsoleHandler();

#ifndef _WINDOWS
    struct rlimit core_limits;
    core_limits.rlim_cur = core_limits.rlim_max = RLIM_INFINITY;
    setrlimit(RLIMIT_CORE, &core_limits);

#endif
    Zeit* z = getZeit();
    Text* pfad = new Text("log/");
    Text* tmp = z->getZeit("y-m-d_h-i-s.log");
    pfad->append(*tmp);
    tmp->release();
    z->release();
    Datei* logFile = new Datei(pfad);
    Logging::LoggingChannel* fileLogger
        = new Logging::FileLoggingChannel(logFile);
    fileLogger->setFormat(Logging::LoggingFormatBuilder()
            .datetime("h:i:s")
            .level(false)
            .text(": ")
            .build());
    Logging::zLoggingHandler()->addChannel(fileLogger);
    Logging::zLoggingHandler()->removeLoggingChannel(
        Logging::LogLevel::Error, fileLogger);
    Logging::zLoggingHandler()->removeLoggingChannel(
        Logging::LogLevel::Warning, fileLogger);
    Logging::LoggingChannel* errorFileLogger = new Logging::FileLoggingChannel(
        dynamic_cast<Datei*>(logFile->getThis()));
    errorFileLogger->setFormat(Logging::LoggingFormatBuilder()
            .datetime("h:i:s")
            .level()
            .fileName()
            .functionName()
            .text("(")
            .fileLine(false)
            .text("): ")
            .build());
    Framework::Logging::zLoggingHandler()->addChannel(
        Logging::LogLevel::Warning,
        dynamic_cast<Logging::LoggingChannel*>(errorFileLogger->getThis()));
    Framework::Logging::zLoggingHandler()->addChannel(
        Logging::LogLevel::Error, errorFileLogger);
    Logging::LoggingChannel* consoleLogger
        = new Logging::ConsoleHandlerLoggingChannel(
            dynamic_cast<ConsoleHandler*>(Game::consoleHandler->getThis()));
    consoleLogger->setFormat(Logging::LoggingFormatBuilder()
            .color(Logging::LogLevel::Debug, Color::LIGHT_BLUE)
            .color(Logging::LogLevel::Trace, Color::LIGHT_CYAN)
            .datetime("h:i:s")
            .level(false)
            .text(": ")
            .build());
    Framework::Logging::zLoggingHandler()->addChannel(consoleLogger);
    Framework::Logging::zLoggingHandler()->removeLoggingChannel(
        Logging::LogLevel::Error, consoleLogger);
    Framework::Logging::zLoggingHandler()->removeLoggingChannel(
        Logging::LogLevel::Warning, consoleLogger);
    Logging::LoggingChannel* errorConsoleLogger
        = new Logging::ConsoleHandlerLoggingChannel(
            dynamic_cast<ConsoleHandler*>(Game::consoleHandler->getThis()));
    errorConsoleLogger->setFormat(Logging::LoggingFormatBuilder()
            .color(Logging::LogLevel::Warning, Color::LIGHT_YELLOW)
            .color(Logging::LogLevel::Error, Color::LIGHT_RED)
            .datetime("h:i:s")
            .level()
            .fileName()
            .functionName()
            .text("(")
            .fileLine(false)
            .text("): ")
            .build());
    Framework::Logging::zLoggingHandler()->addChannel(
        Logging::LogLevel::Warning,
        dynamic_cast<Logging::LoggingChannel*>(errorConsoleLogger->getThis()));
    Framework::Logging::zLoggingHandler()->addChannel(
        Logging::LogLevel::Error, errorConsoleLogger);

    Game::consoleInput = new InputLine();
    Game::consoleHandler->addContent(
        Game::consoleInput, ConsoleContentPosition::Bottom);
    Game::consoleHandler->addContent(
        new StatusBar(), ConsoleContentPosition::Bottom);

    Logging::info() << "Starting ...";
    Logging::info() << "Loading config file fcInit.ini ...";

    InitDatei* dat = new InitDatei("fcInit.ini");
    if (!dat->laden())
    {
        Logging::error() << "Datei konnte nicht gelesen werden. Das Programm "
                            "wird geschlossen.";
        dat->release();
        Game::consoleHandler->release();
        Framework::releaseFramework();
        return 1;
    }
    const char* wichtig[]
        = {"SSLPort", "SSLCert", "SSLKey", "SSLPasswort", "Port"};
    for (const char* w : wichtig)
    {
        if (!dat->wertExistiert(w))
        {
            Logging::error()
                << "The value '" << w
                << "' was not specified. The Server can not start.";
            dat->release();
            Game::consoleHandler->release();
            Framework::releaseFramework();
            return 1;
        }
    }

    mserver = new FactoryCraftServer(dat);
    std::atexit(onExit);
    signal(SIGTERM, onError);
    signal(SIGSEGV, onError);
    signal(SIGILL, onError);
    signal(SIGABRT, onError);
    signal(SIGFPE, onError);
    signal(SIGINT, onError);

    Logging::info() << "The Server is now running.";
    mserver->run();
    exited = 1;
    mserver->release();
    mserver = 0;
    if (Game::INSTANCE)
    {
        Game* tmp = Game::INSTANCE;
        tmp->requestStop();
        tmp->warteAufThread(100000000);
        tmp->release();
        Game::INSTANCE = 0;
    }
    dat->release();
    Logging::info() << "The server was shut down successfully.";
    Game::consoleHandler->release();
    Framework::releaseFramework();
    return 0;
}