#include "format_convertor.hh"
#include "utilities.hh"
#include "../../TradeCore/chart_candle.hh"
#include "base_utility.hh"
#include <QStringList>
#include <QFile>
#include <QDebug>
#include <QFileInfo>
#include <QDir>
#include <memory>

namespace {

/**
 * @brief Unexact week difference
 * Reliable only if first and current have same year
 */
inline qint64 W1_diff(const QDateTime& first, const QDateTime& current)
{
    int weekDiff = current.date().weekNumber() - first.date().weekNumber();
    return (current.date().year() - first.date().year()) * 100 + weekDiff;
}

inline QString parseTimeFormat(const QString& format)
{
    QString str(format);
    str.chop(1);
    return str.remove(0, 3);
}

qint64 calcPeriod(const QDateTime& first, const QDateTime& current, ChartScale scale)
{
    QDateTime base(QDate(1979,1,1));
    switch (scale) {
        case ChartScale::NONE: return 0;
        case ChartScale::M1: return M1_diff(base, first) - M1_diff(base, current);
        case ChartScale::M5: return M5_diff(base, first) - M5_diff(base, current);
        case ChartScale::M15: return M15_diff(base, first) - M15_diff(base, current);
        case ChartScale::M30: return M30_diff(base, first) - M30_diff(base, current);
        case ChartScale::H1: return H1_diff(base, first) - H1_diff(base, current);
        case ChartScale::H4: return H4_diff(base, first) - H4_diff(base, current);
        case ChartScale::D1: return D1_diff(base, first) - D1_diff(base, current);
        case ChartScale::W1: return W1_diff(base, first) - W1_diff(base, current);
        case ChartScale::MN: return MN_diff(base, first) - MN_diff(base, current);
    }
    return 0;
}

std::unique_ptr<QFile> openOutputFile(QString pathName)
{
    std::unique_ptr<QFile> file(new QFile(pathName));
    return file->open(QIODevice::WriteOnly | QIODevice::Text) ? move(file) : nullptr;
}

void writeFirstLine(QTextStream& stream, ChartScale scale, CurrencyPair pair)
{
    stream << chartScaleToString(scale);
    stream << ", ";
    stream << currencyPairToString(pair);
    stream << "\n";
}

} //namespace

FormatConvertor::FormatConvertor() :
    inputFormat_(defaultFormat),
    inputSeparator_(defaultSeparator),
    currencyPair_(CurrencyPair::NONE),
    chartScale_(ChartScale::NONE),
    splitPeriod_(ChartScale::NONE)
{}

void FormatConvertor::inputFile(const QString& file)
{
    inputFile_ = file;
}

void FormatConvertor::inputFormat(const QString& format)
{
    inputFormat_ = format;
}

void FormatConvertor::outputFolder(const QString& folder)
{
    outputFolder_ = folder;
}

void FormatConvertor::outputPrefix(const QString& prefix)
{
    outputPrefix_ = prefix;
}

void FormatConvertor::outputSufix(const QString& sufix)
{
    outputSufix_ = sufix;
}

void FormatConvertor::currencyPair(CurrencyPair pair)
{
    currencyPair_ = pair;
}

void FormatConvertor::chartScale(ChartScale scale)
{
    chartScale_ = scale;
}

void FormatConvertor::splitPeriod(ChartScale period)
{
    splitPeriod_ = period;
}

bool FormatConvertor::convert(QString& error)
{
    if (inputFile_.isEmpty()) {
         error = QObject::tr("Input file was not set.");
         return false;
    }

    if (outputFolder_.isEmpty()) {
        error = QObject::tr("Output folder was not set.");
        return false;
    }

    if (static_cast<int>(chartScale_) >= static_cast<int>(splitPeriod_)) {
        error = QObject::tr("Split period must be bigger than chart scale.");
        return false;
    }

    if (!parseFormat(error))
        return false;

    if (!processFile(error))
        return false;

    return true;
}

bool FormatConvertor::parseLine(const QString& line, ChartCandle& candle)
{
    QStringList values = line.split(defaultSeparator);

    if (values.length() != valueType_.length())
        return false;

    for (int i = 0; i < values.length(); i++) {
        switch (valueType_[i]) {
            case ValueType::DateTime:{
                QDateTime dateTime;
                if (!parseDateTime(values[i], dateTimeFormat_ , dateTime))
                    return false;
                candle.dateTime(dateTime);
                break;
            }
            case ValueType::Time:{
                QTime time;
                if (!parseTime(values[i], timeFormat_, time))
                    return false;
                QDateTime dateTime = candle.dateTime();
                dateTime.setTime(time);
                candle.dateTime(dateTime);
                break;
            }
            case ValueType::Date:{
                QDate date;
                if (!parseDate(values[i], dateFormat_, date))
                    return false;
                QDateTime dateTime = candle.dateTime();
                dateTime.setDate(date);
                candle.dateTime(dateTime);
                break;
            }
            case ValueType::Open:{
                double open = 0;
                if (!parseDouble(values[i], open))
                    return false;
                candle.open(open);
                break;
            }
            case ValueType::High:{
                double high = 0;
                if (!parseDouble(values[i], high))
                    return false;
                candle.high(high);
                break;
            }
            case ValueType::Low:{
                double low = 0;
                if (!parseDouble(values[i], low))
                    return false;
                candle.low(low);
                break;
            }
            case ValueType::Close:{
                double close = 0;
                if (!parseDouble(values[i], close))
                    return false;
                candle.close(close);
                break;
            }
            case ValueType::Volume:{
                int volume = 0;
                if (!parseInt(values[i], volume))
                    return false;
                candle.volume(volume);
                break;
            }
        }
    }

    return true;
}

bool FormatConvertor::parseFormat(QString& error)
{
    if (inputFormat_.isEmpty()) {
        error = QObject::tr("Input data format was not set.");
        return false;
    }

    QString format = inputFormat_.trimmed().replace(" ", "");
    QStringList valueTypes = format.split(",");

    for (QString type : valueTypes) {
        if (type.startsWith("%e")) {
            if (type.length() <= 4) {
                error = QObject::tr("Invalid date time format.");
                return false;
            }
            dateTimeFormat_ = parseTimeFormat(type);
            valueType_.append(ValueType::DateTime);
        } else if (type.startsWith("%t")) {
            if (type.length() <= 4) {
                error = QObject::tr("Invalid time format.");
                return false;
            }
            timeFormat_ = parseTimeFormat(type);
            valueType_.append(ValueType::Time);
        } else if (type.startsWith("%d")) {
            if (type.length() <= 4) {
                error = QObject::tr("Invalid date format.");
                return false;
            }
            dateFormat_ = parseTimeFormat(type);
            valueType_.append(ValueType::Date);
        } else if (type.startsWith("%o")) {
            valueType_.append(ValueType::Open);
        } else if (type.startsWith("%h")) {
            valueType_.append(ValueType::High);
        } else if (type.startsWith("%l")) {
            valueType_.append(ValueType::Low);
        } else if (type.startsWith("%c")) {
            valueType_.append(ValueType::Close);
        } else if (type.startsWith("%v")) {
            valueType_.append(ValueType::Volume);
        }
    }

    return true;
}

ChartCandle FormatConvertor::parseCandle(QFile& inputFile, ChartCandle& lastCandle)
{
    QString line;
    ChartCandle first;
    ChartCandle candle;

    if (!lastCandle.isEmpty()) {
        first = lastCandle;
    } else {
        do line = inputFile.readLine().trimmed();
        while (!inputFile.atEnd() && !parseLine(line, first));
    }

    while (!inputFile.atEnd()) {
        line = inputFile.readLine().trimmed();

        candle = ChartCandle();
        if (!parseLine(line, candle))
            continue;

        if(calcPeriod(first.dateTime(), candle.dateTime(), chartScale_) != 0) {
            lastCandle = candle;
            break;
        }

        first.close(candle.close());
        if (candle.low() < first.low()) first.low(candle.low());
        if (candle.high() > first.high()) first.high(candle.high());
        first.volume(candle.volume() + first.volume());
    }

    return first;
}

bool FormatConvertor::processFile(QString& error)
{
    QFile inputFile(inputFile_);

    if (!inputFile.open(QFile::ReadOnly)) {
        error = QObject::tr("Cant open input file.");
        return false;
    }  

    ChartCandle lastCandle;
    ChartCandle first = parseCandle(inputFile, lastCandle);
    QDateTime firstDateTime = first.dateTime();

    auto outputFile = openOutputFile(outputFileName(
        outputFolder_, outputPrefix_, firstDateTime, outputSufix_));

    if (!outputFile) {
        error = QObject::tr("Cant open output file.");
        return false;
    }

    QTextStream outputStream(outputFile.get());
    writeFirstLine(outputStream, chartScale_, currencyPair_);
    outputStream << first.toString() << "\n";

    while (!inputFile.atEnd()) {
        ChartCandle candle = parseCandle(inputFile, lastCandle);

        if (calcPeriod(firstDateTime, candle.dateTime(), splitPeriod_) != 0) {
            firstDateTime = candle.dateTime();
            outputStream.setDevice(nullptr);

            outputFile = openOutputFile(outputFileName(
                outputFolder_, outputPrefix_, firstDateTime, outputSufix_));

            if (!outputFile) {
                error = QObject::tr("Cant open output file.");
                return false;
            }

            outputStream.setDevice(outputFile.get());
            writeFirstLine(outputStream, chartScale_, currencyPair_);
        }

        outputStream << candle.toString() << "\n";
    }

    return true;
}
