#include "Logging.h"

#include <iostream>

#include "File.h"
#include "Globals.h"
#include "Timer.h"

using namespace Framework;
using namespace Framework::Logging;

FlushingOStream Framework::Logging::trace(std::source_location location)
{
    DynamicBuffer* buff = new DynamicBuffer([&location](
                                                std::stringbuf& buffer) {
        zLoggingHandler()->log(LogLevel::Trace, location, buffer.str().c_str());
        return 0;
    });
    return FlushingOStream(buff, [buff]() { delete buff; });
}

FlushingOStream Framework::Logging::debug(std::source_location location)
{
    DynamicBuffer* buff = new DynamicBuffer([&location](
                                                std::stringbuf& buffer) {
        zLoggingHandler()->log(LogLevel::Debug, location, buffer.str().c_str());
        return 0;
    });
    return FlushingOStream(buff, [buff]() { delete buff; });
}

FlushingOStream Framework::Logging::info(std::source_location location)
{
    DynamicBuffer* buff = new DynamicBuffer([&location](
                                                std::stringbuf& buffer) {
        zLoggingHandler()->log(LogLevel::Info, location, buffer.str().c_str());
        return 0;
    });
    return FlushingOStream(buff, [buff]() { delete buff; });
}

FlushingOStream Framework::Logging::warning(std::source_location location)
{
    DynamicBuffer* buff
        = new DynamicBuffer([&location](std::stringbuf& buffer) {
              zLoggingHandler()->log(
                  LogLevel::Warning, location, buffer.str().c_str());
              return 0;
          });
    return FlushingOStream(buff, [buff]() { delete buff; });
}

FlushingOStream Framework::Logging::error(std::source_location location)
{
    DynamicBuffer* buff = new DynamicBuffer([&location](
                                                std::stringbuf& buffer) {
        zLoggingHandler()->log(LogLevel::Error, location, buffer.str().c_str());
        return 0;
    });
    return FlushingOStream(buff, [buff]() { delete buff; });
}

LoggingHandler* Framework::Logging::zLoggingHandler()
{
    return Framework::loggingHandler;
}

class BaseLoggingFormat : public LoggingFormat
{
public:
    BaseLoggingFormat()
        : LoggingFormat()
    {}

    virtual Framework::Text formatMessage(const Framework::Text& msg,
        LogLevel level,
        const std::source_location& location) const override
    {
        return msg;
    }
};

Framework::Logging::LoggingFormat::LoggingFormat()
    : ReferenceCounter()
{}

Framework::Logging::LoggingFormat::~LoggingFormat() {}

LoggingFormatBuilder::LoggingFormatBuilder(
    std::function<LoggingFormat*(LoggingFormat*)> formatTransformer)
    : formatTransformer(formatTransformer)
{}

Framework::Logging::LoggingFormatBuilder::LoggingFormatBuilder()
{
    formatTransformer = [](LoggingFormat* format) { return format; };
}

class DateTimeLoggingFormat : public LoggingFormat
{
private:
    LoggingFormat* after;
    Text format;
    bool postBlank;

public:
    DateTimeLoggingFormat(LoggingFormat* after, Text format, bool postBlank)
        : LoggingFormat(),
          after(after),
          format(format),
          postBlank(postBlank)
    {}

    ~DateTimeLoggingFormat()
    {
        after->release();
    }

    virtual Framework::Text formatMessage(const Framework::Text& msg,
        LogLevel level,
        const std::source_location& location) const override
    {
        Time* z = getTime();
        Text* time = z->getTime(format);
        Text result;
        if (postBlank)
        {
            result = *time + " " + after->formatMessage(msg, level, location);
        }
        else
        {
            result = *time + after->formatMessage(msg, level, location);
        }
        time->release();
        z->release();
        return result;
    }
};

LoggingFormatBuilder Framework::Logging::LoggingFormatBuilder::datetime(
    Framework::Text format, bool postBlank) const
{
    std::function<LoggingFormat*(LoggingFormat*)> formatTransformer
        = this->formatTransformer;
    return LoggingFormatBuilder(
        [format, postBlank, formatTransformer](LoggingFormat* f) {
            return formatTransformer(
                new DateTimeLoggingFormat(f, format, postBlank));
        });
}

class FileNameLoggingFormat : public LoggingFormat
{
    LoggingFormat* after;
    bool postBlank;

public:
    FileNameLoggingFormat(LoggingFormat* after, bool postBlank)
        : LoggingFormat(),
          after(after),
          postBlank(postBlank)
    {}

    ~FileNameLoggingFormat()
    {
        after->release();
    }

    virtual Framework::Text formatMessage(const Framework::Text& msg,
        LogLevel level,
        const std::source_location& location) const override
    {
        if (postBlank)
        {
            return Text(location.file_name()) + " "
                 + after->formatMessage(msg, level, location);
        }
        else
        {
            return Text(location.file_name())
                 + after->formatMessage(msg, level, location);
        }
    }
};

LoggingFormatBuilder Framework::Logging::LoggingFormatBuilder::fileName(
    bool postBlank) const
{
    std::function<LoggingFormat*(LoggingFormat*)> formatTransformer
        = this->formatTransformer;
    return LoggingFormatBuilder(
        [postBlank, formatTransformer](LoggingFormat* f) {
            return formatTransformer(new FileNameLoggingFormat(f, postBlank));
        });
}

class FileLineLoggingFormat : public LoggingFormat
{
    LoggingFormat* after;
    bool postBlank;

public:
    FileLineLoggingFormat(LoggingFormat* after, bool postBlank)
        : LoggingFormat(),
          after(after),
          postBlank(postBlank)
    {}

    ~FileLineLoggingFormat()
    {
        after->release();
    }

    virtual Framework::Text formatMessage(const Framework::Text& msg,
        LogLevel level,
        const std::source_location& location) const override
    {
        if (postBlank)
        {
            return Text((int)location.line()) + ":"
                 + Text((int)location.column()) + " "
                 + after->formatMessage(msg, level, location);
        }
        else
        {
            return Text((int)location.line()) + ":"
                 + Text((int)location.column())
                 + after->formatMessage(msg, level, location);
        }
    }
};

LoggingFormatBuilder Framework::Logging::LoggingFormatBuilder::fileLine(
    bool postBlank) const
{
    std::function<LoggingFormat*(LoggingFormat*)> formatTransformer
        = this->formatTransformer;
    return LoggingFormatBuilder(
        [postBlank, formatTransformer](LoggingFormat* f) {
            return formatTransformer(new FileLineLoggingFormat(f, postBlank));
        });
}

class FunctionLoggingFormat : public LoggingFormat
{
    LoggingFormat* after;
    bool postBlank;

public:
    FunctionLoggingFormat(LoggingFormat* after, bool postBlank)
        : LoggingFormat(),
          after(after),
          postBlank(postBlank)
    {}

    ~FunctionLoggingFormat()
    {
        after->release();
    }

    virtual Framework::Text formatMessage(const Framework::Text& msg,
        LogLevel level,
        const std::source_location& location) const override
    {
        if (postBlank)
        {
            return Text(location.function_name()) + " "
                 + after->formatMessage(msg, level, location);
        }
        else
        {
            return Text(location.function_name())
                 + after->formatMessage(msg, level, location);
        }
    }
};

LoggingFormatBuilder Framework::Logging::LoggingFormatBuilder::functionName(
    bool postBlank) const
{
    std::function<LoggingFormat*(LoggingFormat*)> formatTransformer
        = this->formatTransformer;
    return LoggingFormatBuilder(
        [postBlank, formatTransformer](LoggingFormat* f) {
            return formatTransformer(new FunctionLoggingFormat(f, postBlank));
        });
}

class LevelLoggingFormat : public LoggingFormat
{
    LoggingFormat* after;
    bool postBlank;

public:
    LevelLoggingFormat(LoggingFormat* after, bool postBlank)
        : LoggingFormat(),
          after(after),
          postBlank(postBlank)
    {}

    ~LevelLoggingFormat()
    {
        after->release();
    }

    virtual Framework::Text formatMessage(const Framework::Text& msg,
        LogLevel level,
        const std::source_location& location) const override
    {
        Text levelStr;
        switch (level)
        {
        case LogLevel::Trace:
            levelStr = "TRACE";
            break;
        case LogLevel::Debug:
            levelStr = "DEBUG";
            break;
        case LogLevel::Info:
            levelStr = "INFO";
            break;
        case LogLevel::Warning:
            levelStr = "WARNING";
            break;
        case LogLevel::Error:
            levelStr = "ERROR";
            break;
        }
        if (postBlank)
        {
            return levelStr + " " + after->formatMessage(msg, level, location);
        }
        else
        {
            return levelStr + after->formatMessage(msg, level, location);
        }
    }
};

LoggingFormatBuilder Framework::Logging::LoggingFormatBuilder::level(
    bool postBlank) const
{
    std::function<LoggingFormat*(LoggingFormat*)> formatTransformer
        = this->formatTransformer;
    return LoggingFormatBuilder(
        [postBlank, formatTransformer](LoggingFormat* f) {
            return formatTransformer(new LevelLoggingFormat(f, postBlank));
        });
}

class ColorLoggingFormat : public LoggingFormat
{
    std::function<bool(LogLevel)> enabled;
    Color color;
    LoggingFormat* after;

public:
    ColorLoggingFormat(LoggingFormat* after,
        std::function<bool(LogLevel)> enabled,
        Color color)
        : LoggingFormat(),
          enabled(enabled),
          color(color),
          after(after)
    {}

    ~ColorLoggingFormat()
    {
        after->release();
    }

    virtual Framework::Text formatMessage(const Framework::Text& msg,
        LogLevel level,
        const std::source_location& location) const override
    {
        bool active = enabled(level);
        if (active)
        {
            if (color < Color::LIGHT_BLACK)
            {
                return Text("\033[1;") + Text(30 + (int)color) + "m"
                     + after->formatMessage(msg, level, location) + "\033[0m";
            }
            else
            {
                return Text("\033[1;") + Text(90 + (int)color - 8) + "m"
                     + after->formatMessage(msg, level, location) + "\033[0m";
            }
        }
        return after->formatMessage(msg, level, location);
    }
};

LoggingFormatBuilder Framework::Logging::LoggingFormatBuilder::color(
    Color color) const
{
    std::function<LoggingFormat*(LoggingFormat*)> formatTransformer
        = this->formatTransformer;
    return LoggingFormatBuilder([color, formatTransformer](LoggingFormat* f) {
        return formatTransformer(new ColorLoggingFormat(
            f, [](LogLevel level) { return true; }, color));
    });
}

LoggingFormatBuilder Framework::Logging::LoggingFormatBuilder::color(
    LogLevel level, Color color) const
{
    std::function<LoggingFormat*(LoggingFormat*)> formatTransformer
        = this->formatTransformer;
    return LoggingFormatBuilder(
        [level, color, formatTransformer](LoggingFormat* f) {
            return formatTransformer(new ColorLoggingFormat(
                f, [level](LogLevel l) { return level == l; }, color));
        });
}

class BackgroundColorLoggingFormat : public LoggingFormat
{
    std::function<bool(LogLevel)> enabled;
    Color color;
    LoggingFormat* after;

public:
    BackgroundColorLoggingFormat(LoggingFormat* after,
        std::function<bool(LogLevel)> enabled,
        Color color)
        : LoggingFormat(),
          enabled(enabled),
          color(color),
          after(after)
    {}

    ~BackgroundColorLoggingFormat()
    {
        after->release();
    }

    virtual Framework::Text formatMessage(const Framework::Text& msg,
        LogLevel level,
        const std::source_location& location) const override
    {
        bool active = enabled(level);
        if (active)
        {
            if (color < Color::LIGHT_BLACK)
            {
                return Text("\033[1;") + Text(40 + (int)color) + "m"
                     + after->formatMessage(msg, level, location) + "\033[0m";
            }
            else
            {
                return Text("\033[1;") + Text(100 + (int)color - 8) + "m"
                     + after->formatMessage(msg, level, location) + "\033[0m";
            }
        }
        return after->formatMessage(msg, level, location);
    }
};

LoggingFormatBuilder Framework::Logging::LoggingFormatBuilder::backgroundColor(
    Color color) const
{
    std::function<LoggingFormat*(LoggingFormat*)> formatTransformer
        = this->formatTransformer;
    return LoggingFormatBuilder([color, formatTransformer](LoggingFormat* f) {
        return formatTransformer(new BackgroundColorLoggingFormat(
            f, [](LogLevel level) { return true; }, color));
    });
}

LoggingFormatBuilder Framework::Logging::LoggingFormatBuilder::backgroundColor(
    LogLevel level, Color color) const
{
    std::function<LoggingFormat*(LoggingFormat*)> formatTransformer
        = this->formatTransformer;
    return LoggingFormatBuilder(
        [level, color, formatTransformer](LoggingFormat* f) {
            return formatTransformer(new BackgroundColorLoggingFormat(
                f, [level](LogLevel l) { return level == l; }, color));
        });
}

class TextLoggingFormat : public LoggingFormat
{
    Text text;
    LoggingFormat* after;

public:
    TextLoggingFormat(LoggingFormat* after, Text text)
        : LoggingFormat(),
          text(text),
          after(after)
    {}

    ~TextLoggingFormat()
    {
        after->release();
    }

    virtual Framework::Text formatMessage(const Framework::Text& msg,
        LogLevel level,
        const std::source_location& location) const override
    {
        return text + after->formatMessage(msg, level, location);
    }
};

LoggingFormatBuilder Framework::Logging::LoggingFormatBuilder::text(
    Framework::Text text) const
{
    std::function<LoggingFormat*(LoggingFormat*)> formatTransformer
        = this->formatTransformer;
    return LoggingFormatBuilder([text, formatTransformer](LoggingFormat* f) {
        return formatTransformer(new TextLoggingFormat(f, text));
    });
}

LoggingFormat* Framework::Logging::LoggingFormatBuilder::build() const
{
    return formatTransformer(new BaseLoggingFormat());
}

Framework::Logging::LoggingChannel::LoggingChannel()
    : ReferenceCounter(),
      format(0)
{}

Framework::Logging::LoggingChannel::~LoggingChannel()
{
    if (this->format)
    {
        this->format->release();
    }
}

void Framework::Logging::LoggingChannel::setFormat(LoggingFormat* format)
{
    if (this->format)
    {
        this->format->release();
    }
    this->format = format;
}

void Framework::Logging::LoggingChannel::writeMessage(
    const Text& msg, LogLevel level, const std::source_location& location) const
{
    if (this->format)
    {
        Text formatted = this->format->formatMessage(msg, level, location);
        this->writeMessage(formatted);
    }
    else
    {
        this->writeMessage(msg);
    }
}

Framework::Logging::CoutLoggingChannel::CoutLoggingChannel()
    : LoggingChannel()
{}

void Framework::Logging::CoutLoggingChannel::writeMessage(const Text& msg) const
{
    std::cout << msg.getText() << std::endl;
}

Framework::Logging::OutputDebugStringLoggingChannel::
    OutputDebugStringLoggingChannel()
    : LoggingChannel()
{}

void Framework::Logging::OutputDebugStringLoggingChannel::writeMessage(
    const Text& msg) const
{
#ifdef WIN32
    OutputDebugStringA(msg.getText());
    OutputDebugStringA("\n");
#else
    std::cout << msg.getText() << std::endl;
#endif
}

Framework::Logging::ConsoleHandlerLoggingChannel::ConsoleHandlerLoggingChannel(
    ConsoleHandler* consolehandler)
    : LoggingChannel(),
      consolehandler(consolehandler)
{}

Framework::Logging::ConsoleHandlerLoggingChannel::
    ~ConsoleHandlerLoggingChannel()
{
    consolehandler->release();
}

void Framework::Logging::ConsoleHandlerLoggingChannel::writeMessage(
    const Text& msg) const
{
    consolehandler->print(msg);
}

Framework::Logging::FileLoggingChannel::FileLoggingChannel(Text filePath)
    : FileLoggingChannel(new File(filePath))
{}

Framework::Logging::FileLoggingChannel::FileLoggingChannel(File* file)
    : LoggingChannel(),
      file(file)
{
    if (!file->exists())
    {
        file->create();
    }
    if (!file->isOpen())
    {
        file->open(File::Style::write | File::Style::end | File::Style::read);
    }
}

Framework::Logging::FileLoggingChannel::~FileLoggingChannel()
{
    if (file->isOpen())
    {
        file->close();
    }
    file->release();
}

void Framework::Logging::FileLoggingChannel::writeMessage(const Text& msg) const
{
    file->write(msg.getText(), msg.getLength());
    file->write("\n", 1);
    file->flush();
}

Framework::Logging::LoggingHandler::LoggingHandler()
    : ReferenceCounter(),
      channels(new RCArray<LoggingChannel>*[5])
{
    for (int i = 0; i < 5; i++)
    {
        channels[i] = new RCArray<LoggingChannel>();
    }
}

Framework::Logging::LoggingHandler::~LoggingHandler()
{
    for (int i = 0; i < 5; i++)
    {
        channels[i]->release();
    }
    delete[] channels;
}

void Framework::Logging::LoggingHandler::addChannel(
    LogLevel level, LoggingChannel* channel)
{
    channels[(int)level]->add(channel);
}

void Framework::Logging::LoggingHandler::removeLoggingChannel(
    LogLevel level, LoggingChannel* zChannel)
{
    for (auto i = channels[(int)level]->begin(); i;)
    {
        if (*i == zChannel)
        {
            i.remove();
        }
        else
        {
            i++;
        }
    }
}

void Framework::Logging::LoggingHandler::addChannel(LoggingChannel* channel)
{
    for (int i = 0; i < 5; i++)
    {
        channels[i]->add(dynamic_cast<LoggingChannel*>(channel->getThis()));
    }
    channel->release();
}

void Framework::Logging::LoggingHandler::removeLoggingChannel(
    LoggingChannel* zChannel)
{
    for (int j = 0; j < 5; j++)
    {
        for (auto i = channels[j]->begin(); i;)
        {
            if (*i == zChannel)
            {
                i.remove();
            }
            else
            {
                i++;
            }
        }
    }
}

void Framework::Logging::LoggingHandler::log(
    LogLevel level, std::source_location& location, const Text message) const
{
    for (const LoggingChannel* channel : *channels[(int)level])
    {
        channel->writeMessage(message, level, location);
    }
}