#include "chart_scene.hh"
#include "../TradeCore/chart_candle.hh"
#include <QPaintEvent>
#include <QPainter>
#include <QGraphicsView>
#include <QGraphicsTextItem>
#include <QGraphicsSceneMouseEvent>
#include <QDebug>
#include <cassert>

namespace {

// Dark scheme
/*constexpr QRgb backgound = qRgb(15, 15, 15);
constexpr QRgb gridText = qRgb(140, 140, 140);
constexpr QRgb gridLine = qRgb(100, 100, 100);
constexpr QRgb bullishPen = qRgb(0, 150, 0);
constexpr QRgb bearishPen = qRgb(150, 0, 0);
constexpr QRgb stillPen = qRgb(80, 80, 255);
constexpr QRgb markerPen = qRgb(40, 40, 50);
constexpr QRgb bullishBrush = qRgb(0, 250, 0);
constexpr QRgb bearishBrush = qRgb(250, 0, 0);
constexpr QRgb markerBrush = qRgb(30, 30, 40);
constexpr QRgb priceLine = qRgb(255, 255, 0);
constexpr QRgb eventPricePen = qRgb(255, 255, 255);
constexpr QRgb stopLossPen = qRgb(255, 0, 0);
constexpr QRgb takeProfitPen = qRgb(0, 255, 0);
constexpr QRgb selectionBrush = qRgb(255, 255, 255);*/

// Light scheme
constexpr QRgb backgound = qRgb(255, 255, 255);
constexpr QRgb gridText = qRgb(50, 50, 50);
constexpr QRgb gridLine = qRgb(130, 130, 130);
constexpr QRgb bullishPen = qRgb(0, 150, 0);
constexpr QRgb bearishPen = qRgb(150, 0, 0);
constexpr QRgb stillPen = qRgb(80, 80, 255);
constexpr QRgb markerPen = qRgb(200, 200, 200);
constexpr QRgb markerBrush = qRgb(230, 230, 230);
constexpr QRgb bullishBrush = qRgb(0, 250, 0);
constexpr QRgb bearishBrush = qRgb(250, 0, 0);
constexpr QRgb priceLine = qRgb(255, 255, 0);
constexpr QRgb eventPricePen = qRgb(0, 0, 0);
constexpr QRgb stopLossPen = qRgb(200, 0, 0);
constexpr QRgb takeProfitPen = qRgb(0, 200, 0);
constexpr QRgb selectionBrush = qRgb(0, 0, 0);

constexpr int selectionBrushAlpha = 15;
constexpr int candleWidth = 10;
constexpr int candleSpacing = 2;
constexpr int pipSize = 10000;
constexpr int verticalBorder = 7;
constexpr int leftBorder = 70;
constexpr int rightBorder = 8;
constexpr int timeTextHeight = 13;
constexpr char timeFormat[] = "dd.MM.yyyy hh:mm";

int findIndex(const QList<ChartCandle>& data, const EventRecord& event)
{
    auto it = std::find_if(
        data.cbegin(), data.cend(), [&event](auto& candle){
            return candle.dateTime() == event.time;
        });

    return it != data.cend() ? std::distance(data.cbegin(), it) : -1;
}

} // namespace

ChartScene::ChartScene(QObject* parent) :
    QGraphicsScene(parent), minPrice_(0), maxPrice_(0),
    lineVisible_(false), zoom_(defaultZoomFactor), cursor_(nullptr)
{
    gridPen_.setColor(gridLine);
    gridPen_.setWidth(1);
    gridPen_.setStyle(Qt::DashLine);

    axisPen_.setColor(gridText);
    axisPen_.setWidth(1);

    bullishPen_.setColor(bullishPen);
    bullishPen_.setWidth(1);

    bullishBrush_ = QBrush(bullishBrush);

    bearishPen_.setColor(bearishPen);
    bearishPen_.setWidth(1);

    bearishBrush_ = QBrush(bearishBrush);

    stillPen_.setColor(stillPen);
    stillPen_.setWidth(1);

    markerPen_.setColor(markerPen);
    markerPen_.setWidth(1);

    markerBrush_ = QBrush(markerBrush);

    selectionPen_.setStyle(Qt::NoPen);

    QColor color = QColor(selectionBrush);
    color.setAlpha(selectionBrushAlpha);
    selectionBrush_ = QBrush(color);

    priceLinePen_.setColor(priceLine);
    priceLinePen_.setWidth(2);

    setBackgroundBrush(QBrush(backgound));

    eventPricePen_.setColor(eventPricePen);
    eventPricePen_.setWidth(2);

    stopLossPen_.setColor(stopLossPen);
    stopLossPen_.setWidth(2);

    takeProfitPen_.setColor(takeProfitPen);
    takeProfitPen_.setWidth(2);

    eventLinePen_.setColor(eventPricePen);
    eventLinePen_.setStyle(Qt::DashLine);
}

void ChartScene::priceData(const QList<ChartCandle>& data)
{
    if (data.empty())
        return;

    data_ = data;

    minPrice_ = std::min_element(data_.begin(), data_.end(),
        [](auto& a, auto& b){return a.low() < b.low();})->low();

    maxPrice_ = std::max_element(data_.begin(), data_.end(),
        [](auto& a, auto& b){return a.high() < b.high();})->high();
}

void ChartScene::orderEvents(
    const QList<EventRecord>& events, const QMap<int, QString>& eventTypes)
{
    events_ = events;
    eventTypes_ = eventTypes;
}

void ChartScene::description(const QString& text)
{
    description_ = text;
}

QList<ChartCandle> ChartScene::getSelection()
{
    QList<ChartCandle> selected;
    QMap<int, QGraphicsRectItem*>::iterator it;

    for (it = selection_.begin(); it != selection_.end(); ++it)
        selected.append(data_[it.key()]);

    return selected;
}

bool ChartScene::hasSelection() const
{
    return !selection_.isEmpty();
}

void ChartScene::lineVisible(bool visible)
{
    bool change = visible != lineVisible_;
    lineVisible_ = visible;
    if (change) drawScene();
}

bool ChartScene::lineVisible() const
{
    return lineVisible_;
}

void ChartScene::drawScene()
{
    clearScene();

    if (data_.isEmpty())
        return;

    calcScale();
    drawGrid();
    drawTimeAxis();
    drawData();

    if (lineVisible_)
        drawLine();

    drawEvents();
    drawDescription();

    QRectF rect = sceneRect();
    rect.setHeight(rect.height() + 2*verticalBorder + 1 + timeTextHeight);
    rect.setWidth(rect.width() + leftBorder + rightBorder);
    setSceneRect(rect);

    for (auto item : items())
        item->moveBy(leftBorder, verticalBorder);
}

void ChartScene::hideCursor(bool hidden)
{
    if (cursor_ == nullptr)
        return;

    cursor_->setVisible(!hidden);
}

void ChartScene::zoomFactor(int zoom)
{
    if (zoom_ + zoom <= 1)
        return;

    zoom_ = zoom;
    drawScene();
}

int ChartScene::zoomFactor() const
{
    return zoom_;
}

void ChartScene::clearScene()
{
    clear();
    cursor_ = nullptr;
    selection_.clear();
    setBackgroundBrush(QBrush(backgound));
}

void ChartScene::calcScale()
{
    int width = data_.length() * candleWidth;
    int height = pipSize * (maxPrice_ - minPrice_) * zoom_;
    scale_ = (double)height / (maxPrice_ - minPrice_);
    setSceneRect(0, 0, width, height);
    cursor_ = addRect(0, 0, candleWidth, height, markerPen_, markerBrush_);
}

void ChartScene::drawGrid()
{
    int width = sceneRect().width();
    int height = sceneRect().height();

    double price1 = minPrice_ + (maxPrice_ - minPrice_) / 3.0;
    double price2 = minPrice_ + (maxPrice_ - minPrice_) / 1.5;

    drawLevel(0, width, 0, QString::number(maxPrice_, 'f', 5), true);
    drawMidLevel(0, width, height/3, QString::number(price2, 'f', 5));
    drawMidLevel(0, width, height/1.5, QString::number(price1, 'f', 5));
    drawLevel(0, width, height, QString::number(minPrice_, 'f', 5), false);

    addLine(-1, 0, -1, height + timeTextHeight, axisPen_);
}

void ChartScene::drawTimeAxis()
{
    QFont bold;
    bold.setBold(true);

    int index = 0;
    QString timeString;

    for (auto& candle : data_) {
        if (index % 24 == 0)
           timeString = candle.dateTime().toString(timeFormat);

        if (index % 24 == 0 && index != 0) {
            int x = index * candleWidth;
            addLine(x, 0, x, sceneRect().height() + timeTextHeight, gridPen_);
        }

        if ((index - 12) % 24 == 0) {
            int x = (index - 12) * candleWidth;
            QGraphicsTextItem* textObj = addText(timeString);
            textObj->setDefaultTextColor(gridText);
            textObj->setPos(x - 2, sceneRect().height() - 2);
        }

        index++;
    }
}

void ChartScene::drawData()
{
    int height = sceneRect().height();
    int index = 0;

    for (auto& candle : data_) {
        int xPos = index * candleWidth;
        int min = (candle.low() - minPrice_) * scale_;
        int max = (candle.high() - minPrice_) * scale_;
        int open = (candle.open() - minPrice_) * scale_;
        int close = (candle.close() - minPrice_) * scale_;
        drawCandle(xPos, candleWidth, height-min, height-max, height-open, height-close);
        index++;
    }
}

void ChartScene::drawEvents()
{
    int height = sceneRect().height();
    QList<QPoint> points;

    for (auto it = events_.begin(); it != events_.end(); it++) {
        int index = findIndex(data_, *it);

        if (index == -1)
            index = std::distance(events_.begin(), it) * 3;

        int xPos = index * candleWidth;

        double eventPrice =
            (it->event == eventTypes_.key("Buy", -1) ||
             it->event == eventTypes_.key("Sell", -1))
            ? it->openPrice : it->closePrice;

        int price = height - (eventPrice - minPrice_) * scale_;
        int sl = height - (it->stopLoss - minPrice_) * scale_;
        int tp = height - (it->takeProfit - minPrice_) * scale_;

        points.append(QPoint(xPos, price));
        drawEvent(xPos+1, candleWidth-1, price, sl, tp);
    }

    for (int i = 0; i < points.length()-1; i++) {
        addLine(
            points[i].x() + candleWidth/2, points[i].y(),
            points[i+1].x() + candleWidth/2, points[i+1].y(),
            eventLinePen_);
    }
}

void ChartScene::drawDescription()
{
    if(description_.isEmpty())
        return;

    QGraphicsTextItem* textObj = addText(description_);
    textObj->setDefaultTextColor(gridText);
    textObj->setPos(0, 0);
}

void ChartScene::drawLine()
{
    int height = sceneRect().height();
    int index = 0;

    auto it = data_.cbegin();
    int xPosPrev = index * candleWidth + candleWidth / 2;
    int yPosPrev = height - ((*it).typical() - minPrice_) * scale_;

    for (; it != data_.cend(); ++it) {
        int xPos = index * candleWidth + candleWidth / 2;
        int yPos = height - ((*it).typical() - minPrice_) * scale_;
        addLine(xPosPrev, yPosPrev, xPos, yPos, QPen(priceLinePen_));
        xPosPrev = xPos;
        yPosPrev = yPos;
        index++;
    }
}

void ChartScene::drawLevel(int x1, int x2, int y, QString text, bool textUnder)
{
    addLine(x1 - leftBorder + rightBorder - 1, y, x2, y, axisPen_);

    QGraphicsTextItem* textObj = addText(text);
    textObj->setDefaultTextColor(gridText);

    if (textUnder)
        textObj->setPos(x1-textObj->boundingRect().width()-3, y-3);
    else
        textObj->setPos(x1-textObj->boundingRect().width()-3, y-17);
}

void ChartScene::drawMidLevel(int x1, int x2, int y, QString text)
{
    addLine(x1, y, x2, y, gridPen_);

    QGraphicsTextItem* textObj = addText(text);
    textObj->setDefaultTextColor(gridText);
    textObj->setPos(x1-textObj->boundingRect().width()-3, y-11);
}

void ChartScene::drawCandle(int x, int width, int low, int high, int open, int close)
{
    width -= candleSpacing * 2;
    x += candleSpacing;

    if (open < close) {
        addRect(x, open, width, close-open, bearishPen_, bearishBrush_);
        x += width / 2;
        addLine(x, open, x, high, bearishPen_);
        addLine(x, close, x, low, bearishPen_);
        return;
    }

    if (open > close) {
        addRect(x, close, width, open-close, bullishPen_, bullishBrush_);
        x += width / 2;
        addLine(x, open, x, low, bullishPen_);
        addLine(x, close, x, high, bullishPen_);
        return;
    }

    int x2 = x + width / 2;
    addLine(x2, close, x2, low, stillPen_);
    addLine(x2, open, x2, high, stillPen_);
    addLine(x, open, x+width, open, stillPen_);
}

void ChartScene::drawEvent(int x, int width, int price, int sl, int tp)
{
    addLine(x, price, x+width, price, eventPricePen_);
    addLine(x, sl, x+width, sl, stopLossPen_);
    addLine(x, tp, x+width, tp, takeProfitPen_);
}

void ChartScene::addToSelection(int index, int x)
{
    if (selection_.contains(index))
        return;

    int y2 = height() - 2 * verticalBorder - timeTextHeight;
    selection_.insert(index, addRect(
        x, verticalBorder, candleWidth, y2, selectionPen_, selectionBrush_));

    emit selectionChanged(selection_.count());
}

void ChartScene::removeFromSelection(int index)
{
    if (!selection_.contains(index))
        return;

    auto item = selection_.take(index);
    removeItem(item);
    delete item;
    item = nullptr;

    emit selectionChanged(selection_.count());
}

void ChartScene::mouseMoveEvent(QGraphicsSceneMouseEvent* mouseEvent)
{
    if (!cursor_ || data_.length() == 0)
        return;

    auto pos = mouseEvent->scenePos();
    int x = (int)pos.x() - (int)pos.x() % candleWidth;
    int index = (x - leftBorder) / candleWidth;

    if (index < 0 || index >= data_.length())
        return;

    cursor_->setX(x);
    emit candleChanged(data_[index]);

    if(mouseEvent->buttons().testFlag(Qt::RightButton)) {
        if (mouseEvent->modifiers().testFlag(Qt::ShiftModifier))
            removeFromSelection(index);
        else
            addToSelection(index, x);
    }

}
