#include "client_interface.hh"
#include "connection.hh"
#include "../TradeCore/message.hh"
#include "../TradeCore/trade_data.hh"
#include <QString>
#include <QMutex>
#include <memory>
#include <atomic>

namespace{

constexpr char dateTimeFormat[] = "yyyy.MM.dd HH:mm";

struct Context{
    std::shared_ptr<Connection> connection;
    bool errorFlag;
    QString errorString;
    DataRequest dataRequest;
    TradeData dataResponse;
    OrderVector tradeDecision;
};

std::atomic<int> magic_(0);
QMutex mutex_;
QMap<int, Context> context_;

Context& getContext(int id)
{
    QMutexLocker mutexLocker(&mutex_);
    return context_[id];
}

bool checkInvalidMessage(Context& context, const Message& message)
{
    if (message.type == MessageType::Invalid) {
        context.errorString =
            QObject::tr("Invalid message received (")
            + context.connection->socketError() + QObject::tr(")");
        context.errorFlag = true;
        return false;
    }

    return true;
}

bool checkStatusReport(Context& context, const Message& message)
{
    if (!checkInvalidMessage(context, message))
        return false;

    if (message.type != MessageType::StatusReport) {
        context.errorString =
            QObject::tr("Unexpected message type received: ")
            + toString(message.type);
        context.errorFlag = true;
        return false;
    }

    if (message.type == MessageType::StatusReport
        && message.statusReport.status != "OK")
    {
        context.errorString =
            QObject::tr("Server returned error status (")
            + message.statusReport.error + QObject::tr(")");
        context.errorFlag = true;
        return false;
    }

    return true;
}

bool checkMessageType(
    Context& context, const Message &message, MessageType expectedType)
{
    if (!checkInvalidMessage(context, message))
        return false;

    if (message.type != expectedType) {
        context.errorString =
            QObject::tr("Unexpected message type received: ")
            + toString(message.type);
        context.errorFlag = true;
        return false;
    }

    return true;
}

} //namespace

int magicNumber()
{
    return magic_++;
}

void initialize(int id)
{
    QMutexLocker mutexLocker(&mutex_);
    context_.insert(id, Context());
    mutexLocker.unlock();
    getContext(id).connection.reset(new Connection());
}

void deinitialize(int id)
{
    QMutexLocker mutexLocker(&mutex_);
    context_.remove(id);
}

void connect(int id, const char* server)
{
    auto& context = getContext(id);
    context.errorFlag = !context.connection->connect(server);

    if (context.errorFlag)
        context.errorString = context.connection->socketError();
}

void registerClient(
    int id, const char* strategy, const char* currency,
    const char* timeframe, int digits)
{
    auto& context = getContext(id);

    Registration registration;
    registration.strategy = strategy;
    registration.currency = currency;
    registration.timeframe = timeframe;
    registration.digits = digits;

    Message request;
    request.clientId = id;
    request.registration = registration;
    request.type = MessageType::RegistrationRequest;

    context.errorFlag = !context.connection->sendMessage(request);

    if (context.errorFlag) {
        context.errorString = context.connection->socketError();
        return;
    }

    Message response = context.connection->readMessage();

    if (checkStatusReport(context, response)) {
        context.errorFlag = false;
        context.errorString = QString();
    }
}

void requestUpdate(int id)
{
    auto& context = getContext(id);
    context.dataRequest.clear();

    Message request;
    request.clientId = id;
    request.type = MessageType::UpdateRequest;

    context.errorFlag = !context.connection->sendMessage(request);

    if (context.errorFlag) {
        context.errorString = context.connection->socketError();
        return;
    }

    Message response = context.connection->readMessage();

    if (!checkMessageType(context, response, MessageType::UpdateDataRequest))
        return;

    context.dataRequest = response.dataRequest;
    context.errorFlag = false;
    context.errorString = QString();
}

int requestedNameCount(int id)
{
    auto& context = getContext(id);
    return context.dataRequest.count();
}

int requestedDataCount(int id, int index)
{
    auto& context = getContext(id);
    return context.dataRequest[index].second;
}

void requestedDataName(int id, int index, char* name)
{
    auto& context = getContext(id);
    strcpy(name, context.dataRequest[index].first.c_str());
}

void appendOrder(
    int id, const char* event, int magic, int ticket, const char* type,
    double openPrice, double closePrice, double lots, double stopLoss,
    double takeProfit, double profit, const char* time, const char* openTime)
{
    auto& context = getContext(id);

    Order order;
    order.event = event;
    order.magic = magic;
    order.ticket = ticket;
    order.type = type;
    order.openPrice = openPrice;
    order.closePrice = closePrice;
    order.lots = lots;
    order.stopLoss = stopLoss;
    order.takeProfit = takeProfit;
    order.profit = profit;
    order.time = QDateTime::fromString(time, dateTimeFormat);
    order.openTime = QDateTime::fromString(openTime, dateTimeFormat);

    context.dataResponse.addOrder(order);
}

void appendCandle(
    int id, const char* dateTime, double open, double high,
    double low, double close, long volume)
{
    auto& context = getContext(id);

    ChartCandle candle;
    candle.set(open, high, low, close, volume);
    candle.dateTime(QDateTime::fromString(dateTime, dateTimeFormat));

    context.dataResponse.addCandle(candle);
}

void appendValue(
    int id, const char* name, const char* dateTime, double value)
{
    auto& context = getContext(id);
    QDateTime qDateTime = QDateTime::fromString(dateTime, dateTimeFormat);
    context.dataResponse.addValue(name, qDateTime, value);
}

void appendVector(
    int id, const char* name, const char* dateTime, double* array, int size)
{
    auto& context = getContext(id);
    QDateTime qDateTime = QDateTime::fromString(dateTime, dateTimeFormat);

    Vector vector;
    for (int i = 0; i < size; i++)
        vector.push_back(array[i]);

    context.dataResponse.addVector(name, qDateTime, vector);
}

void dataResponse(int id)
{
    auto& context = getContext(id);

    Message request;
    request.clientId = id;
    request.type = MessageType::UpdateDataResponse;
    request.dataResponse = context.dataResponse;

    context.errorFlag = !context.connection->sendMessage(request);

    if (context.errorFlag) {
        context.errorString = context.connection->socketError();
        return;
    }

    Message response = context.connection->readMessage();

    if (checkStatusReport(context, response)) {
        context.errorFlag = false;
        context.errorString = QString();
    }

    context.dataResponse.clear();
}

void requestDecision(int id)
{
    auto& context = getContext(id);

    Message request;
    request.clientId = id;
    request.type = MessageType::DecisionRequest;

    context.errorFlag = !context.connection->sendMessage(request);

    if (context.errorFlag) {
        context.errorString = context.connection->socketError();
        return;
    }

    Message response = context.connection->readMessage();

    if (!checkMessageType(context, response, MessageType::DecisionResponse))
        return;

    context.tradeDecision = response.tradeDecision;
    context.errorFlag = false;
    context.errorString = QString();
}

int orderCount(int id)
{
    return getContext(id).tradeDecision.size();
}

void orderCommand(int id, int index, char* command)
{
    strcpy(command, getContext(id).tradeDecision[index].command.c_str());
}

int orderTicket(int id, int index)
{
    return getContext(id).tradeDecision[index].ticket;
}

void orderType(int id, int index, char* type)
{
    strcpy(type, getContext(id).tradeDecision[index].type.c_str());
}

double orderLots(int id, int index)
{
    return getContext(id).tradeDecision[index].lots;
}

int orderSlippage(int id, int index)
{
    return getContext(id).tradeDecision[index].slippage;
}

int orderStopLoss(int id, int index)
{
    return getContext(id).tradeDecision[index].stopLossPt;
}

int orderTakeProfit(int id, int index)
{
    return getContext(id).tradeDecision[index].takeProfitPt;
}

void setOrderError(int id, int index, const char* error)
{
    getContext(id).tradeDecision[index].error = error;
}

void decisionStatus(int id)
{
    auto& context = getContext(id);

    Message request;
    request.clientId = id;
    request.tradeDecision = context.tradeDecision;
    request.type = MessageType::DecisionStatus;

    context.errorFlag = !context.connection->sendMessage(request);

    if (context.errorFlag) {
        context.errorString = context.connection->socketError();
        return;
    }

    Message response = context.connection->readMessage();

    if (checkStatusReport(context, response)) {
        context.errorFlag = false;
        context.errorString = QString();
    }
}

void lastError(int id, char* error)
{
    strcpy(error, getContext(id).errorString.toStdString().c_str());
}

bool hasError(int id)
{
    return getContext(id).errorFlag;
}
