/* Reports/Printing Module
 *
 * Copyright (c) 2019-2024 The OSCAR Team
 * Copyright (c) 2011-2018 Mark Watkins 
 *
 * 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 source code
 * for more details. */

#define TEST_MACROS_ENABLEDoff
#include <test_macros.h>

#include <QMessageBox>
#include <QtPrintSupport/qprinter.h>
#include <QtPrintSupport/qprintdialog.h>
#include <QTextDocument>
#include <QApplication>
#include <cmath>
#include <QPageLayout>

#include "reports.h"
#include "mainwindow.h"
#include "common_gui.h"
#include "SleepLib/progressdialog.h"
#include "version.h"

extern MainWindow *mainwin;


Report::Report()
{
}

void Report::PrintReport(gGraphView *gv, QString name, QDate date)
{
    if (!gv) { return; }


    Session *journal = nullptr;
    //QDate d=QDate::currentDate();

    int visgraphs = gv->visibleGraphs();

    if (visgraphs == 0) {
        mainwin->Notify(QObject::tr("There are no graphs visible to print"));
        return;
    }

    bool print_bookmarks = false;

    if (name == STR_TR_Daily) {
        QVariantList book_start;
        journal = mainwin->getDaily()->GetJournalSession(mainwin->getDaily()->getDate());

        if (journal && journal->settings.contains(Bookmark_Start)) {
            book_start = journal->settings[Bookmark_Start].toList();

            if (book_start.size() > 0) {
                if (QMessageBox::question(mainwin, STR_TR_Bookmarks,
                                          QObject::tr("Would you like to show bookmarked areas in this report?"),
                                          QMessageBox::Yes,
                                          QMessageBox::No) == QMessageBox::Yes) {
                    print_bookmarks = true;
                }
            }
        }
    }

    QPrinter *printer;

    bool aa_setting = AppSetting->antiAliasing();

    bool force_antialiasing = aa_setting;

    printer = new QPrinter(QPrinter::HighResolution);

#ifdef Q_OS_LINUX
    QString username = p_profile->Get(QString("_{") + QString(STR_UI_UserName) + "}_");
    printer->setPrinterName("Print to File (PDF)");
    printer->setOutputFormat(QPrinter::PdfFormat);
    QString filename = p_pref->Get("{home}/") + name + username + date.toString(Qt::ISODate) + ".pdf";

    printer->setOutputFileName(filename);
#endif
    printer->setPrintRange(QPrinter::AllPages);
    printer->setPageOrientation(QPageLayout::Portrait);
    printer->setFullPage(false); // This has nothing to do with scaling
    printer->setCopyCount(1);
    printer->setPageMargins(QMarginsF(10, 10, 10, 10), QPageLayout::Millimeter);
    QPrintDialog dialog(printer);
#ifdef Q_OS_MAC
    // QTBUG-17913
    QApplication::processEvents();
#endif

    if (dialog.exec() != QDialog::Accepted) {
        delete printer;
        return;
    }

    QPainter painter(printer);

    ProgressDialog progress(mainwin);
    progress.setMessage(QObject::tr("Printing %1 Report").arg(name));
    QPixmap icon = QPixmap(":/icons/logo-md.png").scaled(64,64);
    progress.setPixmap(icon);
    progress.open();


    GLint gw;
    gw = 2048; // Rough guess.. No GL_MAX_RENDERBUFFER_SIZE in mingw.. :(
    //QSizeF pxres=printer->paperSize(QPrinter::DevicePixel);
    QRect prect = printer->pageLayout().paintRectPixels( printer->resolution() ) ;
    float ratio = float(prect.height()) / float(prect.width());
    float virt_width = gw;
    float virt_height = virt_width * ratio;
    painter.setWindow(0, 0, virt_width, virt_height);
    painter.setViewport(0, 0, prect.width(), prect.height());
    painter.setViewTransformEnabled(true);

    QFont report_font = *defaultfont;
    QFont medium_font = *mediumfont;
    QFont title_font = *bigfont;
    float normal_height = 30; //fm2.ascent();
    report_font.setPixelSize(normal_height);
    medium_font.setPixelSize(40);
    title_font.setPixelSize(90);
    painter.setFont(report_font);

    //QFontMetrics fm2(*defaultfont);

    qDebug() << "Printer Resolution is" << virt_width << "x" << virt_height;

    const int graphs_per_page = 6;
    float full_graph_height = (virt_height - (normal_height * graphs_per_page)) / float(graphs_per_page);

    QString title = QObject::tr("%1 Report").arg(name);
    painter.setFont(title_font);
    int top = 0;
    QRectF bounds = painter.boundingRect(QRectF(0, top, virt_width, 0), title,
                                         QTextOption(Qt::AlignHCenter | Qt::AlignTop));
    painter.drawText(bounds, title, QTextOption(Qt::AlignHCenter | Qt::AlignTop));
    top += bounds.height() + 90 / 2.0;
    painter.setFont(report_font);

    int maxy = 0;

    if (AppSetting->showPersonalData() && !p_profile->user->firstName().isEmpty()) {
        QString userinfo = STR_TR_Name + QString(":\t %1, %2\n").
                arg(p_profile->user->lastName()).
                arg(p_profile->user->firstName());

        userinfo += STR_TR_DOB + QString(":\t%1\n").
                arg(p_profile->user->DOB().toString(Qt::SystemLocaleShortDate));

        if (!p_profile->doctor->patientID().isEmpty()) {
            userinfo += STR_TR_PatientID + QString(":\t%1\n").arg(p_profile->doctor->patientID());
        }

        userinfo += STR_TR_Phone + QString(":\t%1\n").arg(p_profile->user->phone());
        userinfo += STR_TR_Email + QString(":\t%1\n").arg(p_profile->user->email());

        if (!p_profile->user->address().isEmpty()) {
            userinfo += "\n" + STR_TR_Address + QString(":\n%1").arg(p_profile->user->address());
        }

        QRectF bounds = painter.boundingRect(QRectF(0, top, virt_width, 0), userinfo,
                                             QTextOption(Qt::AlignLeft | Qt::AlignTop));
        painter.drawText(bounds, userinfo, QTextOption(Qt::AlignLeft | Qt::AlignTop));

        if (bounds.height() > maxy) {
            maxy = bounds.height();
        }
    }

    Machine *cpap = nullptr, *oxi = nullptr;

    int graph_slots = 0;
    Day * day = p_profile->GetGoodDay(mainwin->getDaily()->getDate(), MT_CPAP);

    if (day) cpap = day->machine(MT_CPAP);

    if (name == STR_TR_Daily) {

        QString cpapinfo = date.toString(Qt::SystemLocaleLongDate) + "\n\n";

        if (cpap) {
            time_t f = day->first(MT_CPAP) / 1000L;
            time_t l = day->last(MT_CPAP) / 1000L;
            int tt = qint64(day->total_time(MT_CPAP)) / 1000L;
            int h = tt / 3600;
            int m = (tt / 60) % 60;
            int s = tt % 60;

            cpapinfo += STR_TR_MaskTime + QObject::tr(": %1 hours, %2 minutes, %3 seconds\n").arg(h).arg(m).arg(s);
            cpapinfo += STR_TR_BedTime + ": " + QDateTime::fromTime_t(f).time().toString("HH:mm:ss") + " ";
            cpapinfo += STR_TR_WakeUp + ": " + QDateTime::fromTime_t(l).time().toString("HH:mm:ss") + "\n\n";
            cpapinfo += STR_TR_Machine + ": ";


            cpapinfo += cpap->brand() + " " + cpap->model();
            QString mn = cpap->modelnumber();
            if (!mn.isEmpty()) {
                cpapinfo += " (" + mn + ")";
            }
            cpapinfo += "\n";

            cpapinfo += STR_TR_Mode + ": " + day->getCPAPModeStr() + "\n";
            cpapinfo += day->getPressureSettings() + "\n";
            QString pressurerelief = day->getPressureRelief();
            if (pressurerelief.compare(STR_TR_None)) {
                cpapinfo += /*QObject::tr("Pressure Relief")+": "+ */day->getPressureRelief() + "\n";
            }

            float ahi = day->count(AllAhiChannels);

            if (p_profile->general->calculateRDI()) { ahi += day->count(CPAP_RERA); }

            float hours = day->hours(MT_CPAP);
            ahi /= hours;
            float csr = (100.0 / hours) * (day->sum(CPAP_CSR) / 3600.0);
            //float pb = (100.0 / hours) * (day->sum(CPAP_PB) / 3600.0);
            float uai = day->count(CPAP_Apnea) / hours;
            float oai = day->count(CPAP_Obstructive) / hours;
            float ai = day->count(CPAP_AllApnea) / hours;
            float hi = (day->count(CPAP_ExP) + day->count(CPAP_Hypopnea)) / hours;
            float cai = day->count(CPAP_ClearAirway) / hours;
            float rei = day->count(CPAP_RERA) / hours;
            float vsi = day->count(CPAP_VSnore) / hours;
            if (day->channelHasData(CPAP_VSnore2)) {  // PRS1 puts its 2-minute VS count in a different channel rather than reporting each incident.
                vsi = day->sum(CPAP_VSnore2) / hours;
            }
            float fli = day->count(CPAP_FlowLimit) / hours;
//            float sai = day->count(CPAP_SensAwake) / hours;
            float nri = day->count(CPAP_NRI) / hours;
            float lki = day->count(CPAP_LeakFlag) / hours;
            float exp = day->count(CPAP_ExP) / hours;

            int piesize = (2048.0 / 8.0) * 1.3; // 1.5" in size
            //float fscale=font_scale;
            //if (!highres)
            //                fscale=1;

            QString stats;
            painter.setFont(medium_font);

            if (p_profile->general->calculateRDI()) {
                stats = QObject::tr("RDI\t%1\n").arg(ahi, 0, 'f', 2);
            } else {
                stats = QObject::tr("AHI\t%1\n").arg(ahi, 0, 'f', 2);
            }

            QRectF bounds = painter.boundingRect(QRectF(0, 0, virt_width, 0), stats, QTextOption(Qt::AlignRight));
            painter.drawText(bounds, stats, QTextOption(Qt::AlignRight));


            mainwin->getDaily()->eventBreakdownPie()->setShowTitle(false);
            mainwin->getDaily()->eventBreakdownPie()->setMargins(0, 0, 0, 0);
            QPixmap ebp;

            if (ahi > 0) {
                ebp = mainwin->getDaily()->eventBreakdownPie()->renderPixmap(piesize, piesize, true);
            } else {
                ebp = QPixmap(":/icons/smileyface.png");
            }

            if (!ebp.isNull()) {
                painter.drawPixmap(virt_width - piesize, bounds.height(), piesize, piesize, ebp);
            }

            mainwin->getDaily()->eventBreakdownPie()->setShowTitle(true);

            cpapinfo += "\n\n";

            painter.setFont(report_font);
            //bounds=painter.boundingRect(QRectF((virt_width/2)-(virt_width/6),top,virt_width/2,0),cpapinfo,QTextOption(Qt::AlignLeft));
            bounds = painter.boundingRect(QRectF(0, top, virt_width, 0), cpapinfo,
                                          QTextOption(Qt::AlignHCenter));
            painter.drawText(bounds, cpapinfo, QTextOption(Qt::AlignHCenter));

            int ttop = bounds.height();

            stats = QObject::tr("AI=%1 HI=%2 CAI=%3 ").arg(oai, 0, 'f', 2).arg(hi, 0, 'f', 2).arg(cai, 0, 'f',
                    2);

            if (cpap->loaderName() == STR_MACH_PRS1) {

                // FIXME: PB / CSR are now separate flags
                stats += QObject::tr("REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%%")
                         .arg(rei, 0, 'f', 2).arg(vsi, 0, 'f', 2)
                         .arg(fli, 0, 'f', 2).arg(csr, 0, 'f', 2);
            } else if (cpap->loaderName() == STR_MACH_ResMed) {
                stats += QObject::tr("UAI=%1 ").arg(uai, 0, 'f', 2);
            } else if (cpap->loaderName() == STR_MACH_Intellipap) {
                stats += QObject::tr("NRI=%1 LKI=%2 EPI=%3")
                        .arg(nri, 0, 'f', 2).arg(lki, 0, 'f', 2).arg(exp, 0,'f', 2);
            } else if (cpap->loaderName() == STR_MACH_SleepStyle) {
                stats += QObject::tr("AI=%1 ").arg(ai, 0, 'f', 2);
            }

            bounds = painter.boundingRect(QRectF(0, top + ttop, virt_width, 0), stats,
                                          QTextOption(Qt::AlignHCenter));
            painter.drawText(bounds, stats, QTextOption(Qt::AlignHCenter));
            ttop += bounds.height();

            if (journal) {
                stats = "";

                if (journal->settings.contains(Journal_Weight)) {
                    stats += STR_TR_Weight + QString(" %1 ").
                            arg(weightString(journal->settings[Journal_Weight].toDouble()));
                }

                if (journal->settings.contains(Journal_BMI)) {
                    stats += STR_TR_BMI + QString(" %1 ").
                            arg(journal->settings[Journal_BMI].toDouble(), 0, 'f', 2);
                }

                if (journal->settings.contains(Journal_ZombieMeter)) {
                    stats += STR_TR_Zombie + QString(" %1/10 ").
                            arg(journal->settings[Journal_ZombieMeter].toDouble(), 0, 'f', 0);
                }

                if (!stats.isEmpty()) {
                    bounds = painter.boundingRect(QRectF(0, top + ttop, virt_width, 0), stats,
                                                  QTextOption(Qt::AlignHCenter));

                    painter.drawText(bounds, stats, QTextOption(Qt::AlignHCenter));
                    ttop += bounds.height();
                }

                ttop += normal_height;

                if (journal->settings.contains(Journal_Notes)) {
                    QTextDocument doc;
                    doc.setHtml(journal->settings[Journal_Notes].toString());
                    stats = doc.toPlainText();
                    //doc.drawContents(&painter); // doesn't work as intended..

                    bounds = painter.boundingRect(QRectF(0, top + ttop, virt_width, 0), stats,
                                                  QTextOption(Qt::AlignHCenter));
                    painter.drawText(bounds, stats, QTextOption(Qt::AlignHCenter));
                    bounds.setLeft(virt_width / 4);
                    bounds.setRight(virt_width - (virt_width / 4));

                    QPen pen(Qt::black);
                    pen.setWidth(4);
                    painter.setPen(pen);
                    painter.drawRect(bounds);
                    ttop += bounds.height() + normal_height;
                }
            }


            if (ttop > maxy) { maxy = ttop; }
        } else {
            bounds = painter.boundingRect(QRectF(0, top + maxy, virt_width, 0), cpapinfo,
                                          QTextOption(Qt::AlignCenter));
            painter.drawText(bounds, cpapinfo, QTextOption(Qt::AlignCenter));

            if (maxy + bounds.height() > maxy) { maxy = maxy + bounds.height(); }
        }
    } else if (name == STR_TR_Overview) {
        QDateTime first = QDateTime::fromTime_t((*gv)[0]->min_x / 1000L);
        QDateTime last = QDateTime::fromTime_t((*gv)[0]->max_x / 1000L);
        QString ovinfo = QObject::tr("Reporting from %1 to %2").
                arg(first.date().toString(Qt::SystemLocaleShortDate)).
                arg(last.date().toString(Qt::SystemLocaleShortDate));
        QRectF bounds = painter.boundingRect(QRectF(0, top, virt_width, 0), ovinfo,
                                             QTextOption(Qt::AlignHCenter));
        painter.drawText(bounds, ovinfo, QTextOption(Qt::AlignHCenter));

        if (bounds.height() > maxy) { maxy = bounds.height(); }
    } /*else if (name == STR_TR_Oximetry) {
        QString ovinfo = QObject::tr("Reporting data goes here");
        QRectF bounds = painter.boundingRect(QRectF(0, top, virt_width, 0), ovinfo,
                                             QTextOption(Qt::AlignHCenter));
        painter.drawText(bounds, ovinfo, QTextOption(Qt::AlignHCenter));

        if (bounds.height() > maxy) { maxy = bounds.height(); }
    }*/

    top += maxy;

    graph_slots = graphs_per_page - ((virt_height - top) / (full_graph_height + normal_height));

    bool first = true;
    QStringList labels;
    QVector<gGraph *> graphs;
    QVector<qint64> start, end;
    qint64 savest, saveet;

    gGraph *g;

    gv->GetXBounds(savest, saveet);

    for (int i=0;i < gv->size(); i++) {
        g = (*gv)[i];

        if (g->isEmpty() || !g->visible()) continue;
        if (g->group() == 0) {
            savest = g->min_x;
            saveet = g->max_x;
            break;
        }
    }
    qint64 st = savest, et = saveet;

    bool lineCursorMode = AppSetting->lineCursorMode();
    AppSetting->setLineCursorMode(false);

    if (name == STR_TR_Daily) {
        if (!print_bookmarks) {
            for (int i = 0; i < gv->size(); i++) {
                g = (*gv)[i];

                if (g->isEmpty()) { continue; }

                if (!g->visible()) { continue; }

                QString label = "";
                if (!g->isSnapshot() && (g->name() == schema::channel[CPAP_FlowRate].code())) {
                    st = day->first();
                    et = day->last();
                    // If the current selection is more than a minute smaller than the whole night:
                    if ((et - st) - (g->max_x - g->min_x) > 60000) {
                        // First draw the flow waveform in the context of the entire day (matching the flags chart)
                        start.push_back(st);
                        end.push_back(et);
                        graphs.push_back(g);
                        labels.push_back(QObject::tr("Entire Day's Flow Waveform"));
                        // And add a label to the selected flow graph below
                        label = QObject::tr("Current Selection");
                    }
                }

                start.push_back(g->min_x);
                end.push_back(g->max_x);
                graphs.push_back(g);
                labels.push_back(label);
            }
        } else {
            const QString EntireDay = QObject::tr("Entire Day");

            if (journal) {
                if (journal->settings.contains(Bookmark_Start)) {
                    QVariantList st1 = journal->settings[Bookmark_Start].toList();
                    QVariantList et1 = journal->settings[Bookmark_End].toList();
                    QStringList notes = journal->settings[Bookmark_Notes].toStringList();
                    gGraph *flow = (*gv)[schema::channel[CPAP_FlowRate].code()],
                            *spo2 = (*gv)[schema::channel[OXI_SPO2].code()],
                             *pulse = (*gv)[schema::channel[OXI_Pulse].code()];

                    if (cpap && flow && !flow->isEmpty() && flow->visible()) {
                        labels.push_back(EntireDay);
                        start.push_back(day->first(MT_CPAP));
                        end.push_back(day->last(MT_CPAP));
                        graphs.push_back(flow);
                    }

                    if (oxi && spo2 && !spo2->isEmpty() && spo2->visible()) {
                        labels.push_back(EntireDay);
                        start.push_back(day->first(MT_OXIMETER));
                        end.push_back(day->last(MT_OXIMETER));
                        graphs.push_back(spo2);
                    }

                    if (oxi && pulse && !pulse->isEmpty() && pulse->visible()) {
                        labels.push_back(EntireDay);
                        start.push_back(day->first(MT_OXIMETER));
                        end.push_back(day->last(MT_OXIMETER));
                        graphs.push_back(pulse);
                    }

                    for (int i = 0; i < notes.size(); i++) {
                        if (flow && !flow->isEmpty() && flow->visible()) {
                            labels.push_back(notes.at(i));
                            start.push_back(st1.at(i).toLongLong());
                            end.push_back(et1.at(i).toLongLong());
                            graphs.push_back(flow);
                        }

                        if (spo2 && !spo2->isEmpty() && spo2->visible()) {
                            labels.push_back(notes.at(i));
                            start.push_back(st1.at(i).toLongLong());
                            end.push_back(et1.at(i).toLongLong());
                            graphs.push_back(spo2);
                        }

                        if (pulse && !pulse->isEmpty() && pulse->visible()) {
                            labels.push_back(notes.at(i));
                            start.push_back(st1.at(i).toLongLong());
                            end.push_back(et1.at(i).toLongLong());
                            graphs.push_back(pulse);
                        }
                    }
                }
            }

            for (int i = 0; i < gv->size(); i++) {
                gGraph *g = (*gv)[i];

                if (g->isEmpty()) { continue; }

                if (!g->visible()) { continue; }

                if ((g->name() != schema::channel[CPAP_FlowRate].code()) && (g->name() != schema::channel[OXI_SPO2].code())
                        && (g->name() != schema::channel[OXI_Pulse].code())) {
                    start.push_back(st);
                    end.push_back(et);
                    graphs.push_back(g);
                    labels.push_back("");
                }
            }
        }
    } else {
        for (int i = 0; i < gv->size(); i++) {
            gGraph *g = (*gv)[i];

            if (g->isEmpty()) { continue; }

            if (!g->visible()) { continue; }

            start.push_back(st);
            end.push_back(et);
            graphs.push_back(g);
            labels.push_back(""); // date range?
        }
    }

    int pages = ceil(float(graphs.size() + graph_slots) / float(graphs_per_page));

    progress.setProgressMax(graphs.size());

    int page = 1;
    int gcnt = 0;

    for (int i = 0; i < graphs.size(); i++) {

        if ((top + full_graph_height + normal_height) > virt_height) {
            top = 0;
            gcnt = 0;
            first = true;

            if (page > pages) {
                break;
            }

            if (!printer->newPage()) {
                qWarning("failed in flushing page to disk, disk full?");
                break;
            }


        }

        if (first) {
            QDateTime timestamp = QDateTime::currentDateTime();
            QString footer = QString("%1 %2 %3").arg(timestamp.toString(MedDateFormat+" hh:mm"))
                                                           .arg(STR_TR_OSCAR)
                                                           .arg(getVersion());


            QRectF bounds = painter.boundingRect(QRectF(0, virt_height, virt_width, normal_height), footer,
                                                 QTextOption(Qt::AlignHCenter));
            painter.drawText(bounds, footer, QTextOption(Qt::AlignHCenter));

            QString pagestr = QObject::tr("Page %1 of %2").arg(page).arg(pages);
            QRectF pagebnds = painter.boundingRect(QRectF(0, virt_height, virt_width, normal_height), pagestr,
                                                   QTextOption(Qt::AlignRight));
            painter.drawText(pagebnds, pagestr, QTextOption(Qt::AlignRight));
            first = false;
            page++;
        }

        gGraph *g = graphs[i];
        if (!g->isSnapshot()) {
            g->SetXBounds(start[i], end[i]);
        }
        g->deselect();

        QString label = labels[i];

        if (!label.isEmpty()) {
            //label+=":";
            top += normal_height / 3;
            QRectF bounds = painter.boundingRect(QRectF(0, top, virt_width, 0), label,
                                                 QTextOption(Qt::AlignHCenter));
            //QRectF pagebnds=QRectF(0,top,virt_width,normal_height);
            painter.drawText(bounds, label, QTextOption(Qt::AlignHCenter));
            top += bounds.height();
        } else { top += normal_height / 2; }

        AppSetting->setAntiAliasing(force_antialiasing);
        int tmb = g->m_marginbottom;
        g->m_marginbottom = 0;


        //painter.beginNativePainting();
        //g->showTitle(false);
        int hhh = full_graph_height - normal_height;
        QPixmap pm2 = g->renderPixmap(virt_width, hhh, 1);
        QImage pm = pm2.toImage(); //fscale);
        pm2.detach();
        //g->showTitle(true);
        //painter.endNativePainting();
        g->m_marginbottom = tmb;
        AppSetting->setAntiAliasing(aa_setting);


        if (!pm.isNull()) {
            painter.drawImage(QRect(0, top, pm.width(), pm.height()), pm);

            //painter.drawImage(0,top,virt_width,full_graph_height-normal_height,pm);
        }

        top += full_graph_height;

        gcnt++;

        progress.setProgressValue(i);
        QApplication::processEvents();
    }

    gv->SetXBounds(savest, saveet);
    painter.end();
    progress.close();
    delete printer;
    AppSetting->setLineCursorMode(lineCursorMode);
}