/* SleepLib Common Functions
 *
 * Copyright (c) 2019-2025 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 <QDateTime>
#include <QTimeZone>
#include <QDir>
#include <QThread>
#ifndef BROKEN_OPENGL_BUILD
    #if QT_VERSION < QT_VERSION_CHECK(5,4,0)
        #include <QGLWidget>
    #elif  QT_VERSION < QT_VERSION_CHECK(6,0,0)
        #include <QOpenGLWidget>
    #else
        #include <QtOpenGLWidgets/QOpenGLWidget>
        #include <QOpenGLFunctions>
        #include <QSurfaceFormat>
        #include <QOffscreenSurface>
    #endif
#endif //BROKEN_OPENGL_BUILD
#include <QOpenGLFunctions>
#include <QDebug>
#include <QDir>
#include <QCoreApplication>
#include <QApplication>
#include <QSettings>
#include <QFontDatabase>
#include <QStandardPaths>
#include <QMenuBar>

#include "SleepLib/common.h"

#ifdef _MSC_VER
#include <QtZlib/zlib.h>
#else
#include <unistd.h>
#include <zlib.h>
#endif

#include "version.h"
#include "profiles.h"
#include "mainwindow.h"

extern MainWindow * mainwin;

QString MedDateFormat = "ddd MMM d yyyy";  // QT default value, which we override if we can
bool dayFirst = false;

// System locale and regional settings support only a "short" date (m/d/yyy) and a "long"
// date (day of week, month, day, year -- all spelled out fully).  We get the formatting
// for the long format, shorten day and month name, and remove excess commas.
void SetDateFormat () {
    QLocale sysLocale = QLocale::system();
    QString dfmt = sysLocale.dateFormat();
    qDebug() << "system locale date format" << dfmt;

    QString s = dfmt.replace("dddd", "ddd");
    if (!s.isEmpty()) s = s.replace("MMMM", "MMM");
    if (!s.isEmpty()) s = s.replace(",", "");
    if (!s.isEmpty()) {
        QDate testDate (2018, 12, 31);
        QString testresult = testDate.toString(s);
        if (!testresult.isEmpty())  // make sure we can translate a date
            MedDateFormat = s;     // If we can, save the format for future use
    }

    // Record whether month or day is first in the formatting
    QString s2 = MedDateFormat;
    s2 = s2.replace("ddd","");
    int monthidx = s2.indexOf("MMM");
    if (s2.indexOf("d") < monthidx)
        dayFirst = true;

    qDebug() << "shortened date format" << MedDateFormat << "dayFirst" << dayFirst;
}

const QString getDeveloperName()
{
    return STR_DeveloperName;
}

const QString getDeveloperDomain()
{
    return STR_DeveloperDomain;
}

const QString getAppName()
{
    QString name = STR_AppName;
    name += getPrereleaseSuffix();
    return name;
}

const QString getModifiedAppData()
{
    QString appdata = STR_AppData;
    appdata += getPrereleaseSuffix();
    return appdata;
}

bool gfxEgnineIsSupported(GFXEngine e)
{
#if defined(Q_OS_WIN32)
    Q_UNUSED(e)
    return true;
#else
    switch(e) {
    case GFX_OpenGL:
    case GFX_Software:
       return true;
    case GFX_ANGLE:
    default:
       return false;
    }
#endif
}
GFXEngine currentGFXEngine()
{
    QSettings settings;

    return (GFXEngine)qMin(settings.value(GFXEngineSetting, GFX_OpenGL).toUInt(), (unsigned int)MaxGFXEngine);
}
void setCurrentGFXEngine(GFXEngine e)
{
    QSettings settings;

    settings.setValue(GFXEngineSetting, qMin((unsigned int)e, (unsigned int)MaxGFXEngine));
}

QString getOpenGLVersionString()
{
    static QString glversion;
    if (glversion.isEmpty()) {

    #ifdef BROKEN_OPENGL_BUILD
        glversion="LegacyGFX";
        qDebug() << "This LegacyGFX build has been created without the need for OpenGL";
    #else
        #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
            QGLWidget w;
            w.makeCurrent();
            QOpenGLFunctions f;
            f.initializeOpenGLFunctions();
            glversion = QString(QLatin1String(reinterpret_cast<const char*>(f.glGetString(GL_VERSION))));
        #else
            QOffscreenSurface surf;
            surf.create();
            QOpenGLContext ctx;
            ctx.create();
            ctx.makeCurrent(&surf);
            glversion = (const char*)ctx.functions()->glGetString(GL_VERSION);
            surf.destroy();
        #endif
    #endif
    }
    return glversion;
}

float getOpenGLVersion()
{
#ifdef BROKEN_OPENGL_BUILD
    return 0;
#else
    QString glversion = getOpenGLVersionString();
    glversion = glversion.section(" ",0,0);
    bool ok;
    float v = glversion.toFloat(&ok);

    if (!ok) {
        QString tmp = glversion.section(".",0,1);
        v = tmp.toFloat(&ok);
        if (!ok) {
            // just look at major, we are only interested in whether we have OpenGL 2.0 anyway
            tmp = glversion.section(".",0,0);
            v = tmp.toFloat(&ok);
        }
    }
    return v;
#endif
}

// Obtains graphic engine as a string
// This works on Windows.  Don't know about other platforms.
QString getGraphicsEngine()
{
    QString gfxEngine = QString();
#ifdef BROKEN_OPENGL_BUILD
    gfxEngine = CSTR_GFX_BrokenGL;
#else
    if (QCoreApplication::testAttribute(Qt::AA_UseSoftwareOpenGL))
        gfxEngine = CSTR_GFX_BrokenGL;
    else if (QCoreApplication::testAttribute(Qt::AA_UseOpenGLES))
        gfxEngine = CSTR_GFX_ANGLE;
    else
        gfxEngine = CSTR_GFX_OpenGL;
#endif
    return gfxEngine;
}

QString getCompilerVersion()
{
    #ifdef __clang_version__
        return QString("clang++:%1").arg(__clang_version__);
    #elif defined(__MINGW64__)
        return QString("MINGW64:%1").arg(__VERSION__);
    #elif defined(__MINGW32__)
        return QString("MINGW32:%1").arg(__VERSION__);
    #elif defined (__GNUG__) or defined (__GNUC__)
        return QString("GNU C++:%1").arg(__VERSION__);
    #else
        return QString();
    #endif
}

QString getCppVersion() {
    #if __cplusplus > 202002L
        return  "Using C++23 or later";
    #elif __cplusplus == 202002L
        return  "Using C++20";
    #elif __cplusplus >= 201703L
        return "Using C++17";
    #elif __cplusplus >= 201402L
        return "Using C++14";
    #elif __cplusplus >= 201103L
        return "Using C++11";
    #else
        return QString();
    #endif
}

QStringList buildInfo;

QStringList makeBuildInfo (QString forcedEngine){
    // application name and version has already been added
    buildInfo << (QObject::tr("Built with Qt %1 on %2").arg(QT_VERSION_STR).arg(getBuildDateTime()));
    QString compiler = getCompilerVersion();
    if (compiler.length() >0 )
        buildInfo << (QObject::tr("Compiler:") + " " + compiler);
    if (!getCppVersion().isEmpty())
        buildInfo << getCppVersion();
    buildInfo << QString("");
    buildInfo << (QObject::tr("Operating system:") + " " + QSysInfo::prettyProductName());
    buildInfo << (QObject::tr("Graphics Engine:") + " " + getOpenGLVersionString());
    buildInfo << (QObject::tr("Graphics Engine type:") + " " + getGraphicsEngine());

    if (forcedEngine != "")
        buildInfo << forcedEngine;

    buildInfo << QString("");
    if (getAppName() != STR_AppName)        // Report any non-standard app key
        buildInfo << (QObject::tr("App key:") + " " + getAppName());
    // Data directory will always be added, later.
    
    return buildInfo;
}

QStringList addBuildInfo (QString value) {
    buildInfo << (value);
    return buildInfo;
}

QStringList getBuildInfo() {
    return buildInfo;
}

QString appResourcePath()
{
     static QString path;
     if ( path.size() != 0 )
         return path;

#ifdef Q_OS_MAC
    path = QDir::cleanPath(QCoreApplication::applicationDirPath() + "/../Resources");
#else
//    QStringList paths = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
    // Check the Appiication Path first, so we can execute out of the build directory
    QStringList paths;
    // This one will be used if the Html and Translations folders
    // are in the same folder as  the OSCAR executable
    paths.append( QCoreApplication::applicationDirPath() );
#ifdef Q_OS_LINUX 
    QString appName = QCoreApplication::applicationName();
    if (appName != QString("OSCAR"))
        appName = QString("OSCAR-test");
    paths.append( QString( "/usr/share/" ) + appName );
    paths.append( QString( "/usr/local/share/" ) + appName );
#endif
    for (auto p = begin(paths); p != end(paths); ++p ) {
        QString fname = *p+QString("/Html/about.html"); // was "/Translations/oscar_qt_fr.qm"  - Crimson Nape
        qDebug() << "Trying" << fname;
        QFileInfo f = QFileInfo(fname);
        if ( f.exists() ) {
            path = *p;
            break;
        }
    }
    if ( path.size() == 0 )
        path = QCoreApplication::applicationDirPath();
#endif
     
    return path;
}

Qt::DayOfWeek firstDayOfWeekFromLocale()
{
    return QLocale::system().firstDayOfWeek();
}

int idealThreads() { return QThread::idealThreadCount(); }

qint64 timezoneOffset()
{
    static bool ok = false;
    static qint64 _TZ_offset = 0;

    if (ok) { return _TZ_offset; }

    QDateTime d1 = QDateTime::currentDateTime();
    QDateTime d2 = d1;
    d1.setTimeZone(QTimeZone("UTC"));
    _TZ_offset = d2.secsTo(d1);
    _TZ_offset *= 1000L;
    return _TZ_offset;
}

QString weightString(float kg, UnitSystem us)
{
    if (us == US_Undefined) {
        us = p_profile->general->unitSystem();
    }

    if (us == US_Metric) {
        return QString("%1kg").arg(kg, 0, 'f', 2);
    } else if (us == US_English) {
        int oz = (kg * 1000.0) * (float)gram_ounce_convert;
        int lb = oz / 16.0;
        oz = oz % 16;
        return QString("%1lb %2oz").arg(lb, 0, 10).arg(oz);
    }

    return ("Bad UnitSystem");
}

// Format perssure relief
QString formatRelief (QString relief)
{
    int icm = 0;
    QString newRelief = relief;
    if (relief == nullptr )
        return relief;
    icm = relief.indexOf("cmH2O");
    if (icm >= 1) {
        QChar t = relief.mid(icm-1,1)[0];
        if (t.isDigit())
            newRelief = relief.insert(icm, " ");
    }
//  qDebug() << "Relief input" << relief << "returning" << newRelief;
    return newRelief;
}

bool operator <(const ValueCount &a, const ValueCount &b)
{
    return a.value < b.value;
}

static QStringList installedFontFamilies;

// Validate all fonts
void validateAllFonts () {
    validateFont("Application", 10, false, false);
    validateFont("Graph", 10, false, false);
    validateFont("Title", 12, true, false);
    validateFont("Big", 35, false, false);
}

// Validate font from preference settings, and substitute system font if font in preferences cannot be found on this system
void validateFont (QString which, int size, bool bold, bool italic) {

    // Get list of installed font families, including system font
    // Do this just once so we don't have to call font functions repeatedly
    // (This list includes private fonts)
    #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    QFontDatabase fontdatabase;
    #endif
    if (installedFontFamilies.isEmpty()) {
        #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
            installedFontFamilies = fontdatabase.families();
        #else
            installedFontFamilies = QFontDatabase::families();
        #endif
        }
    qDebug() << "validateFont found" << installedFontFamilies.count() << "installed font families";

    QString prefPrefix = "Fonts_" + which + "_";

    // start off assuming we don't have a font specified, and system font is desired font
    QString desiredFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont).family();
    bool forceFont = true;

    // If a font is specified, make sure it is a valid font on this platform
    if (p_pref->contains(prefPrefix + "Name")) {
        // We already have a font, so it becomes desired font (if valid)
        QString testFont = (*p_pref)[prefPrefix + "Name"].toString();
	int prefSize = (*p_pref)[prefPrefix+"Size"].toInt();
        // Is this a good font?
        if (testFont.length() > 0 ) {
	    qDebug() << which << "Preferences font is" << testFont;
    	    if ( installedFontFamilies.indexOf(testFont) >= 0) {
                desiredFont = testFont;
                forceFont = false;
            } else {
                qDebug() << testFont << prefSize << "not found, substituting" << desiredFont << size;
                for (int i = 0; i< installedFontFamilies.size(); i++)
                    qDebug() << installedFontFamilies.at(i);
            }
	}
    }

#ifdef Q_OS_MAC
    // Don't allow private font to be set for anything other than Application font (Mac restricts use to UI)
    #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    if (which != "Application" && fontdatabase.isPrivateFamily(desiredFont)) {
    #else
    if (which != "Application" && QFontDatabase::isPrivateFamily(desiredFont)) {
    #endif
        desiredFont = "Helvetica";              // We assume "Helvetica" is universally available on Mac
        forceFont = true;
    }
#endif

    // Font not valid or not specified.  Set a default font in its place
    if (forceFont) {
        (*p_pref)[prefPrefix + "Name"]   = desiredFont;
        (*p_pref)[prefPrefix + "Size"]   = size;
        (*p_pref)[prefPrefix + "Bold"]   = bold;
        (*p_pref)[prefPrefix + "Italic"] = italic;
    }
    qDebug() << which << "font set to" << desiredFont << "at size" << (*p_pref)[prefPrefix + "Size"];
}

void setApplicationFont () {
    if (mainwin == nullptr) {
	qWarning() << "Warning: mainwin not set in setApplicationFont";
	return;	
    }
    qDebug() << "Application font starts out as" << QApplication::font();
    if (p_pref == nullptr) {
	qWarning() << "Warning: p_pref not set in setApplicationFont";
	return;
    }
    QFont font = QFont(((*p_pref)["Fonts_Application_Name"]).toString());
    font.setPointSize(((*p_pref)["Fonts_Application_Size"]).toInt());
    font.setWeight(((*p_pref)["Fonts_Application_Bold"]).toBool() ? QFont::Bold : QFont::Normal);
    font.setItalic(((*p_pref)["Fonts_Application_Italic"]).toBool());
    QApplication::setFont(font);
    mainwin->menuBar()->setFont(font);
    qDebug() << "Application font set to" << font;
    qDebug() << "Application font reads back as" << QApplication::font();
    qDebug() << "system font is" << QFontDatabase::systemFont(QFontDatabase::GeneralFont).family();
}

bool removeDir(const QString &path)
{
    bool result = true;
    QDir dir(path);

    if (dir.exists(path)) {
        Q_FOREACH(QFileInfo info, dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden  |
                  QDir::AllDirs | QDir::Files, QDir::DirsFirst)) {
            if (info.isDir()) { // Recurse to remove this child directory
                result = removeDir(info.absoluteFilePath());
            } else { // File
                result = QFile::remove(info.absoluteFilePath());
            }

            if (!result) {
                return result;
            }
        }
        result = dir.rmdir(path);
    }

    return result;
}

void copyPath(QString src, QString dst, bool overwrite)
{
    QDir dir(src);
    if (!dir.exists())
        return;

    // Recursively handle directories
    foreach (QString d, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
        QString dst_path = dst + QDir::separator() + d;
        dir.mkpath(dst_path);
        copyPath(src + QDir::separator() + d, dst_path, overwrite);
    }

    // Files
    foreach (QString f, dir.entryList(QDir::Files)) {
        QString srcFile = src + QDir::separator() + f;
        QString destFile = dst + QDir::separator() + f;

        if (overwrite && QFile::exists(destFile)) {
            QFile::remove(destFile);
        }
        if (!QFile::exists(destFile)) {
            if (!QFile::copy(srcFile, destFile)) {
                qWarning() << "copyPath: could not copy" << srcFile << "to" << destFile;
            }
            // TODO: Since copyPath is only used by loaders, it should
            // build the list of files first, and then update the progress bar
            // while copying.
            // TODO: copyPath should also either hide the abort button
            // or respond to it.
            QCoreApplication::processEvents();
        }
    }
}

QString GFXEngineNames[MaxGFXEngine+1]; // Set by initializeStrings()

QString STR_UNIT_M;
QString STR_UNIT_CM;
QString STR_UNIT_INCH;
QString STR_UNIT_FOOT;
QString STR_UNIT_POUND;
QString STR_UNIT_OUNCE;
QString STR_UNIT_KG;
QString STR_UNIT_CMH2O;
QString STR_UNIT_Hours;
QString STR_UNIT_Minutes;
QString STR_UNIT_Seconds;
QString STR_UNIT_milliSeconds;
QString STR_UNIT_h;
QString STR_UNIT_m;
QString STR_UNIT_s;
QString STR_UNIT_ms;
QString STR_UNIT_BPM;       // Beats per Minute
QString STR_UNIT_LPM;       // Litres per Minute
QString STR_UNIT_ml;        // MilliLitres
QString STR_UNIT_Litres;
QString STR_UNIT_Hz;
QString STR_UNIT_EventsPerHour;
QString STR_UNIT_BreathsPerMinute;
QString STR_UNIT_Percentage;
QString STR_UNIT_Unknown;
QString STR_UNIT_Ratio;
QString STR_UNIT_Severity;
QString STR_UNIT_Degrees;

QString STR_MessageBox_Question;
QString STR_MessageBox_Error;
QString STR_MessageBox_Warning;
QString STR_MessageBox_Information;
QString STR_MessageBox_Busy;
QString STR_MessageBox_PleaseNote;
QString STR_MessageBox_Yes;
QString STR_MessageBox_No;
QString STR_MessageBox_Cancel;
QString STR_MessageBox_Destroy;
QString STR_MessageBox_Save;

QString STR_Empty_NoData;
QString STR_Empty_Brick;
QString STR_Empty_NoGraphs;
QString STR_Empty_SummaryOnly;
QString STR_Empty_NoSessions;


QString STR_TR_BMI;         // Short form of Body Mass Index
QString STR_TR_Weight;
QString STR_TR_Zombie;
QString STR_TR_PulseRate;   // Pulse / Heart rate
QString STR_TR_SpO2;
QString STR_TR_Plethy;      // Plethysomogram
QString STR_TR_Pressure;

QString STR_TR_Daily;
QString STR_TR_Profile;
QString STR_TR_Overview;
QString STR_TR_Oximetry;

QString STR_TR_Oximeter;
QString STR_TR_EventFlags;

QString STR_TR_Inclination;
QString STR_TR_Orientation;
QString STR_TR_Motion;


// Device type names.
QString STR_TR_CPAP;    // Constant Positive Airway Pressure
QString STR_TR_BIPAP;   // Bi-Level Positive Airway Pressure
QString STR_TR_BiLevel; // Another name for BiPAP
QString STR_TR_EPAP;    // Expiratory Positive Airway Pressure
QString STR_TR_EEPAP;    // End Expiratory Positive Airway Pressure
QString STR_TR_EEPAPLo;  // End-Expiratory Positive Airway Pressure, Low
QString STR_TR_EEPAPHi;  // End-Expiratory Positive Airway Pressure, High
QString STR_TR_EPAPLo;  // Expiratory Positive Airway Pressure, Low
QString STR_TR_EPAPHi;  // Expiratory Positive Airway Pressure, High
QString STR_TR_IPAP;    // Inspiratory Positive Airway Pressure
QString STR_TR_IPAPLo;  // Inspiratory Positive Airway Pressure, Low
QString STR_TR_IPAPHi;  // Inspiratory Positive Airway Pressure, High
QString STR_TR_APAP;    // Automatic Positive Airway Pressure
QString STR_TR_ASV;     // Assisted Servo Ventilator
QString STR_TR_AVAPS;   // Average Volume Assured Pressure Support
QString STR_TR_STASV;

QString STR_TR_Humidifier;

QString STR_TR_H;       // Short form of Hypopnea
QString STR_TR_OA;      // Short form of Obstructive Apnea
QString STR_TR_A;       // Short form of Apnea
QString STR_TR_UA;      // Short form of Unspecified Apnea
QString STR_TR_CA;      // Short form of Clear Airway Apnea
QString STR_TR_FL;      // Short form of Flow Limitation
QString STR_TR_SA;      // Short form of SensAwake
QString STR_TR_LE;      // Short form of Leak Event
QString STR_TR_EP;      // Short form of Expiratory Puff
QString STR_TR_VS;      // Short form of Vibratory Snore
QString STR_TR_VS2;     // Short form of Secondary Vibratory Snore (Some Philips Respironics devices have two sources)
QString STR_TR_RERA;    // Acronym for Respiratory Effort Related Arousal
QString STR_TR_PP;      // Short form for Pressure Pulse
QString STR_TR_P;       // Short form for Pressure Event
QString STR_TR_RE;      // Short form of Respiratory Effort Related Arousal
QString STR_TR_NR;      // Short form of Non Responding event? (forgot sorry)
QString STR_TR_NRI;     // Sorry I Forgot.. it's a flag on Intellipap devices
QString STR_TR_O2;      // SpO2 Desaturation
QString STR_TR_PC;      // Short form for Pulse Change
QString STR_TR_UF1;     // Short form for User Flag 1
QString STR_TR_UF2;     // Short form for User Flag 2
QString STR_TR_UF3;     // Short form for User Flag 3



QString STR_TR_PS;     // Short form of Pressure Support
QString STR_TR_AHI;    // Short form of Apnea Hypopnea Index
QString STR_TR_RDI;    // Short form of Respiratory Distress Index
QString STR_TR_AI;     // Short form of Apnea Index
QString STR_TR_HI;     // Short form of Hypopnea Index
QString STR_TR_UAI;    // Short form of Uncatagorized Apnea Index
QString STR_TR_CAI;    // Short form of Clear Airway Index
QString STR_TR_FLI;    // Short form of Flow Limitation Index
//QString STR_TR_SAI;    // Short form of SensAwake Index
QString STR_TR_REI;    // Short form of RERA Index
QString STR_TR_EPI;    // Short form of Expiratory Puff Index
QString STR_TR_CSR;    // Short form of Cheyne Stokes Respiration
QString STR_TR_PB;     // Short form of Periodic Breathing


// Graph Titles
QString STR_TR_IE;              // Inspiratory Expiratory Ratio
QString STR_TR_InspTime;        // Inspiratory Time
QString STR_TR_ExpTime;         // Expiratory Time
QString STR_TR_RespEvent;       // Respiratory Event
QString STR_TR_FlowLimitation;
QString STR_TR_FlowLimit;
//QString STR_TR_FlowLimitation;
QString STR_TR_SensAwake;
QString STR_TR_PatTrigBreath;   // Patient Triggered Breath
QString STR_TR_TgtMinVent;      // Target Minute Ventilation
QString STR_TR_TargetVent;      // Target Ventilation
QString STR_TR_MinuteVent;      // Minute Ventilation
QString STR_TR_TidalVolume;
QString STR_TR_RespRate;        // Respiratory Rate
QString STR_TR_Snore;
QString STR_TR_Leak;
QString STR_TR_Leaks;
QString STR_TR_LargeLeak;
QString STR_TR_LL;
QString STR_TR_TotalLeaks;
QString STR_TR_UnintentionalLeaks;
QString STR_TR_MaskPressure;
QString STR_TR_FlowRate;
QString STR_TR_SleepStage;
QString STR_TR_Usage;
QString STR_TR_Sessions;
QString STR_TR_PrRelief; // Pressure Relief

QString STR_TR_Bookmarks;
QString STR_TR_OSCAR;
//QString STR_TR_AppVersion;

QString STR_TR_Default;

QString STR_TR_Mode;
QString STR_TR_Model;
QString STR_TR_Brand;
QString STR_TR_Serial;
QString STR_TR_Series;
QString STR_TR_Machine;
QString STR_TR_Channel;
QString STR_TR_Settings;

QString STR_TR_Name;
QString STR_TR_DOB;    // Date of Birth
QString STR_TR_Phone;
QString STR_TR_Address;
QString STR_TR_Email;
QString STR_TR_PatientID;
QString STR_TR_Date;

QString STR_TR_BedTime;
QString STR_TR_WakeUp;
QString STR_TR_MaskTime;
QString STR_TR_Unknown;
QString STR_TR_None;
QString STR_TR_Ready;

QString STR_TR_First;
QString STR_TR_Last;
QString STR_TR_Start;
QString STR_TR_End;
QString STR_TR_On;
QString STR_TR_Off;
QString STR_TR_Auto;
QString STR_TR_Yes;
QString STR_TR_No;

QString STR_TR_Min;    // Minimum
QString STR_TR_Max;    // Maximum
QString STR_TR_Med;    // Median

QString STR_TR_Average;
QString STR_TR_Median;
QString STR_TR_Avg;    // Short form of Average
QString STR_TR_WAvg;   // Short form of Weighted Average

void initializeStrings()
{
    GFXEngineNames[GFX_Software] = QObject::tr("Software Engine");
    GFXEngineNames[GFX_ANGLE] = QObject::tr("ANGLE / OpenGLES");
    GFXEngineNames[GFX_OpenGL] = QObject::tr("Desktop OpenGL");

    STR_UNIT_M = QObject::tr(" m");
    STR_UNIT_CM = QObject::tr(" cm");
    STR_UNIT_INCH = QObject::tr("in");
    STR_UNIT_FOOT = QObject::tr("ft");
    STR_UNIT_POUND = QObject::tr("lb");
    STR_UNIT_OUNCE = QObject::tr("oz");
    STR_UNIT_KG = QObject::tr("kg");
    STR_UNIT_CMH2O = QObject::tr("cmH2O");
    STR_UNIT_Hours = QObject::tr("Hours");
    STR_UNIT_Minutes = QObject::tr("Minutes");
    STR_UNIT_Seconds = QObject::tr("Seconds");
    STR_UNIT_milliSeconds = QObject::tr("milliSeconds");
    STR_UNIT_h = QObject::tr("h"); // hours shortform
    STR_UNIT_m = QObject::tr("m"); // minutes shortform
    STR_UNIT_s = QObject::tr("s"); // seconds shortform
    STR_UNIT_ms = QObject::tr("ms"); // milliseconds
    STR_UNIT_EventsPerHour = QObject::tr("Events/hr"); // Events per hour
    STR_UNIT_Percentage = QString("%");
    STR_UNIT_Hz = QObject::tr("Hz");          // Hertz
    STR_UNIT_BPM = QObject::tr("bpm");        // Beats per Minute
    STR_UNIT_LPM = QObject::tr("l/min");      // Litres per Minute
    STR_UNIT_Litres = QObject::tr("Litres");
    STR_UNIT_ml = QObject::tr("ml");        // millilitres
    STR_UNIT_BreathsPerMinute = QObject::tr("Breaths/min"); // Breaths per minute
    STR_UNIT_Unknown = QString("?");
    STR_UNIT_Ratio = QObject::tr("ratio");
    STR_UNIT_Severity = QObject::tr("Severity (0-1)");
    STR_UNIT_Degrees = QObject::tr("Degrees");

    STR_MessageBox_Question = QObject::tr("Question");
    STR_MessageBox_Error = QObject::tr("Error");
    STR_MessageBox_Warning = QObject::tr("Warning");
    STR_MessageBox_Information = QObject::tr("Information");
    STR_MessageBox_Busy = QObject::tr("Busy");
    STR_MessageBox_PleaseNote = QObject::tr("Please Note");

    STR_Empty_NoData = QObject::tr("No Data Available");
    STR_Empty_Brick = QObject::tr("Only Settings and Compliance Data Available");
    STR_Empty_NoGraphs = QObject::tr("Graphs Switched Off");
    STR_Empty_SummaryOnly = QObject::tr("Summary Data Only");
    STR_Empty_NoSessions = QObject::tr("Sessions Switched Off");


    // Dialog box options
    STR_MessageBox_Yes = QObject::tr("Yes");
    STR_MessageBox_No = QObject::tr("No");
    STR_MessageBox_Cancel = QObject::tr("Cancel");
    STR_MessageBox_Destroy = QObject::tr("Destroy");;
    STR_MessageBox_Save = QObject::tr("Save");

    STR_TR_BMI = QObject::tr("BMI");              // Short form of Body Mass Index
    STR_TR_Weight = QObject::tr("Weight");
    STR_TR_Zombie = QObject::tr("Feeling");
    STR_TR_PulseRate = QObject::tr("Pulse Rate"); // Pulse / Heart rate
    STR_TR_SpO2 = QString("SpO2");
    STR_TR_Plethy = QObject::tr("Plethy");        // Plethysomogram
    STR_TR_Pressure = QObject::tr("Pressure");

    STR_TR_Daily = QObject::tr("Daily");
    STR_TR_Profile = QObject::tr("Profile");
    STR_TR_Overview = QObject::tr("Overview");
    STR_TR_Oximetry = QObject::tr("Oximetry");

    STR_TR_Oximeter = QObject::tr("Oximeter");
    STR_TR_EventFlags = QObject::tr("Event Flags");


    STR_TR_Default = QObject::tr("Default");

    // Device type names.
    STR_TR_CPAP = QObject::tr("CPAP");    // Constant Positive Airway Pressure
    STR_TR_BIPAP = QObject::tr("BiPAP");  // Bi-Level Positive Airway Pressure
    STR_TR_BiLevel = QObject::tr("Bi-Level"); // Another name for BiPAP
    STR_TR_EPAP = QObject::tr("EPAP");    // Expiratory Positive Airway Pressure
    STR_TR_EEPAP = QObject::tr("EEPAP");    // End-Expiratory Positive Airway Pressure
    STR_TR_EEPAPLo = QObject::tr("Min EEPAP");    // Lower End-Expiratory Positive Airway Pressure
    STR_TR_EEPAPHi = QObject::tr("Max EEPAP");    // Higher End-Expiratory Positive Airway Pressure
    STR_TR_EPAPLo = QObject::tr("Min EPAP"); // Lower Expiratory Positive Airway Pressure
    STR_TR_EPAPHi = QObject::tr("Max EPAP"); // Higher Expiratory Positive Airway Pressure
    STR_TR_IPAP = QObject::tr("IPAP");    // Inspiratory Positive Airway Pressure
    STR_TR_IPAPLo = QObject::tr("Min IPAP"); // Lower Inspiratory Positive Airway Pressure
    STR_TR_IPAPHi = QObject::tr("Max IPAP"); // Higher Inspiratory Positive Airway Pressure
    STR_TR_APAP = QObject::tr("APAP");    // Automatic Positive Airway Pressure
    STR_TR_ASV = QObject::tr("ASV");      // Assisted Servo Ventilator
    STR_TR_AVAPS = QObject::tr("AVAPS");  // Average Volume Assured Pressure Support
    STR_TR_STASV = QObject::tr("ST/ASV");

    STR_TR_Humidifier = QObject::tr("Humidifier");

    STR_TR_H = QObject::tr("H");      // Short form of Hypopnea
    STR_TR_OA = QObject::tr("OA");    // Short form of Obstructive Apnea
    STR_TR_A = QObject::tr("A");     // Short form of All Apnea
    STR_TR_UA = QObject::tr("UA");     // Short form of Unspecified Apnea
    STR_TR_CA = QObject::tr("CA");    // Short form of Clear Airway Apnea
    STR_TR_FL = QObject::tr("FL");    // Short form of Flow Limitation
    STR_TR_SA = QObject::tr("SA");    // Short form of Flow Limitation
    STR_TR_LE = QObject::tr("LE");    // Short form of Leak Event
    STR_TR_EP = QObject::tr("EP");    // Short form of Expiratory Puff
    STR_TR_VS = QObject::tr("VS");    // Short form of Vibratory Snore
    STR_TR_VS2 =
        QObject::tr("VS2");  // Short form of Secondary Vibratory Snore (Some Philips Respironics devices have two sources)
    STR_TR_RERA = QObject::tr("RERA"); // Acronym for Respiratory Effort Related Arousal
    STR_TR_PP = QObject::tr("PP");    // Short form for Pressure Pulse
    STR_TR_P = QObject::tr("P");      // Short form for Pressure Event
    STR_TR_RE = QObject::tr("RE");    // Short form of Respiratory Effort Related Arousal
    STR_TR_NR = QObject::tr("NR");    // Short form of Non Responding event? (forgot sorry)
    STR_TR_NRI = QObject::tr("NRI");  // Sorry I Forgot.. it's a flag on Intellipap devices
    STR_TR_O2 = QObject::tr("O2");    // SpO2 Desaturation
    STR_TR_PC = QObject::tr("PC");    // Short form for Pulse Change
    STR_TR_UF1 = QObject::tr("UF1");    // Short form for User Flag 1
    STR_TR_UF2 = QObject::tr("UF2");    // Short form for User Flag 2
    STR_TR_UF3 = QObject::tr("UF3");    // Short form for User Flag 3

    STR_TR_PS = QObject::tr("PS");    // Short form of Pressure Support
    STR_TR_AHI = QObject::tr("AHI");  // Short form of Apnea Hypopnea Index
    STR_TR_RDI = QObject::tr("RDI");  // Short form of Respiratory Distress Index
    STR_TR_AI = QObject::tr("AI");    // Short form of Apnea Index
    STR_TR_HI = QObject::tr("HI");    // Short form of Hypopnea Index
    STR_TR_UAI = QObject::tr("UAI");  // Short form of Uncatagorized Apnea Index
    STR_TR_CAI = QObject::tr("CAI");  // Short form of Clear Airway Index
    STR_TR_FLI = QObject::tr("FLI");  // Short form of Flow Limitation Index
//    STR_TR_SAI = QObject::tr("SAI");  // Short form of SleepAwake Index
    STR_TR_REI = QObject::tr("REI");  // Short form of RERA Index
    STR_TR_EPI = QObject::tr("EPI");  // Short form of Expiratory Puff Index
    STR_TR_CSR = QObject::tr("CSR");  // Short form of Cheyne Stokes Respiration
    STR_TR_PB = QObject::tr("PB");    // Short form of Periodic Breathing


    // Graph Titles
    STR_TR_IE = QObject::tr("IE");    // Inspiratory Expiratory Ratio
    STR_TR_InspTime = QObject::tr("Insp. Time");  // Inspiratory Time
    STR_TR_ExpTime = QObject::tr("Exp. Time");    // Expiratory Time
    STR_TR_RespEvent = QObject::tr("Resp. Event");  // Respiratory Event
    STR_TR_FlowLimitation = QObject::tr("Flow Limitation");
    STR_TR_FlowLimit = QObject::tr("Flow Limit");
    STR_TR_SensAwake = QObject::tr("SensAwake");
    STR_TR_PatTrigBreath = QObject::tr("Pat. Trig. Breath"); // Patient Triggered Breath
    STR_TR_TgtMinVent = QObject::tr("Tgt. Min. Vent");      // Target Minute Ventilation
    STR_TR_TargetVent = QObject::tr("Target Vent.");        // Target Ventilation
    STR_TR_MinuteVent = QObject::tr("Minute Vent."); // Minute Ventilation
    STR_TR_TidalVolume = QObject::tr("Tidal Volume");
    STR_TR_RespRate = QObject::tr("Resp. Rate");  // Respiratory Rate
    STR_TR_Snore = QObject::tr("Snore");
    STR_TR_Leak = QObject::tr("Leak");
    STR_TR_Leaks = QObject::tr("Leaks");
    STR_TR_LargeLeak = QObject::tr("Large Leak");
    STR_TR_LL = QObject::tr("LL"); // Large Leak
    STR_TR_TotalLeaks = QObject::tr("Total Leaks");
    STR_TR_UnintentionalLeaks = QObject::tr("Unintentional Leaks");
    STR_TR_MaskPressure = QObject::tr("MaskPressure");
    STR_TR_FlowRate = QObject::tr("Flow Rate");
    STR_TR_SleepStage = QObject::tr("Sleep Stage");
    STR_TR_Usage = QObject::tr("Usage");
    STR_TR_Sessions = QObject::tr("Sessions");
    STR_TR_PrRelief = QObject::tr("Pr. Relief"); // Pressure Relief

    STR_TR_Bookmarks = QObject::tr("Bookmarks");
    STR_TR_OSCAR = QString("OSCAR");
    //STR_TR_AppVersion = QObject::tr("v%1").arg(getVersion());

    STR_TR_Mode = QObject::tr("Mode");
    STR_TR_Model = QObject::tr("Model");
    STR_TR_Brand = QObject::tr("Brand");
    STR_TR_Serial = QObject::tr("Serial");
    STR_TR_Series = QObject::tr("Series");
    STR_TR_Machine = QObject::tr("Device");
    STR_TR_Channel = QObject::tr("Channel");
    STR_TR_Settings = QObject::tr("Settings");

    STR_TR_Inclination = QObject::tr("Inclination");
    STR_TR_Orientation = QObject::tr("Orientation");
    STR_TR_Motion = QObject::tr("Motion");

    STR_TR_Name = QObject::tr("Name");
    STR_TR_DOB = QObject::tr("DOB");  // Date of Birth
    STR_TR_Phone = QObject::tr("Phone");
    STR_TR_Address = QObject::tr("Address");
    STR_TR_Email = QObject::tr("Email");
    STR_TR_PatientID = QObject::tr("Patient ID");
    STR_TR_Date = QObject::tr("Date");

    STR_TR_BedTime = QObject::tr("Bedtime");
    STR_TR_WakeUp = QObject::tr("Wake-up");
    STR_TR_MaskTime = QObject::tr("Mask Time");
    STR_TR_Unknown = QObject::tr("Unknown");
    STR_TR_None = QObject::tr("None");
    STR_TR_Ready = QObject::tr("Ready");

    STR_TR_First = QObject::tr("First");
    STR_TR_Last = QObject::tr("Last");
    STR_TR_Start = QObject::tr("Start");
    STR_TR_End = QObject::tr("End");
    STR_TR_On = QObject::tr("On");
    STR_TR_Off = QObject::tr("Off");
    STR_TR_Auto = QObject::tr("Auto");
    STR_TR_Yes = QObject::tr("Yes");
    STR_TR_No = QObject::tr("No");

    STR_TR_Min = QObject::tr("Min");  // Minimum
    STR_TR_Max = QObject::tr("Max");  // Maximum
    STR_TR_Med = QObject::tr("Med");  // Median

    STR_TR_Average = QObject::tr("Average");
    STR_TR_Median = QObject::tr("Median");
    STR_TR_Avg = QObject::tr("Avg");      // Average
    STR_TR_WAvg = QObject::tr("W-Avg");   // Weighted Average
}




quint32 CRC32(const char * data, quint32 length)
{
  quint32 crc32 = 0xffffffff;

  for (quint32 idx=0; idx<length; idx++) {

    quint32 i = (data[idx]) ^ ((crc32) & 0x000000ff);

    for(int j=8; j > 0; j--) {
        if (i & 1) {
            i = (i >> 1) ^ 0xedb88320;
        } else {
            i >>= 1;
        }
    }

    crc32 = ((crc32) >> 8) ^ i;
  }

  return ~crc32;
}

quint32 crc32buf(const QByteArray& data)
{
    return CRC32(data.constData(), data.size());
}

// Gzip function
QByteArray gCompress(const QByteArray& data)
{
    QByteArray compressedData = qCompress(data);
    //  Strip the first six bytes (a 4-byte length put on by qCompress and a 2-byte zlib header)
    // and the last four bytes (a zlib integrity check).
    compressedData.remove(0, 6);
    compressedData.chop(4);

    QByteArray header;
    QDataStream ds1(&header, QIODevice::WriteOnly);
    // Prepend a generic 10-byte gzip header (see RFC 1952),
    ds1 << quint16(0x1f8b)
        << quint16(0x0800)
        << quint16(0x0000)
        << quint16(0x0000)
        << quint16(0x000b);

    // Append a four-byte CRC-32 of the uncompressed data
    // Append 4 bytes uncompressed input size modulo 2^32
    QByteArray footer;
    QDataStream ds2(&footer, QIODevice::WriteOnly);
    ds2.setByteOrder(QDataStream::LittleEndian);
    ds2 << crc32buf(data)
        << quint32(data.size());

    return header + compressedData + footer;
}


// Pinched from http://stackoverflow.com/questions/2690328/qt-quncompress-gzip-data
QByteArray gUncompress(const QByteArray & data)
{
    if (data.size() <= 4) {
        qWarning("gUncompress: Input data is truncated");
        return QByteArray();
    }

    QByteArray result;

    int ret;
    z_stream strm;
    static const int CHUNK_SIZE = 1048576;
    char *out = new char [CHUNK_SIZE];

    /* allocate inflate state */
    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;
    strm.avail_in = data.size();
    strm.next_in = (Bytef*)(data.data());

    ret = inflateInit2(&strm, 15 +  32); // gzip decoding
    if (ret != Z_OK) {
        delete [] out;
        return QByteArray();
    }

    // run inflate()
    do {
        strm.avail_out = CHUNK_SIZE;
        strm.next_out = (Bytef*)(out);

        ret = inflate(&strm, Z_NO_FLUSH);
        if (ret == Z_STREAM_ERROR) {
            qWarning() << "ret == Z_STREAM_ERROR in gzUncompress in common.cpp";
            delete [] out;
            return QByteArray();
        }

        switch (ret) {
        case Z_NEED_DICT:
            ret = Z_DATA_ERROR;
            // fall through
        case Z_DATA_ERROR:
        case Z_MEM_ERROR:
            Q_UNUSED(ret)
            (void)inflateEnd(&strm);
            delete [] out;
            return QByteArray();
        }

        result.append(out, CHUNK_SIZE - strm.avail_out);
    } while (strm.avail_out == 0);

    // clean up and return
    inflateEnd(&strm);
    delete [] out;
    return result;
}
