#include <qlayout.h>
#include <qiconview.h>
#include <qsqldatabase.h>
#include <qwidgetstack.h>
#include <qvbox.h>
#include <qgrid.h>
#include <qregexp.h>
#include <qhostaddress.h>

#include <unistd.h>

#include <iostream>
#include <cerrno>
using namespace std;

#include "config.h"
#include "statusbox.h"
#include "mythcontext.h"
#include "remoteutil.h"
#include "programinfo.h"
#include "tv.h"
#include "jobqueue.h"
#include "util.h"
#include "mythdbcon.h"
#include "cardutil.h"

#define REC_CAN_BE_DELETED(rec) \
    ((((rec)->programflags & FL_INUSEPLAYING) == 0) && \
     ((((rec)->programflags & FL_INUSERECORDING) == 0) || \
      ((rec)->recgroup != "LiveTV")))


/** \class StatusBox
 *  \brief Reports on various status items.
 *
 *  StatusBox reports on the listing status, that is how far
 *  into the future program listings exits. It also reports
 *  on the status of each tuner, the log entries, the status
 *  of the job queue, and the machine status.
 */

StatusBox::StatusBox(MythMainWindow *parent, const char *name)
    : MythDialog(parent, name), errored(false)
{
    // Set this value to the number of items in icon_list
    // to prevent scrolling off the bottom
    int item_count = 0;
    dateFormat = gContext->GetSetting("ShortDateFormat", "M/d");
    timeFormat = gContext->GetSetting("TimeFormat", "h:mm AP");
    timeDateFormat = timeFormat + " " + dateFormat;

    setNoErase();
    LoadTheme();
    if (IsErrored())
        return;
  
    icon_list->SetItemText(item_count++, QObject::tr("Listings Status"));
    icon_list->SetItemText(item_count++, QObject::tr("Tuner Status"));
    icon_list->SetItemText(item_count++, QObject::tr("Log Entries"));
    icon_list->SetItemText(item_count++, QObject::tr("Job Queue"));
    icon_list->SetItemText(item_count++, QObject::tr("Machine Status"));
    icon_list->SetItemText(item_count++, QObject::tr("AutoExpire List"));
    icon_list->SetItemCurrent(0);
    icon_list->SetActive(true);

    QStringList strlist;
    strlist << "QUERY_IS_ACTIVE_BACKEND";
    strlist << gContext->GetHostName();

    gContext->SendReceiveStringList(strlist);

    if (QString(strlist[0]) == "FALSE")
        isBackend = false;
    else if (QString(strlist[0]) == "TRUE")
        isBackend = true;
    else
        isBackend = false;

    VERBOSE(VB_NETWORK, QString("QUERY_IS_ACTIVE_BACKEND=%1").arg(strlist[0]));

    max_icons = item_count;
    inContent = false;
    contentPos = 0;
    contentTotalLines = 0;
    contentSize = 0;
    contentMid = 0;
    min_level = gContext->GetNumSetting("LogDefaultView",5);
    my_parent = parent;
    clicked();

    gContext->addCurrentLocation("StatusBox");
}

StatusBox::~StatusBox(void)
{
    gContext->removeCurrentLocation();
}

void StatusBox::paintEvent(QPaintEvent *e)
{
    QRect r = e->rect();

    if (r.intersects(TopRect))
        updateTopBar();
    if (r.intersects(SelectRect))
        updateSelector();
    if (r.intersects(ContentRect))
        updateContent();
}

void StatusBox::updateContent()
{
    QRect pr = ContentRect;
    QPixmap pix(pr.size());
    pix.fill(this, pr.topLeft());
    QPainter tmp(&pix);
    QPainter p(this);

    // Normalize the variables here and set the contentMid
    contentSize = list_area->GetItems();
    if (contentSize > contentTotalLines)
        contentSize = contentTotalLines;
    contentMid = contentSize / 2;

    int startPos = 0;
    int highlightPos = 0;

    if (contentPos < contentMid)
    {
        startPos = 0;
        highlightPos = contentPos;
    }
    else if (contentPos >= (contentTotalLines - contentMid))
    {
        startPos = contentTotalLines - contentSize;
        highlightPos = contentSize - (contentTotalLines - contentPos);
    }
    else if (contentPos >= contentMid)
    {
        startPos = contentPos - contentMid;
        highlightPos = contentMid;
    }
 
    if (content  == NULL) return;
    LayerSet *container = content;

    list_area->ResetList();
    for (int x = startPos; (x - startPos) <= contentSize; x++)
    {
        if (contentLines.contains(x))
        {
            list_area->SetItemText(x - startPos, contentLines[x]);
            if (contentFont.contains(x))
                list_area->EnableForcedFont(x - startPos, contentFont[x]);
        }
    }

    list_area->SetItemCurrent(highlightPos);

    if (inContent)
    {
        helptext->SetText(contentDetail[contentPos]);
        update(TopRect);
    }

    list_area->SetUpArrow((startPos > 0) && (contentSize < contentTotalLines));
    list_area->SetDownArrow((startPos + contentSize) < contentTotalLines);

    container->Draw(&tmp, 0, 0);
    container->Draw(&tmp, 1, 0);
    container->Draw(&tmp, 2, 0);
    container->Draw(&tmp, 3, 0);
    container->Draw(&tmp, 4, 0);
    container->Draw(&tmp, 5, 0);
    container->Draw(&tmp, 6, 0);
    container->Draw(&tmp, 7, 0);
    container->Draw(&tmp, 8, 0);
    tmp.end();
    p.drawPixmap(pr.topLeft(), pix);
}

void StatusBox::updateSelector()
{
    QRect pr = SelectRect;
    QPixmap pix(pr.size());
    pix.fill(this, pr.topLeft());
    QPainter tmp(&pix);
    QPainter p(this);
 
    if (selector == NULL) return;
    LayerSet *container = selector;

    container->Draw(&tmp, 0, 0);
    container->Draw(&tmp, 1, 0);
    container->Draw(&tmp, 2, 0);
    container->Draw(&tmp, 3, 0);
    container->Draw(&tmp, 4, 0);
    container->Draw(&tmp, 5, 0);
    container->Draw(&tmp, 6, 0);
    container->Draw(&tmp, 7, 0);
    container->Draw(&tmp, 8, 0);
    tmp.end();
    p.drawPixmap(pr.topLeft(), pix);
}

void StatusBox::updateTopBar()
{
    QRect pr = TopRect;
    QPixmap pix(pr.size());
    pix.fill(this, pr.topLeft());
    QPainter tmp(&pix);
    QPainter p(this);

    if (topbar == NULL) return;
    LayerSet *container = topbar;

    container->Draw(&tmp, 0, 0);
    tmp.end();
    p.drawPixmap(pr.topLeft(), pix);
}

void StatusBox::LoadTheme()
{
    int screenheight = 0, screenwidth = 0;
    float wmult = 0, hmult = 0;

    gContext->GetScreenSettings(screenwidth, wmult, screenheight, hmult);

    theme = new XMLParse();
    theme->SetWMult(wmult);
    theme->SetHMult(hmult);
    if (!theme->LoadTheme(xmldata, "status", "status-"))
    {
        VERBOSE(VB_IMPORTANT, "StatusBox: Unable to load theme.");
        errored = true;
        return;
    }

    for (QDomNode child = xmldata.firstChild(); !child.isNull();
         child = child.nextSibling()) {

        QDomElement e = child.toElement();
        if (!e.isNull()) {
            if (e.tagName() == "font") {
                theme->parseFont(e);
            }
            else if (e.tagName() == "container") {
                QRect area;
                QString name;
                int context;
                theme->parseContainer(e, name, context, area);

                if (name.lower() == "topbar")
                    TopRect = area;
                if (name.lower() == "selector")
                    SelectRect = area;
                if (name.lower() == "content")
                    ContentRect = area;
            }
            else {
                QString msg =
                    QString(tr("The theme you are using contains an "
                               "unknown element ('%1').  It will be ignored"))
                    .arg(e.tagName());
                VERBOSE(VB_IMPORTANT, msg);
                errored = true;
            }
        }
    }

    selector = theme->GetSet("selector");
    if (!selector)
    {
        VERBOSE(VB_IMPORTANT, "StatusBox: Failed to get selector container.");
        errored = true;
    }

    icon_list = (UIListType*)selector->GetType("icon_list");
    if (!icon_list)
    {
        VERBOSE(VB_IMPORTANT, "StatusBox: Failed to get icon list area.");
        errored = true;
    }

    content = theme->GetSet("content");
    if (!content)
    {
        VERBOSE(VB_IMPORTANT, "StatusBox: Failed to get content container.");
        errored = true;
    }

    list_area = (UIListType*)content->GetType("list_area");
    if (!list_area)
    {
        VERBOSE(VB_IMPORTANT, "StatusBox: Failed to get list area.");
        errored = true;
    }

    topbar = theme->GetSet("topbar");
    if (!topbar)
    {
        VERBOSE(VB_IMPORTANT, "StatusBox: Failed to get topbar container.");
        errored = true;
    }

    heading = (UITextType*)topbar->GetType("heading");
    if (!heading)
    {
        VERBOSE(VB_IMPORTANT, "StatusBox: Failed to get heading area.");
        errored = true;
    }

    helptext = (UITextType*)topbar->GetType("helptext");
    if (!helptext)
    {
        VERBOSE(VB_IMPORTANT, "StatusBox: Failed to get helptext area.");
        errored = true;
    }
}

void StatusBox::keyPressEvent(QKeyEvent *e)
{
    bool handled = false;
    QStringList actions;
    gContext->GetMainWindow()->TranslateKeyPress("Status", e, actions);

    for (unsigned int i = 0; i < actions.size() && !handled; i++)
    {
        QString action = actions[i];
        QString currentItem;
        QRegExp logNumberKeys( "^[12345678]$" );

        currentItem = icon_list->GetItemText(icon_list->GetCurrentItem());
        handled = true;

        if (action == "SELECT")
        {
            clicked();
        }
        else if (action == "MENU")
        {
            if ((inContent) &&
                (currentItem == QObject::tr("Log Entries")))
            {
                int retval = MythPopupBox::show2ButtonPopup(my_parent,
                                 QString("AckLogEntry"),
                                 QObject::tr("Acknowledge all log entries at "
                                             "this priority level or lower?"),
                                 QObject::tr("Yes"), QObject::tr("No"), 0);
                if (retval == 0)
                {
                    MSqlQuery query(MSqlQuery::InitCon());
                    query.prepare("UPDATE mythlog SET acknowledged = 1 "
                                  "WHERE priority <= :PRIORITY ;");
                    query.bindValue(":PRIORITY", min_level);
                    query.exec();
                    doLogEntries();
                }
            }
            else if ((inContent) &&
                     (currentItem == QObject::tr("Job Queue")))
            {
                clicked();
            }
        }
        else if (action == "UP")
        {
            if (inContent)
            {
                if (contentPos > 0)
                    contentPos--;
                update(ContentRect);
            }
            else
            {
                if (icon_list->GetCurrentItem() > 0)
                    icon_list->SetItemCurrent(icon_list->GetCurrentItem()-1);
                clicked();
                setHelpText();
                update(SelectRect);
            }

        }
        else if (action == "DOWN")
        {
            if (inContent)
            {
                if (contentPos < (contentTotalLines - 1))
                    contentPos++;
                update(ContentRect);
            }
            else
            {
                if (icon_list->GetCurrentItem() < (max_icons - 1))
                    icon_list->SetItemCurrent(icon_list->GetCurrentItem()+1);
                clicked();
                setHelpText();
                update(SelectRect);
            }
        }
        else if (action == "PAGEUP" && inContent) 
        {
            contentPos -= contentSize;
            if (contentPos < 0)
                contentPos = 0;
            update(ContentRect);
        }
        else if (action == "PAGEDOWN" && inContent)
        {
            contentPos += contentSize;
            if (contentPos > (contentTotalLines - 1))
                contentPos = contentTotalLines - 1;
            update(ContentRect);
        }
        else if ((action == "RIGHT") &&
                 (!inContent) &&
                 ((contentTotalLines > contentSize) ||
                  (doScroll)))
        {
            clicked();
            inContent = true;
            contentPos = 0;
            icon_list->SetActive(false);
            list_area->SetActive(true);
            update(SelectRect);
            update(ContentRect);
        }
        else if (action == "LEFT")
        {
            if (inContent)
            {
                inContent = false;
                contentPos = 0;
                list_area->SetActive(false);
                icon_list->SetActive(true);
                setHelpText();
                update(SelectRect);
                update(ContentRect);
            }
            else
            {
                if (gContext->GetNumSetting("UseArrowAccels", 1))
                    accept();
            }
        }
        else if ((currentItem == QObject::tr("Log Entries")) &&
                 (logNumberKeys.search(action) == 0))
        {
            min_level = action.toInt();
            helptext->SetText(QObject::tr("Setting priority level to %1")
                                          .arg(min_level));
            update(TopRect);
            doLogEntries();
        }
        else
            handled = false;
    }

    if (!handled)
        MythDialog::keyPressEvent(e);
}

void StatusBox::setHelpText()
{
    if (inContent)
    {
        helptext->SetText(contentDetail[contentPos]);
    } else {
        topbar->ClearAllText();
        QString currentItem;

        currentItem = icon_list->GetItemText(icon_list->GetCurrentItem());

        if (currentItem == QObject::tr("Listings Status"))
            helptext->SetText(QObject::tr("Listings Status shows the latest "
                                          "status information from "
                                          "mythfilldatabase"));

        if (currentItem == QObject::tr("Tuner Status"))
            helptext->SetText(QObject::tr("Tuner Status shows the current "
                                          "information about the state of "
                                          "backend tuner cards"));

        if (currentItem == QObject::tr("DVB Status"))
            helptext->SetText(QObject::tr("DVB Status shows the quality "
                                          "statistics of all DVB cards, if "
                                          "present"));

        if (currentItem == QObject::tr("Log Entries"))
            helptext->SetText(QObject::tr("Log Entries shows any unread log "
                                          "entries from the system if you "
                                          "have logging enabled"));
        if (currentItem == QObject::tr("Job Queue"))
            helptext->SetText(QObject::tr("Job Queue shows any jobs currently "
                                          "in Myth's Job Queue such as a "
                                          "commercial flagging job."));
        if (currentItem == QObject::tr("Machine Status"))
        {
            QString machineStr = QObject::tr("Machine Status shows "
                                             "some operating system "
                                             "statistics of this machine");
            if (!isBackend)
                machineStr.append(" " + QObject::tr("and the MythTV server"));

            helptext->SetText(machineStr);
        }

        if (currentItem == QObject::tr("AutoExpire List"))
            helptext->SetText(QObject::tr("The AutoExpire List shows all "
                "recordings which may be expired and the order of their "
                "expiration. Recordings at the top of the list will be "
                "expired first."));
    }
    update(TopRect);
}

void StatusBox::clicked()
{
    QString currentItem = icon_list->GetItemText(icon_list->GetCurrentItem());

    if (inContent)
    {
        if (currentItem == QObject::tr("Log Entries"))
        {
            int retval;

            retval = MythPopupBox::show2ButtonPopup(my_parent,
                                   QString("AckLogEntry"),
                                   QObject::tr("Acknowledge this log entry?"),
                                   QObject::tr("Yes"), QObject::tr("No"), 0);
            if (retval == 0)
            {
                MSqlQuery query(MSqlQuery::InitCon());
                query.prepare("UPDATE mythlog SET acknowledged = 1 "
                              "WHERE logid = :LOGID ;");
                query.bindValue(":LOGID", contentData[contentPos]);
                query.exec();
                doLogEntries();
            }
        }
        else if (currentItem == QObject::tr("Job Queue"))
        {
            QStringList msgs;
            int jobStatus;
            int retval;

            jobStatus = JobQueue::GetJobStatus(
                                contentData[contentPos].toInt());

            if (jobStatus == JOB_QUEUED)
            {
                retval = MythPopupBox::show2ButtonPopup(my_parent,
                                       QString("JobQueuePopup"),
                                       QObject::tr("Delete Job?"),
                                       QObject::tr("Yes"),
                                       QObject::tr("No"), 1);
                if (retval == 0)
                {
                    JobQueue::DeleteJob(contentData[contentPos].toInt());
                    doJobQueueStatus();
                }
            }
            else if ((jobStatus == JOB_PENDING) ||
                     (jobStatus == JOB_STARTING) ||
                     (jobStatus == JOB_RUNNING))
            {
                msgs << QObject::tr("Pause");
                msgs << QObject::tr("Stop");
                msgs << QObject::tr("No Change");
                retval = MythPopupBox::showButtonPopup(my_parent,
                                       QString("JobQueuePopup"),
                                       QObject::tr("Job Queue Actions:"),
                                       msgs, 2);
                if (retval == 0)
                {
                    JobQueue::PauseJob(contentData[contentPos].toInt());
                    doJobQueueStatus();
                }
                else if (retval == 1)
                {
                    JobQueue::StopJob(contentData[contentPos].toInt());
                    doJobQueueStatus();
                }
            }
            else if (jobStatus == JOB_PAUSED)
            {
                msgs << QObject::tr("Resume");
                msgs << QObject::tr("Stop");
                msgs << QObject::tr("No Change");
                retval = MythPopupBox::showButtonPopup(my_parent,
                                       QString("JobQueuePopup"),
                                       QObject::tr("Job Queue Actions:"),
                                       msgs, 2);
                if (retval == 0)
                {
                    JobQueue::ResumeJob(contentData[contentPos].toInt());
                    doJobQueueStatus();
                }
                else if (retval == 1)
                {
                    JobQueue::StopJob(contentData[contentPos].toInt());
                    doJobQueueStatus();
                }
            }
            else if (jobStatus & JOB_DONE)
            {
                retval = MythPopupBox::show2ButtonPopup(my_parent,
                                       QString("JobQueuePopup"),
                                       QObject::tr("Requeue Job?"),
                                       QObject::tr("Yes"),
                                       QObject::tr("No"), 1);
                if (retval == 0)
                {
                    JobQueue::ChangeJobStatus(contentData[contentPos].toInt(),
                                              JOB_QUEUED);
                    doJobQueueStatus();
                }
            }
        }
        else if (currentItem == QObject::tr("AutoExpire List"))
        {
            ProgramInfo* rec;

            rec = expList[contentPos];

            if (rec) 
            {
                QStringList msgs;
                int retval;

                msgs << QObject::tr("Delete Now");
                msgs << QObject::tr("Disable AutoExpire");
                msgs << QObject::tr("No Change");
                
                retval = MythPopupBox::showButtonPopup(my_parent,
                             QString("AutoExpirePopup"),
                             QObject::tr("AutoExpire Actions:"),
                             msgs, 2);

                if (retval == 0 && REC_CAN_BE_DELETED(rec))
                {
                    RemoteDeleteRecording(rec, false, false);
                }
                else if (retval == 1)
                {
                    rec->SetAutoExpire(0);
                    if ((rec)->recgroup == "LiveTV")
                        rec->ApplyRecordRecGroupChange("Default");
                }

                // Update list, prevent selected item going off bottom
                doAutoExpireList();
                if (contentPos >= (int)expList.size())  
                    contentPos = max((int)expList.size()-1,0);
            }
        }
        return;
    }
    
    // Clear all visible content elements here
    // I'm sure there's a better way to do this but I can't find it
    content->ClearAllText();
    list_area->ResetList();
    contentLines.clear();
    contentDetail.clear();
    contentFont.clear();
    contentData.clear();

    if (currentItem == QObject::tr("Listings Status"))
        doListingsStatus();
    else if (currentItem == QObject::tr("Tuner Status"))
        doTunerStatus();
    else if (currentItem == QObject::tr("Log Entries"))
        doLogEntries();
    else if (currentItem == QObject::tr("Job Queue"))
        doJobQueueStatus();
    else if (currentItem == QObject::tr("Machine Status"))
        doMachineStatus();
    else if (currentItem == QObject::tr("AutoExpire List"))
        doAutoExpireList();
}

void StatusBox::doListingsStatus()
{
    QString mfdLastRunStart, mfdLastRunEnd, mfdLastRunStatus, mfdNextRunStart;
    QString querytext, Status, DataDirectMessage;
    int DaysOfData;
    QDateTime qdtNow, GuideDataThrough;
    int count = 0;

    contentLines.clear();
    contentDetail.clear();
    contentFont.clear();
    doScroll = false;

    qdtNow = QDateTime::currentDateTime();

    MSqlQuery query(MSqlQuery::InitCon());
    query.prepare("SELECT max(endtime) FROM program WHERE manualid=0;");
    query.exec();

    if (query.isActive() && query.size())
    {
        query.next();
        GuideDataThrough = QDateTime::fromString(query.value(0).toString(),
                                                 Qt::ISODate);
    }

    mfdLastRunStart = gContext->GetSetting("mythfilldatabaseLastRunStart");
    mfdLastRunEnd = gContext->GetSetting("mythfilldatabaseLastRunEnd");
    mfdLastRunStatus = gContext->GetSetting("mythfilldatabaseLastRunStatus");
    mfdNextRunStart = gContext->GetSetting("MythFillSuggestedRunTime");
    DataDirectMessage = gContext->GetSetting("DataDirectMessage");

    mfdNextRunStart.replace("T", " ");

    extern const char *myth_source_version;
    contentLines[count++] = QObject::tr("Myth version:") + " " +
                                        MYTH_BINARY_VERSION + "   " +
                                        myth_source_version;
    contentLines[count++] = QObject::tr("Last mythfilldatabase guide update:");
    contentLines[count++] = QObject::tr("Started:   ") + mfdLastRunStart;

    if (mfdLastRunEnd >= mfdLastRunStart) //if end < start, it's still running.
        contentLines[count++] = QObject::tr("Finished: ") + mfdLastRunEnd;

    contentLines[count++] = QObject::tr("Result: ") + mfdLastRunStatus;


    if (mfdNextRunStart >= mfdLastRunStart) 
        contentLines[count++] = QObject::tr("Suggested Next: ") + 
                                mfdNextRunStart;

    DaysOfData = qdtNow.daysTo(GuideDataThrough);

    if (GuideDataThrough.isNull())
    {
        contentLines[count++] = "";
        contentLines[count++] = QObject::tr("There's no guide data available!");
        contentLines[count++] = QObject::tr("Have you run mythfilldatabase?");
    }
    else
    {
        contentLines[count++] = QObject::tr("There is guide data until ") + 
                                QDateTime(GuideDataThrough)
                                          .toString("yyyy-MM-dd hh:mm");

        if (DaysOfData > 0)
        {
            Status = QString("(%1 ").arg(DaysOfData);
            if (DaysOfData >1)
                Status += QObject::tr("days");
            else
                Status += QObject::tr("day");
            Status += ").";
            contentLines[count++] = Status;
        }
    }

    if (DaysOfData <= 3)
    {
        contentLines[count++] = QObject::tr("WARNING: is mythfilldatabase "
                                            "running?");
    }

    if (!DataDirectMessage.isNull())
    {
        contentLines[count++] = QObject::tr("DataDirect Status: "); 
        contentLines[count++] = DataDirectMessage;
    }
   
    contentTotalLines = count;
    update(ContentRect);
}

void StatusBox::doTunerStatus()
{
    doScroll = true;
    contentLines.clear();
    contentDetail.clear();
    contentFont.clear();

    MSqlQuery query(MSqlQuery::InitCon());
    query.prepare(
        "SELECT cardid, cardtype, videodevice "
        "FROM capturecard WHERE parentid='0' ORDER BY cardid");

    if (!query.exec() || !query.isActive())
    {
        MythContext::DBError("StatusBox::doTunerStatus()", query);
        contentTotalLines = 0;
        update(ContentRect);
        return;
    }

    uint count = 0;
    while (query.next())
    {
        int cardid = query.value(0).toInt();

        QString cmd = QString("QUERY_REMOTEENCODER %1").arg(cardid);
        QStringList strlist = cmd;
        strlist << "GET_STATE";

        gContext->SendReceiveStringList(strlist);
        int state = strlist[0].toInt();
  
        QString status = "";
        if (state == kState_Error)
            status = tr("is unavailable");
        else if (state == kState_WatchingLiveTV)
            status = tr("is watching live TV");
        else if (state == kState_RecordingOnly ||
                 state == kState_WatchingRecording)
            status = tr("is recording");
        else 
            status = tr("is not recording");

        QString tun = tr("Tuner %1 ").arg(cardid);
        QString devlabel = CardUtil::GetDeviceLabel(
            cardid, query.value(1).toString(), query.value(2).toString());

        contentLines[count]  = tun + status;
        contentDetail[count] = tun + devlabel + " " + status;

        if (state == kState_RecordingOnly ||
            state == kState_WatchingRecording)
        {
            strlist = QString("QUERY_RECORDER %1").arg(cardid);
            strlist << "GET_RECORDING";
            gContext->SendReceiveStringList(strlist);
            ProgramInfo *proginfo = new ProgramInfo;
            proginfo->FromStringList(strlist, 0);
   
            status += " " + proginfo->title;
            status += "\n";
            status += proginfo->subtitle;
            contentDetail[count] = tun + devlabel + " " + status;
        }
        count++;
    }
    contentTotalLines = count;
    update(ContentRect);
}

void StatusBox::doLogEntries(void)
{
    QString line;
    int count = 0;
 
    doScroll = true;

    contentLines.clear();
    contentDetail.clear();
    contentFont.clear();
    contentData.clear();

    MSqlQuery query(MSqlQuery::InitCon());
    query.prepare("SELECT logid, module, priority, logdate, host, "
                  "message, details "
                  "FROM mythlog WHERE acknowledged = 0 "
                  "AND priority <= :PRIORITY ORDER BY logdate DESC;");
    query.bindValue(":PRIORITY", min_level);
    query.exec();

    if (query.isActive())
    {
        while (query.next())
        {
            line = QString("%1").arg(query.value(5).toString());
            contentLines[count] = line;

            if (query.value(6).toString() != "")
                line = tr("On %1 %2 from %3.%4\n%5\n%6")
                               .arg(query.value(3).toDateTime()
                                         .toString(dateFormat))
                               .arg(query.value(3).toDateTime()
                                         .toString(timeFormat))
                               .arg(query.value(4).toString())
                               .arg(query.value(1).toString())
                               .arg(query.value(5).toString())
                               .arg(QString::fromUtf8(query.value(6).toString()));
            else
                line = tr("On %1 %2 from %3.%4\n%5\nNo other details")
                               .arg(query.value(3).toDateTime()
                                         .toString(dateFormat))
                               .arg(query.value(3).toDateTime()
                                         .toString(timeFormat))
                               .arg(query.value(4).toString())
                               .arg(query.value(1).toString())
                               .arg(query.value(5).toString());
            contentDetail[count] = line;
            contentData[count++] = query.value(0).toString();
        }
    }

    if (!count)
    {
        doScroll = false;
        contentLines[count++] = QObject::tr("No items found at priority "
                                            "level %1 or lower.")
                                            .arg(min_level);
        contentLines[count++] = QObject::tr("Use 1-8 to change priority "
                                            "level.");
    }
      
    contentTotalLines = count;
    if (contentPos > (contentTotalLines - 1))
        contentPos = contentTotalLines - 1;

    update(ContentRect);
}

void StatusBox::doJobQueueStatus()
{
    QMap<int, JobQueueEntry> jobs;
    QMap<int, JobQueueEntry>::Iterator it;
    int count = 0;

    QString detail;

    JobQueue::GetJobsInQueue(jobs,
                             JOB_LIST_NOT_DONE | JOB_LIST_ERROR |
                             JOB_LIST_RECENT);

    doScroll = true;

    contentLines.clear();
    contentDetail.clear();
    contentFont.clear();
    contentData.clear();

    if (jobs.size())
    {
        for (it = jobs.begin(); it != jobs.end(); ++it)
        {
            QString chanid = it.data().chanid;
            QDateTime starttime = it.data().starttime;
            ProgramInfo *pginfo;

            pginfo = ProgramInfo::GetProgramFromRecorded(chanid, starttime);

            if (!pginfo)
                continue;

            detail = pginfo->title + "\n" +
                     pginfo->channame + " " + pginfo->chanstr +
                         " @ " + starttime.toString(timeDateFormat) + "\n" +
                     tr("Job:") + " " + JobQueue::JobText(it.data().type) +
                     "     " + tr("Status: ") +
                     JobQueue::StatusText(it.data().status);

            if (it.data().status != JOB_QUEUED)
                detail += " (" + it.data().hostname + ")";

            detail += "\n" + it.data().comment;

            contentLines[count] = pginfo->title + " @ " +
                                  starttime.toString(timeDateFormat);

            contentDetail[count] = detail;
            contentData[count] = QString("%1").arg(it.data().id);

            if (it.data().status == JOB_ERRORED)
                contentFont[count] = "error";
            else if (it.data().status == JOB_ABORTED)
                contentFont[count] = "warning";

            count++;

            delete pginfo;
        }
    }
    else
    {
        contentLines[count++] = QObject::tr("Job Queue is currently empty.");
        doScroll = false;
    }

    contentTotalLines = count;
    update(ContentRect);
}

// Some helper routines for doMachineStatus() that format the output strings

/** \fn sm_str(long long, int)
 *  \brief Returns a short string describing an amount of space, choosing
 *         one of a number of useful units, "TB", "GB", "MB", or "KB".
 *  \param sizeKB Number of kilobytes.
 *  \param prec   Precision to use if we have less than ten of whatever
 *                unit is chosen.
 */
static const QString sm_str(long long sizeKB, int prec=1)
{
    if (sizeKB>1024*1024*1024) // Terabytes
    {
        double sizeGB = sizeKB/(1024*1024*1024.0);
        return QString("%1 TB").arg(sizeGB, 0, 'f', (sizeGB>10)?0:prec);
    }
    else if (sizeKB>1024*1024) // Gigabytes
    {
        double sizeGB = sizeKB/(1024*1024.0);
        return QString("%1 GB").arg(sizeGB, 0, 'f', (sizeGB>10)?0:prec);
    }
    else if (sizeKB>1024) // Megabytes
    {
        double sizeMB = sizeKB/1024.0;
        return QString("%1 MB").arg(sizeMB, 0, 'f', (sizeMB>10)?0:prec);
    }
    // Kilobytes
    return QString("%1 KB").arg(sizeKB);
}

static const QString usage_str_kb(long long total,
                                  long long used,
                                  long long free)
{
    QString ret = QObject::tr("Unknown");
    if (total > 0.0 && free > 0.0)
    {
        double percent = (100.0*free)/total;
        ret = QObject::tr("%1 total, %2 used, %3 (or %4%) free.")
            .arg(sm_str(total)).arg(sm_str(used))
            .arg(sm_str(free)).arg(percent, 0, 'f', (percent >= 10.0) ? 0 : 2);
    }
    return ret;
}

static const QString usage_str_mb(float total, float used, float free)
{
    return usage_str_kb((long long)(total*1024), (long long)(used*1024),
                        (long long)(free*1024));
}

static void disk_usage_with_rec_time_kb(QStringList& out, long long total,
                                        long long used, long long free,
                                        const recprof2bps_t& prof2bps)
{
    const QString tail = QObject::tr(", using your %1 rate of %2 Kb/sec");

    out<<usage_str_kb(total, used, free);
    if (free<0)
        return;

    recprof2bps_t::const_iterator it = prof2bps.begin();
    for (; it != prof2bps.end(); ++it)
    {
        const QString pro =
                tail.arg(it.key()).arg((int)((float)it.data() / 1024.0));
        
        long long bytesPerMin = (it.data() >> 1) * 15;
        uint minLeft = ((free<<5)/bytesPerMin)<<5;
        minLeft = (minLeft/15)*15;
        uint hoursLeft = minLeft/60;
        if (hoursLeft > 3)
            out<<QObject::tr("%1 hours left").arg(hoursLeft) + pro;
        else if (minLeft > 90)
            out<<QObject::tr("%1 hours and %2 minutes left")
                .arg(hoursLeft).arg(minLeft%60) + pro;
        else
            out<<QObject::tr("%1 minutes left").arg(minLeft) + pro;
    }
}

static const QString uptimeStr(time_t uptime)
{
    int     days, hours, min, secs;
    QString str;

    str = QString("   " + QObject::tr("Uptime") + ": ");

    if (uptime == 0)
        return str + "unknown";

    days = uptime/(60*60*24);
    uptime -= days*60*60*24;
    hours = uptime/(60*60);
    uptime -= hours*60*60;
    min  = uptime/60;
    secs = uptime%60;

    if (days > 0)
    {
        char    buff[6];
        QString dayLabel;

        if (days == 1)
            dayLabel = QObject::tr("day");
        else
            dayLabel = QObject::tr("days");

        sprintf(buff, "%d:%02d", hours, min);

        return str + QString("%1 %2, %3").arg(days).arg(dayLabel).arg(buff);
    }
    else
    {
        char  buff[9];

        sprintf(buff, "%d:%02d:%02d", hours, min, secs);

        return str + buff;
    }
}

/** \fn StatusBox::getActualRecordedBPS(QString hostnames)
 *  \brief Fills in recordingProfilesBPS w/ average bitrate from recorded table
 */
void StatusBox::getActualRecordedBPS(QString hostnames)
{
    recordingProfilesBPS.clear();

    QString querystr;
    MSqlQuery query(MSqlQuery::InitCon());

    querystr =
        "SELECT sum(filesize) * 8 / "
            "sum(((unix_timestamp(endtime) - unix_timestamp(starttime)))) "
            "AS avg_bitrate "
        "FROM recorded WHERE hostname in (%1) "
            "AND (unix_timestamp(endtime) - unix_timestamp(starttime)) > 300;";

    query.prepare(querystr.arg(hostnames));

    if (query.exec() && query.isActive() && query.size() > 0 && query.next() &&
        query.value(0).toDouble() > 0)
    {
        recordingProfilesBPS[QObject::tr("average")] =
            (int)(query.value(0).toDouble());
    }

    querystr =
        "SELECT max(filesize * 8 / "
            "(unix_timestamp(endtime) - unix_timestamp(starttime))) "
            "AS max_bitrate "
        "FROM recorded WHERE hostname in (%1) "
            "AND (unix_timestamp(endtime) - unix_timestamp(starttime)) > 300;";

    query.prepare(querystr.arg(hostnames));

    if (query.exec() && query.isActive() && query.size() > 0 && query.next() &&
        query.value(0).toDouble() > 0)
    {
        recordingProfilesBPS[QObject::tr("maximum")] =
            (int)(query.value(0).toDouble());
    }
}

/** \fn StatusBox::doMachineStatus()
 *  \brief Show machine status.
 *  
 *   This returns statisics for master backend when using
 *   a frontend only machine. And returns info on the current
 *   system if frontend is running on a backend machine.
 *  \bug We should report on all backends and the current frontend.
 */
void StatusBox::doMachineStatus()
{
    int           count(0);
    int           totalM, usedM, freeM;    // Physical memory
    int           totalS, usedS, freeS;    // Virtual  memory (swap)
    time_t        uptime;
    int           detailBegin;
    QString       detailString;
    int           detailLoop;

    contentLines.clear();
    contentDetail.clear();
    contentFont.clear();
    doScroll = true;

    detailBegin = count;
    detailString = "";

    if (isBackend)
        contentLines[count] = QObject::tr("System") + ":";
    else
        contentLines[count] = QObject::tr("This machine") + ":";
    detailString += contentLines[count] + "\n";
    count++;

    // uptime
    if (!getUptime(uptime))
        uptime = 0;
    contentLines[count] = uptimeStr(uptime);

    // weighted average loads
    contentLines[count].append(".   " + QObject::tr("Load") + ": ");

    double loads[3];
    if (getloadavg(loads,3) == -1)
        contentLines[count].append(QObject::tr("unknown") +
                                   " - getloadavg() " + QObject::tr("failed"));
    else
    {
        char buff[30];

        sprintf(buff, "%0.2lf, %0.2lf, %0.2lf", loads[0], loads[1], loads[2]);
        contentLines[count].append(QString(buff));
    }
    detailString += contentLines[count] + "\n";
    count++;


    // memory usage
    if (getMemStats(totalM, freeM, totalS, freeS))
    {
        usedM = totalM - freeM;
        if (totalM > 0)
        {
            contentLines[count] = "   " + QObject::tr("RAM") +
                                  ": "  + usage_str_mb(totalM, usedM, freeM);
            detailString += contentLines[count] + "\n";
            count++;
        }
        usedS = totalS - freeS;
        if (totalS > 0)
        {
            contentLines[count] = "   " + QObject::tr("Swap") +
                                  ": "  + usage_str_mb(totalS, usedS, freeS);
            detailString += contentLines[count] + "\n";
            count++;
        }
    }

    for (detailLoop = detailBegin; detailLoop < count; detailLoop++)
        contentDetail[detailLoop] = detailString;

    detailBegin = count;
    detailString = "";

    if (!isBackend)
    {
        contentLines[count] = QObject::tr("MythTV server") + ":";
        detailString += contentLines[count] + "\n";
        count++;

        // uptime
        if (!RemoteGetUptime(uptime))
            uptime = 0;
        contentLines[count] = uptimeStr(uptime);

        // weighted average loads
        contentLines[count].append(".   " + QObject::tr("Load") + ": ");
        float loads[3];
        if (RemoteGetLoad(loads))
        {
            char buff[30];

            sprintf(buff, "%0.2f, %0.2f, %0.2f", loads[0], loads[1], loads[2]);
            contentLines[count].append(QString(buff));
        }
        else
            contentLines[count].append(QObject::tr("unknown"));

        detailString += contentLines[count] + "\n";
        count++;

        // memory usage
        if (RemoteGetMemStats(totalM, freeM, totalS, freeS))
        {
            usedM = totalM - freeM;
            if (totalM > 0)
            {
                contentLines[count] = "   " + QObject::tr("RAM") +
                                      ": "  + usage_str_mb(totalM, usedM, freeM);
                detailString += contentLines[count] + "\n";
                count++;
            }

            usedS = totalS - freeS;
            if (totalS > 0)
            {
                contentLines[count] = "   " + QObject::tr("Swap") +
                                      ": "  + usage_str_mb(totalS, usedS, freeS);
                detailString += contentLines[count] + "\n";
                count++;
            }
        }
    }

    for (detailLoop = detailBegin; detailLoop < count; detailLoop++)
        contentDetail[detailLoop] = detailString;

    detailBegin = count;
    detailString = "";

    // get free disk space
    QString hostnames;
    
    vector<FileSystemInfo> fsInfos = RemoteGetFreeSpace();
    for (uint i=0; i<fsInfos.size(); i++)
    {
        hostnames = QString("\"%1\"").arg(fsInfos[i].hostname);
        hostnames.replace(QRegExp(" "), "");
        hostnames.replace(QRegExp(","), "\",\"");

        getActualRecordedBPS(hostnames);

        QStringList list;
        disk_usage_with_rec_time_kb(list,
            fsInfos[i].totalSpaceKB, fsInfos[i].usedSpaceKB,
            fsInfos[i].totalSpaceKB - fsInfos[i].usedSpaceKB,
            recordingProfilesBPS);

        contentLines[count] = 
            QObject::tr("Disk usage on %1:").arg(fsInfos[i].hostname);
        detailString += contentLines[count] + "\n";
        count++;

        QStringList::iterator it = list.begin();
        for (;it != list.end(); ++it)
        {
            contentLines[count] =  QString("   ") + (*it);
            detailString += contentLines[count] + "\n";
            count++;
        }

        for (detailLoop = detailBegin; detailLoop < count; detailLoop++)
            contentDetail[detailLoop] = detailString;

        detailBegin = count;
        detailString = "";
    }

    contentTotalLines = count;
    update(ContentRect);
}

/** \fn StatusBox::doAutoExpireList()
 *  \brief Show list of recordings which may AutoExpire
 */
void StatusBox::doAutoExpireList()
{
    int                   count(0);
    ProgramInfo*          pginfo;
    QString               contentLine;
    QString               detailInfo;
    QString               staticInfo;
    long long             totalSize(0);
    long long             liveTVSize(0);
    int                   liveTVCount(0);

    contentLines.clear();
    contentDetail.clear();
    contentFont.clear();
    doScroll = true;

    vector<ProgramInfo *>::iterator it;
    for (it = expList.begin(); it != expList.end(); it++)
        delete *it;
    expList.clear();

    RemoteGetAllExpiringRecordings(expList);

    for (it = expList.begin(); it != expList.end(); it++)
    {
        pginfo = *it;
       
        totalSize += pginfo->filesize;
        if (pginfo->recgroup == "LiveTV")
        {
            liveTVSize += pginfo->filesize;
            liveTVCount++;
        }
    }

    staticInfo = tr("%1 recordings consuming %2 are allowed to expire")
                    .arg(expList.size()).arg(sm_str(totalSize / 1024)) + "\n";

    if (liveTVCount)
        staticInfo += tr("%1 of these are LiveTV and consume %2")
                        .arg(liveTVCount).arg(sm_str(liveTVSize / 1024)) + "\n";
    else
        staticInfo += "\n";

    for (it = expList.begin(); it != expList.end(); it++)
    {
        pginfo = *it;
        contentLine = pginfo->recstartts.toString(dateFormat) + " - ";

        if (pginfo->recgroup == "LiveTV")
            contentLine += "(" + tr("LiveTV") + ") ";

        contentLine += pginfo->title +
                       " (" + sm_str(pginfo->filesize / 1024) + ")";

        detailInfo = staticInfo;
        detailInfo += pginfo->recstartts.toString(timeDateFormat) + " - " +
                      pginfo->recendts.toString(timeDateFormat);

        detailInfo += " (" + sm_str(pginfo->filesize / 1024) + ")";

        if (pginfo->recgroup == "LiveTV")
            detailInfo += " (" + tr("LiveTV") + ")";

        detailInfo += "\n" + pginfo->title;

        if (pginfo->subtitle != "")
            detailInfo += " - " + pginfo->subtitle + "";

        contentLines[count] = contentLine;
        contentDetail[count] = detailInfo;
        count++;
    }

    contentTotalLines = count;
    update(ContentRect);
}

/* vim: set expandtab tabstop=4 shiftwidth=4: */