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

#include "memorymonitor.h"

#include <QtCharts/QAbstractAxis>
#include <QtCharts/QSplineSeries>
#include <QtCharts/QValueAxis>
#include <QtCore/QRandomGenerator>
#include <QLoggingCategory>

#include <QMainWindow>
#include <QWindow>
#include <QScreen>
#include <QDir>

#include <QTime>

extern "C" {
#include <unistd.h>
#include <sys/resource.h>
}
#include <cstdio>

namespace  {
const QString KChartFileName = "MemoryUsageChart";
const quint8 KYPosition = 15;
const quint8 KReadingMargin = 80;
const quint16 KToKilo = 1024;
}

Q_LOGGING_CATEGORY(lcMemoryMonitor, "qt.virtualkeyboard.tests.manual.x11vkbtest.memorymonitor")

MemoryMonitor::MemoryMonitor(QMainWindow &window, QGraphicsItem *parent, Qt::WindowFlags wFlags) :
    QChart(QChart::ChartTypeCartesian, parent, wFlags),
    m_series(nullptr),
    m_axisX(new QValueAxis()),
    m_axisY(new QValueAxis()),
    m_x(0),
    m_y(0),
    m_peakMemory(0),
    m_minMemory(0),
    m_procID(0),
    m_MainWindow(window),
    m_screen(nullptr),
    m_drawInterval(new QTimer()),
    m_elapsedTime(0),
    m_minMemReading(nullptr),
    m_curMemReading(nullptr),
    m_peakMemReading(nullptr),
    m_elapsedTimeReading(nullptr)
{
    m_series = new QSplineSeries(this);
    QPen red(Qt::red);
    red.setWidth(3);
    m_series->setPen(red);

    this->addSeries(m_series);

    this->addAxis(m_axisX,Qt::AlignBottom);
    m_axisX->setTitleText("Time");
    this->addAxis(m_axisY,Qt::AlignLeft);
    m_axisY->setTitleText("Memory KB");
    m_series->attachAxis(m_axisX);
    m_series->attachAxis(m_axisY);
    m_axisX->setTickCount(15);
    m_axisX->setLabelFormat("%.1u");
    m_axisY->setTickCount(15);
    m_axisY->setLabelFormat("%.1u");
    m_axisX->setRange(0, 500);
    m_axisY->setRange(0, 100 * KToKilo);
}

void MemoryMonitor::init()
{
    QWindow *win = m_MainWindow.windowHandle();
    m_screen = win->screen();

    m_minMemReading = new QGraphicsSimpleTextItem(this);
    m_minMemReading->setPen(QPen(QBrush("black"), 1));
    m_minMemReading->setText("Min mem ----- KB");
    m_minMemReading->setPos(KReadingMargin, KYPosition);

    m_curMemReading = new QGraphicsSimpleTextItem(this);
    m_curMemReading->setPen(QPen(QBrush("black"), 1));
    m_curMemReading->setText("Current mem ----- KB");
    m_curMemReading->setPos((this->size().width() / 2) - KReadingMargin, KYPosition);

    m_peakMemReading = new QGraphicsSimpleTextItem(this);
    m_peakMemReading->setPen(QPen(QBrush("black"), 1));
    m_peakMemReading->setText("Peak mem ----- KB");
    m_peakMemReading->setPos(this->size().width() - (KReadingMargin * 2), KYPosition);

    m_elapsedTimeReading = new QGraphicsSimpleTextItem(this);
    m_elapsedTimeReading->setPen(QPen(QBrush("black"), 1));
    m_elapsedTimeReading->setText("-- s");
    m_elapsedTimeReading->setPos((this->size().width() / 2) + KReadingMargin, this->size().height() - KYPosition * 3);

    QObject::connect(this, &QChart::geometryChanged, this, [this]() {
        m_minMemReading->setPos(KReadingMargin, KYPosition);
        m_curMemReading->setPos((this->size().width() / 2) - KReadingMargin, KYPosition);
        m_peakMemReading->setPos(this->size().width() - (KReadingMargin * 2), KYPosition);
        m_elapsedTimeReading->setPos((this->size().width() / 2) + KReadingMargin, this->size().height() - KYPosition * 3);
    });
}

void MemoryMonitor::setProcID(const qint64 procID)
{
    m_procID = procID;
}

void MemoryMonitor::start()
{
    QObject::connect(m_drawInterval, &QTimer::timeout, m_drawInterval, [&]() {
        ulong memUsed = currentMemUsage();
        this->drawMemUsage(memUsed);
        this->storeMemValues(memUsed);
    });
    m_drawInterval->setInterval(1000);
    m_drawInterval->start();
}

/**
 * @brief currentMemUsage
 * @param procID
 * @return current memory by procID in kB
 * Getting memory usage of a given process using
 * Linux proc filesystem.
 */
ulong MemoryMonitor::currentMemUsage()
{
    ulong memSize = 0UL;
    QString str = QString("/proc/" + QString::number(m_procID)  + "/statm");
    QFile procFile(str);

    if (procFile.open(QIODevice::ReadOnly)) {
        QTextStream procStream(&procFile);
        ulong mem = procStream.readLine().split(" ").at(1).toULong();
        memSize = mem * static_cast<ulong>(sysconf(_SC_PAGESIZE));
        procFile.close();
    }
    auto result = memSize / KToKilo;
    qCDebug(lcMemoryMonitor).noquote() << QTime::currentTime().toString() << "memory usage:" << result;

    emit memUsage(m_elapsedTime, result);

    return result;
}

MemoryMonitor::~MemoryMonitor()
{
    qCDebug(lcMemoryMonitor) << "Destroying MemoryChart";
}

void MemoryMonitor::saveChart()
{
    QPixmap pix = m_screen->grabWindow(m_MainWindow.winId());

    const QString format = "png";
    QString path = QDir::currentPath();
    path += "/" + KChartFileName + "." + format;
    pix.save(path);
}

void MemoryMonitor::stopChart()
{
    this->saveChart();
    m_drawInterval->stop();

    emit minAndPeakReport(QString("Min process memory: %1 KB\nPeak process memory: %2 KB")
                                .arg(QString::number(m_minMemory))
                                .arg(QString::number(m_peakMemory)));
}

void MemoryMonitor::storeMemValues(const ulong memValue)
{
    if ((m_minMemory == 0 || m_minMemory > memValue)) {
        m_minMemory = memValue;
    }
    if (m_peakMemory < memValue) {
        m_peakMemory = memValue;
    }
    m_minMemReading->setText(QString("Min mem %1 KB").arg(QString::number(m_minMemory)));
    m_curMemReading->setText(QString("Current mem %1 KB").arg(QString::number(memValue)));
    m_peakMemReading->setText(QString("Peak mem %1 KB").arg(QString::number(m_peakMemory)));
    m_elapsedTimeReading->setText(QString("%1 s").arg(m_elapsedTime += 1));
}

void MemoryMonitor::drawMemUsage(const ulong memUsed)
{
    m_x += 1;
    m_y = memUsed;
    if (m_y >= m_axisY->max()) {
        m_axisY->setRange(0, (m_axisY->max() * 2));
    }
    if (m_x >= m_axisX->max()) {
        m_axisX->setRange(0, (m_axisX->max() * 2));
    }
    m_series->append(m_x, m_y);
}
