#include "database.hh"
#include <QtSql/QSqlError>
#include <QtSql/QSqlQuery>
#include <QVariant>
#include <QDebug>

Database::Database(const QString& connectionName) :
    sql_(QSqlDatabase::addDatabase("QMYSQL", connectionName))
{}

Database::~Database()
{
    QString connectionName = sql_.connectionName();
    sql_.close();

    // reference to database still held by sql_, reset
    // done by assigning a default constructed value
    sql_ = QSqlDatabase();

    QSqlDatabase::removeDatabase(connectionName);
}

bool Database::connect(
    const QString& hostname, const QString& database,
    const QString& username, const QString& password)
{
    sql_.setHostName(hostname);
    sql_.setDatabaseName(database);
    sql_.setUserName(username);
    sql_.setPassword(password);

    return sql_.open();
}

QString Database::lastError() const
{
    return sql_.lastError().text();
}

QMap<std::string, int> Database::enumValues(const QString& enumName)
{
    QMap<std::string, int> enumeration;

    if (!sql_.isOpen())
        return enumeration;

    QSqlQuery query(sql_);

    query.prepare(
        "SELECT name, value FROM enum "
        "WHERE enum=:enumName");

    query.bindValue(":enumName", enumName);

    if (!query.exec())
        return enumeration;

    while (query.next()) {
        enumeration.insert(
            query.value(0).toString().toStdString(), query.value(1).toInt());
    }

    return enumeration;
}

void Database::logTrade(
    const OrderVector& orders, const CandleVector& price,
    CandleVector::const_iterator from, const ChartCandle& last,
    const ClientInfo& client)
{
    if (!sql_.isOpen())
        return;

    qlonglong tradeId = logTrade(client);

    if (tradeId == -1)
        return;

    logEvents(tradeId, orders);
    logPriceHistory(tradeId, price, from, last);
}

qlonglong Database::logTrade(const ClientInfo& client)
{
    if (!sql_.isOpen())
        return -1;

    auto currencies = enumValues("Currency");
    auto timeFrames = enumValues("TimeFrame");

    QSqlQuery query(sql_);

    query.prepare(
        "INSERT INTO trade "
        "   (client, strategy, currency, timeframe, digits, created) "
        "VALUES "
        "   (:id, :strategy, :currency, :timeframe, :digits, now())");

    query.bindValue(":id", client.id);
    query.bindValue(":strategy", QString::fromStdString(client.strategy));
    query.bindValue(":currency", currencies.value(client.currency, 0));
    query.bindValue(":timeframe", timeFrames.value(client.timeframe, 0));
    query.bindValue(":digits", client.digits);

    if (!query.exec())
        return -1;

    return query.lastInsertId().toLongLong();
}

void Database::logEvents(qlonglong tradeId, const OrderVector& orders)
{
    if (!sql_.isOpen())
        return;

    auto eventTypes = enumValues("EventType");

    for (auto& order : orders) {
        QSqlQuery query(sql_);

        query.prepare(
            "INSERT INTO event "
            "   (trade, event, ticket, openPrice, closePrice, lots, "
            "    stopLoss, takeProfit, profit, time) "
            "VALUES "
            "   (:trade, :event, :ticket, :openPrice, :closePrice, :lots, "
            "    :stopLoss, :takeProfit, :profit, :time)");

        query.bindValue(":trade", tradeId);
        query.bindValue(":event", eventTypes.value(order.event, 0));
        query.bindValue(":ticket", order.ticket);
        query.bindValue(":openPrice", order.openPrice);
        query.bindValue(":closePrice", order.closePrice);
        query.bindValue(":lots", order.lots);
        query.bindValue(":stopLoss", order.stopLoss);
        query.bindValue(":takeProfit", order.takeProfit);
        query.bindValue(":profit", order.profit);
        query.bindValue(":time", order.time);

        if (!query.exec())
            break;
    }
}

bool Database::logCandle(qlonglong tradeId, const ChartCandle& candle)
{
    QSqlQuery query(sql_);

    query.prepare(
        "INSERT INTO price "
        "   (trade, time, open, high, low, close, volume) "
        "VALUES "
        "   (:trade, :time, :open, :high, :low, :close, :volume)");

    query.bindValue(":trade", tradeId);
    query.bindValue(":time", candle.dateTime());
    query.bindValue(":open", candle.open());
    query.bindValue(":high", candle.high());
    query.bindValue(":low", candle.low());
    query.bindValue(":close", candle.close());
    query.bindValue(":volume", (qlonglong)candle.volume());

    return query.exec();
}

void Database::logPriceHistory(
    qlonglong tradeId, const CandleVector& price,
    CandleVector::const_iterator from, const ChartCandle& last)
{
    if (!sql_.isOpen())
        return;

    for (auto it = from; it != price.cend(); it++) {
        if (!logCandle(tradeId, *it))
            return;
    }

    if (from != price.cend() && price.back().dateTime() != last.dateTime())
        logCandle(tradeId, last);
}
