/* gXAxis Implementation
 *
 * Copyright (c) 2011-2014 Mark Watkins <jedimark@users.sourceforge.net>
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License. See the file COPYING in the main directory of the Linux
 * distribution for more details. */

#include "Graphs/gXAxis.h"

#include <QDebug>

#include <math.h>

#include "Graphs/glcommon.h"
#include "Graphs/gGraph.h"
#include "Graphs/gGraphView.h"

// These divisors are used to round xaxis timestamps to reasonable increments
const quint64 divisors[] = {
    15552000000ULL, 7776000000ULL, 5184000000ULL, 2419200000ULL, 1814400000ULL, 1209600000L, 604800000L, 259200000L,
    172800000L, 86400000, 2880000, 14400000, 7200000, 3600000, 2700000,
    1800000, 1200000, 900000, 600000, 300000, 120000, 60000, 45000, 30000,
    20000, 15000, 10000, 5000, 2000, 1000, 500, 100, 50, 10, 1
};
const int divcnt = sizeof(divisors) / sizeof(quint64);

gXAxis::gXAxis(QColor col, bool fadeout)
    : Layer(NoChannel)
{
    m_line_color = col;
    m_text_color = col;
    m_major_color = Qt::darkGray;
    m_minor_color = Qt::lightGray;
    m_show_major_lines = false;
    m_show_minor_lines = false;
    m_show_minor_ticks = true;
    m_show_major_ticks = true;
    m_utcfix = false;
    m_fadeout = fadeout;

    tz_offset = timezoneOffset();
    tz_hours = tz_offset / 3600000.0;

    m_roundDays = false;
}
gXAxis::~gXAxis()
{
}
void gXAxis::paint(QPainter &painter, gGraph &w, const QRegion &region)
{
    int left = region.boundingRect().left();
    int top = region.boundingRect().top();
    int width = region.boundingRect().width();
    int height = region.boundingRect().height();

    QString months[] = {
        QObject::tr("Jan"), QObject::tr("Feb"), QObject::tr("Mar"), QObject::tr("Apr"), QObject::tr("May"), QObject::tr("Jun"),
        QObject::tr("Jul"), QObject::tr("Aug"), QObject::tr("Sep"), QObject::tr("Oct"), QObject::tr("Nov"), QObject::tr("Dec")
    };
    //static QString dow[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};


    QVector<QLine> ticks;


    QPainter painter2; // Only need this for pixmap caching

    // pixmap caching screws font size when printing

    bool usepixmap = w.graphView()->usePixmapCache(); // Whether or not to use pixmap caching

    if (!usepixmap || (usepixmap && w.invalidate_xAxisImage)) {
        // Redraw graph xaxis labels and ticks either to pixmap or directly to screen

        if (usepixmap) {
            // Initialize a new cache image
            m_image = QImage(width + 22, height + 4, QImage::Format_ARGB32_Premultiplied);
            m_image.fill(Qt::transparent);
            painter2.begin(&m_image);
            painter2.setPen(Qt::black);
            painter2.setFont(*defaultfont);
        }

        double px, py;

        int start_px = left;
        //int start_py=top;
        //int width=scrx-(w.GetLeftMargin()+w.GetRightMargin());
        //    float height=scry-(w.GetTopMargin()+w.GetBottomMargin());

        if (width < 40) {
            return;
        }

        qint64 minx;
        qint64 maxx;

        if (w.blockZoom()) {
            // Lock zoom to entire data range
            minx = w.rmin_x;
            maxx = w.rmax_x;
        } else {
            // Allow zoom
            minx = w.min_x;
            maxx = w.max_x;
        }

        int days = ceil(double(maxx-minx) / 86400000.0);

        if (m_roundDays && (days >= 1)) {
            minx = floor(double(minx)/86400000.0);
            minx *= 86400000L;

            maxx = minx + 86400000L * qint64(days);
        }


        // duration of graph display window in milliseconds.
        qint64 xx = maxx - minx;

        // shouldn't really be negative, but this is safer than an assert
        if (xx <= 0) {
            return;
        }

        //Most of this could be precalculated when min/max is set..
        QString fd, tmpstr;
        int divmax, dividx;
        int fitmode;

        // Have a quick look at the scale and prep the autoscaler a little faster
        if (xx >= 86400000L) {       // Day
            fd = "Mjj 00";
            dividx = 0;
            divmax = 10;
            fitmode = 0;
        } else if (xx > 600000) {  // Minutes
            fd = " j0:00";
            dividx = 10;
            divmax = 27;
            fitmode = 1;
        } else if (xx > 5000) {    // Seconds
            fd = " j0:00:00";
            dividx = 16;
            divmax = 29;
            fitmode = 2;
        } else {                   // Microseconds
            fd = "j0:00:00:000";
            dividx = 28;
            divmax = divcnt;
            fitmode = 3;
        }

        //if (divmax>divcnt) divmax=divcnt;

        int x, y;
        // grab the text extent of the dummy text fields above to know how much space is needed
        GetTextExtent(fd, x, y);

        // Not sure when this was a problem...
        Q_ASSERT(x > 0);
//        if (x <= 0) {
//            qWarning() << "gXAxis::Plot() x<=0 font size bug";
//            return;
//        }


        // Max number of ticks that will fit, with a bit of room for a buffer
        int max_ticks = width / (x + 15);

        int fit_ticks = 0;
        int div = -1;
        qint64 closest = 0, tmp, tmpft;

        // Scan through divisor list with the index range given above, to find which
        // gives the closest number of ticks to the maximum that will physically fit

        for (int i = dividx; i < divmax; i++) {
            tmpft = xx / divisors[i];
            tmp = max_ticks - tmpft;

            if (tmp < 0) { continue; }

            if (tmpft > closest) {   // Find the closest scale to the number
                closest = tmpft;     // that will fit
                div = i;
                fit_ticks = tmpft;
            }
        }

        if (fit_ticks == 0) {
            qDebug() << "gXAxis::Plot() Couldn't fit ticks.. Too short?" << minx << maxx << xx;
            return;
        }

        if ((div < 0) || (div > divcnt)) {
            qDebug() << "gXAxis::Plot() div out of bounds";
            return;
        }

        qint64 step = divisors[div];

        //Align left minimum to divisor by losing precision
        qint64 aligned_start = minx / step;
        aligned_start *= step;

        while (aligned_start < minx) {
            aligned_start += step;
        }

        painter.setPen(QColor(Qt::black));


        //int utcoff=m_utcfix ? tz_hours : 0;

        //utcoff=0;
        int num_minor_ticks;

        if (step >= 86400000) {
            qint64 i = step / 86400000L; // number of days

            if (i > 14) { i /= 2; }

            if (i < 0) { i = 1; }

            num_minor_ticks = i;
        } else { num_minor_ticks = 10; }

        float xmult = double(width) / double(xx);
        float step_pixels = double(step / float(num_minor_ticks)) * xmult;

        py = left + float(aligned_start - minx) * xmult;

        //py+=usepixmap ? 20 : left;

        int mintop = top + 4.0 * (float(y) / 10.0);
        int majtop = top + 6.0 * (float(y) / 10.0);
        int texttop = majtop + y; // 18*w.printScaleY();

        // Fill in the minor tick marks up to the first major alignment tick

        for (int i = 0; i < num_minor_ticks; i++) {
            py -= step_pixels;

            if (py < start_px) { continue; }

            if (usepixmap) {
                ticks.append(QLine(py - left + 20, 0, py - left + 20, mintop - top));
            } else {
                ticks.append(QLine(py, top+2, py, mintop+2));
            }
        }

        int ms, m, h, s, d;
        qint64 j;

        for (qint64 i = aligned_start; i < maxx; i += step) {
            px = (i - minx) * xmult;
            px += left;

            if (usepixmap) {
                ticks.append(QLine(px - left + 20, 0, px - left + 20, majtop - top));
            } else {
                ticks.append(QLine(px, top+2, px, majtop+2));
            }

            j = i;

            if (!m_utcfix) { j += tz_offset; }

            ms = j % 1000;
            m = (j / 60000L) % 60L;
            h = (j / 3600000L) % 24L;
            s = (j / 1000L) % 60L;
            //int d=(j/86400000) % 7;

            if (fitmode == 0) {
                d = (j / 1000);
                QDateTime dt = QDateTime::fromTime_t(d).toUTC();
                QDate date = dt.date();
                // SLOW SLOW SLOW!!! On Mac especially, this function is pathetically slow.
                //dt.toString("MMM dd");

                // Doing it this way instead because it's MUUUUUUCH faster
                tmpstr = QString("%1 %2").arg(months[date.month() - 1]).arg(date.day());
                //} else if (fitmode==0) {
                //            tmpstr=QString("%1 %2:%3").arg(dow[d]).arg(h,2,10,QChar('0')).arg(m,2,10,QChar('0'));
            } else if (fitmode == 1) { // minute
                tmpstr = QString("%1:%2").arg(h, 2, 10, QChar('0')).arg(m, 2, 10, QChar('0'));
            } else if (fitmode == 2) { // second
                tmpstr = QString("%1:%2:%3").arg(h, 2, 10, QChar('0')).arg(m, 2, 10, QChar('0')).arg(s, 2, 10,
                         QChar('0'));
            } else if (fitmode == 3) { // milli
                tmpstr = QString("%1:%2:%3:%4").arg(h, 2, 10, QChar('0')).arg(m, 2, 10, QChar('0')).arg(s, 2, 10,
                         QChar('0')).arg(ms, 3, 10, QChar('0'));
            }

            int tx = px - x / 2.0;

            if (m_utcfix) {
                tx += step_pixels / 2.0;
            }

            if ((tx + x) < (left + width)) {
                if (!usepixmap) { w.renderText(tmpstr, tx, texttop, 0, Qt::black, defaultfont); }
                else { painter2.drawText(tx - left + 20, texttop - top, tmpstr); }
            }

            py = px;

            for (int j = 1; j < num_minor_ticks; j++) {
                py += step_pixels;

                if (py >= left + width) { break; }

                if (usepixmap) {
                    ticks.append(QLine(py - left + 20, 0, py - left + 20, mintop - top));
                } else {
                    ticks.append(QLine(py, top+2, py, mintop+2));
                }
            }
        }

        if (usepixmap) {
            painter2.drawLines(ticks);
            painter2.end();
        } else {
            painter.drawLines(ticks);
        }
        w.graphView()->lines_drawn_this_frame += ticks.size();

        w.invalidate_xAxisImage = false;
    }

    if (usepixmap && !m_image.isNull()) {
        painter.drawImage(QPoint(left - 20, top + height - m_image.height() + 5), m_image);
    }
}