#include "main_window.hh"
#include "ui_main_window.h"
#include "../TradeCore/exceptions.hh"
#include "../TradeCore/abstract_strategy.hh"
#include "../TradeCore/logger.hh"
#include "../TradeCore/functions.hh"
#include <QDebug>
#include <QMessageBox>
#include <QDir>
#include <QMenu>

namespace{

constexpr char timeFormat[] = "[HH:mm:ss] ";
constexpr char connectionName[] = "AutoTrader";
constexpr char moduleListName[] = "moduleList.xml";
constexpr char settingsName[] = "settings.xml";
constexpr char appDirName[] = ".AutoTrader";
constexpr char logFileName[] = "autoTrader.log";

QString toString(int clientId, const Registration& registration)
{
    return QString::number(clientId) + "-" + registration.strategy +
        "-" + registration.currency + "-" + registration.timeframe;
}

} //namespace

MainWindow::MainWindow(QWidget* parent):
    QMainWindow(parent), ui_(new Ui::MainWindow),
    tray_(nullptr), trayMenu_(nullptr), logger_(nullptr),
    db_(new Database(connectionName))
{
    ui_->setupUi(this);
    addAction(ui_->actionExit);

    checkApplicationDirectory();

    ui_->settingsWidget->setSettingsFile(
        absoluteFileName(settingsName, appDirName));

    if (ui_->settingsWidget->serverSettings().logger) {
        QString logFile = absoluteFileName(logFileName, appDirName);
        logger_.reset(new Logger(logFile.toStdString()));
    }

    if (ui_->settingsWidget->serverSettings().systray)
        initSystray();

    if (ui_->settingsWidget->databaseSettings().logging) {}
        connectToDatabase();

    connectSignalsSlots();

    ui_->moduleManager->loadModuleList(
        absoluteFileName(moduleListName, appDirName));

    startServer();
}

MainWindow::~MainWindow()
{
    delete ui_;
}

void MainWindow::checkApplicationDirectory()
{
    try {
        createApplicationDirectory(appDirName);
    } catch (StorageException& ex) {
        QMessageBox::critical(
            this, tr("Can not create application directory"), ex.what());
        exit(EXIT_FAILURE);
    }
}

void MainWindow::connectToDatabase()
{
    if (!ui_->settingsWidget->databaseSettings().logging)
        return;

    bool result = db_->connect(
        ui_->settingsWidget->databaseSettings().hostname,
        ui_->settingsWidget->databaseSettings().database,
        ui_->settingsWidget->databaseSettings().username,
        ui_->settingsWidget->databaseSettings().password);

    if (!result) {
        appendLog(
            tr("Can not connect to database ")
            + ui_->settingsWidget->databaseSettings().database
            + tr(" (") + db_->lastError() + tr(")"), true);
    } else {
        appendLog(
            tr("Connected to database ")
            + ui_->settingsWidget->databaseSettings().database);
        ui_->commandProcessor->setDatabase(db_);
    }
}

void MainWindow::connectSignalsSlots()
{
    connect(
        ui_->actionExit, QAction::triggered,
        this, MainWindow::onActionExit);

    connect(
        ui_->serverWidget, &ServerWidget::newConnectionCreated,
        [this](){appendLog(tr("New connection created"));});

    connect(
        ui_->serverWidget, &ServerWidget::connectionRegistered,
        this, &MainWindow::onConnectionRegistered);

    connect(
        ui_->serverWidget, &ServerWidget::messageReceived,
        ui_->commandProcessor, &CommandProcessor::handleMessage);

    connect(
        ui_->serverWidget, &ServerWidget::connectionClosed,
        this, &MainWindow::onConnectionClosed);

    connect(
        ui_->commandProcessor, &CommandProcessor::sendMessage,
        ui_->serverWidget, &ServerWidget::sendMessage);
}

void MainWindow::initSystray()
{
    if (!QSystemTrayIcon::isSystemTrayAvailable()) {
        QMessageBox::critical(
            this, QObject::tr("Systray error"),
            QObject::tr("System tray was not detected"));
        return;
    }

    QApplication::setQuitOnLastWindowClosed(false);

    trayMenu_ = new QMenu(this);
    trayMenu_->addAction(ui_->actionExit);

    tray_ = new QSystemTrayIcon(QIcon(":app24"), this);
    tray_->setContextMenu(trayMenu_);

    connect(
        tray_, &QSystemTrayIcon::activated,
        this, &MainWindow::onTrayActivated);

    tray_->show();
}

void MainWindow::startServer()
{
    if (ui_->serverWidget->listen()) {
        appendLog(tr("Listening on \"") + serverName + tr("\" ..."));
    } else {
        appendLog(tr("Error starting server"), true);
    }
}

void MainWindow::appendLog(const QString& str, bool error)
{
    QTime now = QTime::currentTime();
    auto nowStr = now.toString(timeFormat);

    ui_->serverLog->appendPlainText(nowStr + str);
    ui_->statusBar->showMessage(nowStr + str, 60000);

    if (logger_) {
        if (error)
            logger_->error(str.toStdString());
        else
            logger_->info(str.toStdString());
    }
}

void MainWindow::onConnectionRegistered(
    int clientId, const Registration& registration)
{
    appendLog(
        tr("Connection for ") + toString(clientId, registration)
        + tr(" registered"));

    std::unique_ptr<AbstractStrategy> strategy =
        ui_->moduleManager->getStrategy(registration.strategy);

    if (strategy) {
        ui_->commandProcessor->registerClient(
            clientId, registration, std::move(strategy));
        ui_->serverWidget->sendMessage(successStatusReport(
            clientId, MessageType::RegistrationRequest));
        appendLog(
            tr("Client ") + toString(clientId, registration)
            + tr(" registered"));
    }
    else {
        ui_->serverWidget->sendMessage(errorStatusReport(
            clientId, MessageType::RegistrationRequest,
            tr("Unable to register client")));
        appendLog(
            tr("Unable to register client ")
            + toString(clientId, registration));
        ui_->serverWidget->closeConnection(clientId);
    }
}

void MainWindow::onConnectionClosed(int clientId)
{
    appendLog(
        tr("Connection of client with id=") +
        QString::number(clientId) + tr(" closed"));
    ui_->commandProcessor->removeClient(clientId);
}

void MainWindow::closeEvent(QCloseEvent* event)
{
    if (tray_) {
        hide();
        event->ignore();
    } else {
        onActionExit();
    }
}

void MainWindow::onTrayActivated(QSystemTrayIcon::ActivationReason reason)
{
    if (reason == QSystemTrayIcon::DoubleClick) {
        if (isHidden())
            show();
        else
            hide();
    }
}

void MainWindow::onActionExit()
{
    ui_->serverWidget->disconnectAll();
    QApplication::quit();
}
