// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include <QtCharts/QCandlestickSet>
#include <QtGui/QPainter>
#include <private/abstractdomain_p.h>
#include <private/candlestick_p.h>
#include <private/qchart_p.h>

QT_BEGIN_NAMESPACE

Candlestick::Candlestick(QCandlestickSet *set, AbstractDomain *domain, QGraphicsObject *parent)
    : QGraphicsObject(parent),
      m_set(set),
      m_domain(domain),
      m_timePeriod(0.0),
      m_maximumColumnWidth(-1.0), // no maximum column width by default
      m_minimumColumnWidth(-1.0), // no minimum column width by default
      m_bodyWidth(0.5),
      m_bodyOutlineVisible(true),
      m_capsWidth(0.5),
      m_capsVisible(false),
      m_brush(QChartPrivate::defaultBrush()),
      m_pen(QChartPrivate::defaultPen()),
      m_hovering(false),
      m_mousePressed(false)
{
    setAcceptHoverEvents(true);
    setAcceptedMouseButtons(Qt::MouseButtonMask);
    setFlag(QGraphicsObject::ItemIsSelectable);
}

Candlestick::~Candlestick()
{
    // End hover event, if candlestick is deleted during it.
    if (m_hovering)
        emit hovered(false, m_set);
}

void Candlestick::setTimePeriod(qreal timePeriod)
{
    m_timePeriod = timePeriod;
}

void Candlestick::setMaximumColumnWidth(qreal maximumColumnWidth)
{
    m_maximumColumnWidth = maximumColumnWidth;
}

void Candlestick::setMinimumColumnWidth(qreal minimumColumnWidth)
{
    m_minimumColumnWidth = minimumColumnWidth;
}

void Candlestick::setBodyWidth(qreal bodyWidth)
{
    m_bodyWidth = bodyWidth;
}

void Candlestick::setBodyOutlineVisible(bool bodyOutlineVisible)
{
    m_bodyOutlineVisible = bodyOutlineVisible;
}

void Candlestick::setCapsWidth(qreal capsWidth)
{
    m_capsWidth = capsWidth;
}

void Candlestick::setCapsVisible(bool capsVisible)
{
    m_capsVisible = capsVisible;
}

void Candlestick::setIncreasingColor(const QColor &color)
{
    m_increasingColor = color;

    update();
}

void Candlestick::setDecreasingColor(const QColor &color)
{
    m_decreasingColor = color;

    update();
}

void Candlestick::setBrush(const QBrush &brush)
{
    m_brush = brush;

    update();
}

void Candlestick::setPen(const QPen &pen)
{
    qreal widthDiff = pen.widthF() - m_pen.widthF();
    m_boundingRect.adjust(-widthDiff, -widthDiff, widthDiff, widthDiff);

    m_pen = pen;

    update();
}

void Candlestick::setLayout(const CandlestickData &data)
{
    m_data = data;

    updateGeometry(m_domain);
    update();
}

void Candlestick::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    m_mousePressed = true;
    emit pressed(m_set);
    QGraphicsItem::mousePressEvent(event);
}

void Candlestick::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
    Q_UNUSED(event);

    m_hovering = true;
    emit hovered(m_hovering, m_set);
}

void Candlestick::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
    Q_UNUSED(event);

    m_hovering = false;
    emit hovered(m_hovering, m_set);
}

void Candlestick::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    emit released(m_set);
    if (m_mousePressed)
        emit clicked(m_set);
    m_mousePressed = false;
    QGraphicsItem::mouseReleaseEvent(event);
}

void Candlestick::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
    // For candlestick a pressed signal needs to be explicitly fired for mouseDoubleClickEvent.
    emit pressed(m_set);
    emit doubleClicked(m_set);
    QGraphicsItem::mouseDoubleClickEvent(event);
}

QRectF Candlestick::boundingRect() const
{
    return m_boundingRect;
}

void Candlestick::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option);
    Q_UNUSED(widget);

    bool increasingTrend = (m_data.m_open < m_data.m_close);
    QColor color = increasingTrend ? m_increasingColor : m_decreasingColor;

    QBrush brush(m_brush);
    brush.setColor(color);

    painter->save();
    painter->setBrush(brush);
    painter->setPen(m_pen);
    painter->setClipRect(m_boundingRect);
    if (m_capsVisible)
        painter->drawPath(m_capsPath);
    painter->drawPath(m_wicksPath);
    if (!m_bodyOutlineVisible)
        painter->setPen(QColor(Qt::transparent));
    painter->drawRect(m_bodyRect);
    painter->restore();
}

void Candlestick::updateGeometry(AbstractDomain *domain)
{
    m_domain = domain;

    prepareGeometryChange();

    m_capsPath = QPainterPath();
    m_wicksPath = QPainterPath();
    m_boundingRect = QRectF();

    if (!m_data.m_series->chart())
        return;

    QList<QAbstractAxis *> axes = m_data.m_series->chart()->axes(Qt::Horizontal, m_data.m_series);
    if (axes.isEmpty())
        return;

    QAbstractAxis *axisX = axes.value(0);
    if (!axisX)
        return;

    qreal columnWidth = 0.0;
    qreal columnCenter = 0.0;
    switch (axisX->type()) {
    case QAbstractAxis::AxisTypeBarCategory:
        columnWidth = 1.0 / m_data.m_seriesCount;
        columnCenter = m_data.m_index - 0.5
                       + m_data.m_seriesIndex * columnWidth
                       + columnWidth / 2.0;
        break;
    case QAbstractAxis::AxisTypeDateTime:
    case QAbstractAxis::AxisTypeValue:
        columnWidth = m_timePeriod;
        columnCenter = m_data.m_timestamp;
        break;
    default:
        qWarning() << "Unexpected axis type";
        return;
    }

    const qreal bodyWidth = m_bodyWidth * columnWidth;
    const qreal bodyLeft = columnCenter - (bodyWidth / 2.0);
    const qreal bodyRight = bodyLeft + bodyWidth;

    const qreal upperBody = qMax(m_data.m_open, m_data.m_close);
    const qreal lowerBody = qMin(m_data.m_open, m_data.m_close);
    const bool upperWickVisible = (m_data.m_high > upperBody);
    const bool lowerWickVisible = (m_data.m_low < lowerBody);

    QPointF geometryPoint;
    bool validData;

    // upper extreme
    geometryPoint = m_domain->calculateGeometryPoint(QPointF(bodyLeft, m_data.m_high), validData);
    if (!validData)
        return;
    const qreal geometryUpperExtreme = geometryPoint.y();
    // upper body
    geometryPoint = m_domain->calculateGeometryPoint(QPointF(bodyLeft, upperBody), validData);
    if (!validData)
        return;
    const qreal geometryBodyLeft = geometryPoint.x();
    const qreal geometryUpperBody = geometryPoint.y();
    // lower body
    geometryPoint = m_domain->calculateGeometryPoint(QPointF(bodyRight, lowerBody), validData);
    if (!validData)
        return;
    const qreal geometryBodyRight = geometryPoint.x();
    const qreal geometryLowerBody = geometryPoint.y();
    // lower extreme
    geometryPoint = m_domain->calculateGeometryPoint(QPointF(bodyRight, m_data.m_low), validData);
    if (!validData)
        return;
    const qreal geometryLowerExtreme = geometryPoint.y();

    // Real Body
    m_bodyRect.setCoords(geometryBodyLeft, geometryUpperBody, geometryBodyRight, geometryLowerBody);
    if (m_maximumColumnWidth != -1.0) {
        if (m_bodyRect.width() > m_maximumColumnWidth) {
            qreal extra = (m_bodyRect.width() - m_maximumColumnWidth) / 2.0;
            m_bodyRect.adjust(extra, 0.0, 0.0, 0.0);
            m_bodyRect.setWidth(m_maximumColumnWidth);
        }
    }
    if (m_minimumColumnWidth != -1.0) {
        if (m_bodyRect.width() < m_minimumColumnWidth) {
            qreal extra = (m_minimumColumnWidth - m_bodyRect.width()) / 2.0;
            m_bodyRect.adjust(-extra, 0.0, 0.0, 0.0);
            m_bodyRect.setWidth(m_minimumColumnWidth);
        }
    }

    const qreal geometryCapsExtra = (m_bodyRect.width() - (m_bodyRect.width() * m_capsWidth)) /2.0;
    const qreal geometryCapsLeft = m_bodyRect.left() + geometryCapsExtra;
    const qreal geometryCapsRight = m_bodyRect.right() - geometryCapsExtra;

    // Upper Wick and Cap
    if (upperWickVisible) {
        m_capsPath.moveTo(geometryCapsLeft, geometryUpperExtreme);
        m_capsPath.lineTo(geometryCapsRight, geometryUpperExtreme);
        m_wicksPath.moveTo((geometryCapsLeft + geometryCapsRight) / 2.0, geometryUpperExtreme);
        m_wicksPath.lineTo((geometryCapsLeft + geometryCapsRight) / 2.0, geometryUpperBody);
    }
    // Lower Wick and Cap
    if (lowerWickVisible) {
        m_capsPath.moveTo(geometryCapsLeft, geometryLowerExtreme);
        m_capsPath.lineTo(geometryCapsRight, geometryLowerExtreme);
        m_wicksPath.moveTo((geometryCapsLeft + geometryCapsRight) / 2.0, geometryLowerBody);
        m_wicksPath.lineTo((geometryCapsLeft + geometryCapsRight) / 2.0, geometryLowerExtreme);
    }
    m_wicksPath.closeSubpath();

    // bounding rectangle top
    qreal boundingRectTop;
    if (upperWickVisible)
        boundingRectTop = m_wicksPath.boundingRect().top();
    else
        boundingRectTop = m_bodyRect.top();
    boundingRectTop = qMax(boundingRectTop, parentItem()->boundingRect().top());
    // bounding rectangle right
    qreal boundingRectRight = qMin(m_bodyRect.right(), parentItem()->boundingRect().right());
    // bounding rectangle bottom
    qreal boundingRectBottom;
    if (lowerWickVisible)
        boundingRectBottom = m_wicksPath.boundingRect().bottom();
    else
        boundingRectBottom = m_bodyRect.bottom();
    boundingRectBottom = qMin(boundingRectBottom, parentItem()->boundingRect().bottom());
    // bounding rectangle left
    qreal boundingRectLeft = qMax(m_bodyRect.left(), parentItem()->boundingRect().left());

    m_boundingRect.setTop(boundingRectTop);
    m_boundingRect.setRight(boundingRectRight);
    m_boundingRect.setBottom(boundingRectBottom);
    m_boundingRect.setLeft(boundingRectLeft);

    qreal extra = m_pen.widthF();
    m_boundingRect.adjust(-extra, -extra, extra, extra);
}

QT_END_NAMESPACE

#include "moc_candlestick_p.cpp"
