
/*
 * See the files COPYING and README for copyright information and how to reach 
 * the author.
 *
 *  $Id: menuburn.c,v 1.5 2004/12/05 18:50:34 lordjaxom Exp $
 */

#include "common.h"
#include "process.h"
#include "menuburn.h"
#include "menuitems.h"
#include "i18n.h"
#include "setup.h"
#include <vdr/interface.h>
#include <vdr/videodir.h>

extern int DVDSIZE;

cRecordings cBurnMenuBurn::VDRRecordings;
cBurnJobQueue cBurnMenuBurn::ActiveJobs;
cBurnJobQueue cBurnMenuBurn::ErrorJobs;
cBurnJobQueue cBurnMenuBurn::FinishedJobs;
cBurnJob *cBurnMenuBurn::Pending = NULL;

bool cBurnMenuBurn::mConfirmResult = false;
bool cBurnMenuBurn::mOpen = false;
char *cBurnMenuBurn::mPrompt = NULL;
cCondVar cBurnMenuBurn::mWait;
cMutex cBurnMenuBurn::mMutex;
cMutex cBurnMenuBurn::mProtect;

cBurnMenuBurn::cBurnMenuBurn(void):
    cOsdMenu(tr("Write DVDs with ProjectX"), /*6, 10 */ 16)
{
    mOpen = true;
    mDiskType = dtDVD;

    if (Pending != NULL && Pending->Count() == 0) {
        DELETENULL(Pending);
    }

    if (Pending == NULL) {
        Pending = new cBurnJob;
    }

    STATUS(tr("Scanning recordings..."));
    FLUSH();
    for (cRecording * rec = Recordings.First(); rec;
            rec = Recordings.Next(rec))
    {
        char *path;
        struct stat buf;
        asprintf(&path, "%s/%s", rec->FileName(), BURNMARKFILE);
        if (stat(path, &buf) == 0 && S_ISREG(buf.st_mode) &&
                Pending->GetByFileName(rec->FileName()) == NULL)
        {
            cBurnRecording *brec = new cBurnRecording(rec, buf.st_mtime);
            Pending->Add(brec);
        }
        unlink(path);
        free(path);
    }

    if (Pending->Count() == 0) {
        DELETENULL(Pending);
    }

    if (ActiveJobs.Count() > 0) {
        mBurnState = bsStatus;
    } else {
        mBurnState = bsRecordings;
    }
    Set();
}

cBurnMenuBurn::~cBurnMenuBurn()
{
    if (IsQueued(Pending)) {
        Pending = NULL;
    }
    mOpen = false;
}

void cBurnMenuBurn::NextJob(void)
{
    //cMutexLock MutexLock(&mProtect);

    // rotate job
    if (ActiveJobs.Count() > 0) {
        cBurnJob *job = ActiveJobs.First();
        ActiveJobs.Del(job, false);
        if (job->Status() == rsError) {
            ErrorJobs.Add(job);
        } else {
            FinishedJobs.Add(job);
        }
    }
    // start following job
    if (ActiveJobs.Count() > 0) {
        ActiveJobs.First()->Start();
    }
}

bool cBurnMenuBurn::Confirm(const char *Prompt)
{
    if (mOpen) {
        mMutex.Lock();
        if (mPrompt) {
            free(mPrompt);
        }
        mPrompt = Prompt ? strdup(Prompt) : strdup("");
        mConfirmResult = false;
        mWait.Wait(mMutex);
        mMutex.Unlock();
        return mConfirmResult;
    } else {
        return Interface->Confirm(Prompt, 20);
    }
}

void cBurnMenuBurn::Cleanup(void)
{
    FinishedJobs.Clear();
    ErrorJobs.Clear();
    if (ActiveJobs.Count() > 0) {
        ActiveJobs.First()->Stop();
    }
    ActiveJobs.Clear();
}

void cBurnMenuBurn::Housekeeping(void)
{
    if (FinishedJobs.Count() + ErrorJobs.Count() > BurnSetup.CleanupLimit) {
        if (FinishedJobs.First()) {
            FinishedJobs.Del(FinishedJobs.First(), true);
        }
        if (ErrorJobs.First()) {
            ErrorJobs.Del(ErrorJobs.First(), true);
        }
    }
}

void cBurnMenuBurn::Set(void)
{
    //cMutexLock MutexLock(&mProtect);

    switch (mBurnState) {
        case bsRecordings:
            DisplayRecordings();
            break;

        case bsStatus:
            DisplayStatus();
            break;

        default:
            break;
    }
    FLUSH();
}

void cBurnMenuBurn::DisplayRecordings(void)
{
    int offs;
    double sizeMB = 0;
    char *temp;
    int old = Current();

    Clear();

    Add(new cBurnMenuTextItem(tr("Recordings marked for burning")));
    Add(new cBurnMenuTextItem(""));

    if (Pending != NULL) {
        mDiskType = Pending->mDiskType;
        for (cBurnRecording * rec = Pending->First(); rec;
                rec = Pending->Next(rec))
        {
            Add(new cBurnMenuEditRecordingItem(rec));
        }
        sizeMB = Pending->SizeMB();
    } else {
        Add(new cBurnMenuTextItem(tr("No recordings marked"), true));
    }

    Add(new cBurnMenuTextItem(""));

    temp = MALLOC(char, WIDTH() + 1);
    offs = 0;
    snprintf(temp + offs, WIDTH() - offs, tr("Total size: %.1f MB"), sizeMB);
    offs += strlen(temp + offs);
    snprintf(temp + offs, WIDTH() - offs, "  %s",
            ProgressBar(sizeMB, DVDSIZE, WIDTH() - offs - 3));
    Add(new cBurnMenuTextItem(temp));

    if (sizeMB >= DVDSIZE) {
        const char *text = tr("Recording(s) would be shrinked!");
        memset(temp, ' ', WIDTH() - strlen(text));
        strcpy(temp + WIDTH() - strlen(text) - 1, text);
        Add(new cBurnMenuTextItem(temp));
    }
    free(temp);

    if (Pending != NULL) {
        Add(new cBurnMenuTextItem(""));
        Add(new cBurnMenuEditStrItem(tr("DVD title:"),
                Pending->mTitle, JOBNAMELEN, TitleChars));
        Add(new cBurnMenuEditStraItem(tr("Disk type:"),
                (int*)&Pending->mDiskType,
                (int)(sizeMB >= DVDSIZE ? __TYPE_COUNT_REQUANT__ :
                        __TYPE_COUNT__), DiskTypes));
        if (Pending->mDiskType != dtVDR) {
            Add(new cBurnMenuEditStraItem(tr("Chapters:"),
                    (int*)&Pending->mChaptersMode,
                    __CHAPTERS_COUNT__, ChaptersModes));
        }
        if (DVDdev != NULL && ISOdir != NULL) {
            Add(new cBurnMenuEditStraItem(tr("Target:"),
                    (int *)&Pending->mStoreMode, __STORE_COUNT__,
                    StoreModes));
        }
        Add(new cBurnMenuTextItem(""));
        if (Pending->mDiskType == dtVDR) {
            if (Pending->HasArchiveMark()) {
                asprintf(&temp, "%s: %04d", tr("Index of archive DVD"),
                        Pending->GetArchiveMark());
            } else {
                asprintf(&temp, "%s: %04d", tr("Index of next archive DVD"),
                        Pending->GetNextArchiveMark());
            }
            Add(new cBurnMenuTextItem(temp));
            free(temp);
        }
    }

    if (old == -1 || Get(old) == NULL ||
            ((cBurnMenuItem*)Get(old))->Selectable() == false)
    {
        old = 2;
    }
    SetCurrent(Get(old));
    SetHelpKeys();
    Display();

    if (mPrompt) {
        STATUS(mPrompt);
    }
}

void cBurnMenuBurn::DisplayStatus(void)
{
    int number;
    int old = Current();

    Clear();

    Add(new cBurnMenuTextItem(tr("Queued jobs")));
    Add(new cBurnMenuTextItem(""));

    number = 1;
    for (cBurnJob *job = ActiveJobs.First(); job; job = ActiveJobs.Next(job))
    {
        Add(new cBurnMenuJobItem(job, number));
        if (job == ActiveJobs.First()) {
            char *temp;
            int percent;
            if (job->Burning(percent)) {
                asprintf(&temp, tr("Job active (Burning: % 3d%%)"), percent);
                Add(new cBurnMenuTextItem(temp));
                free(temp);
            } else if (job->Verifying(percent)) {
                asprintf(&temp, tr("Job active (Verifying: % 3d%%)"),
                        percent);
                Add(new cBurnMenuTextItem(temp));
                free(temp);
            } else {
                Add(new cBurnMenuTextItem(tr("Job active (Converting)")));
            }
            asprintf(&temp, " %s",
                    ProgressBar(job->Progress(), 100, WIDTH() - 4));
            Add(new cBurnMenuTextItem(temp));
            free(temp);
        }
        ++number;
    }

    if (ActiveJobs.Count() == 0) {
        Add(new cBurnMenuTextItem(tr("No active or waiting jobs present"),
                true));
    }

    if (ErrorJobs.Count() > 0) {
        Add(new cBurnMenuTextItem(""));
        Add(new cBurnMenuTextItem(tr("Erroneous jobs")));
        Add(new cBurnMenuTextItem(""));

        number = 1;
        for (cBurnJob * job = ErrorJobs.First(); job;
                job = ErrorJobs.Next(job))
        {
            Add(new cBurnMenuJobItem(job, number, true));
            ++number;
        }
    }

    if (FinishedJobs.Count() > 0) {
        Add(new cBurnMenuTextItem(""));
        Add(new cBurnMenuTextItem(tr("Finished jobs")));
        Add(new cBurnMenuTextItem(""));

        number = 1;
        for (cBurnJob * job = FinishedJobs.First(); job;
                job = FinishedJobs.Next(job))
        {
            Add(new cBurnMenuJobItem(job, number, false));
            ++number;
        }
    }

    if (old == -1 || Get(old) == NULL ||
            ((cBurnMenuItem*)Get(old))->Selectable() == false)
    {
        old = 2;
    }
    SetCurrent(Get(old));
    SetHelpKeys();
    Display();

    if (mPrompt) {
        STATUS(mPrompt);
    }
}

void cBurnMenuBurn::SetHelpKeys(void)
{
    const char *Red = NULL, *Green = NULL, *Yellow = NULL, *Blue = NULL;
    cBurnMenuItem *current = (cBurnMenuItem*)Get(Current());

    if (mBurnState == bsRecordings) {
        if (Pending != NULL) {
            if (!Pending->IsProcessed()) {
                Red = tr("Burn");
                if (current && current->Type() == bitEditRecording) {
                    cBurnRecording *rec = Pending->GetByFileName(
                            ((cBurnMenuEditRecordingItem*)current)->FileName());
                    if (rec != Pending->Last()) {
                        Green = tr("Move down");
                    }
                    Yellow = tr("Remove");
                }
            } else {
                if (Pending != ActiveJobs.First()) {
                    Red = tr("Repeat");
                }
            }
        }
    } else if (mBurnState == bsStatus) {
        if (current && current->Type() == bitJob) {
            cBurnJob* job=((cBurnMenuJobItem*)current)->Job();
            if (ActiveJobs.Count() > 0 && job == ActiveJobs.First()) {
                Yellow = tr("Cancel");
            } else {
                Yellow = tr("Remove");
            }
            if (!IsQueued(job, &ActiveJobs)) {
                Red = tr("Repeat");
            }
        }
    }
    Blue = tr(BurnStates[(mBurnState + 1) % __STATE_COUNT__]);

    SetHelp(Red, Green, Yellow, Blue);
}

void cBurnMenuBurn::RemoveCurrent(void)
{
    //cMutexLock MutexLock(&mProtect);

    if (Pending != NULL && mBurnState == bsRecordings) {
        cBurnMenuEditRecordingItem *rec =
                (cBurnMenuEditRecordingItem*)Get(Current());
        if (rec && Interface->Confirm(tr("Remove recording from list?"))) {
            char *path;
            asprintf(&path, "%s/%s", rec->FileName(), BURNMARKFILE);
            if (unlink(path) != 0) {
                esyslog("BURN: Couldn't delete %s: %m (This shouldn't happen!)",
                        path);
            }
            Pending->Del(Pending->GetByFileName(rec->FileName()));
            Set();
        }
    } else if (mBurnState == bsStatus) {
        cBurnMenuJobItem *item = (cBurnMenuJobItem*)Get(Current());
        if (item && Interface->Confirm(tr("Remove job from list?"))) {
            cBurnJob *job = item->Job();
            if (job == ActiveJobs.First()) {
                if (Interface->Confirm(tr("Job still active - really cancel?")))
                {
                    job->Stop();
                }
            } else {
                if (DeleteFromQueue(job, &ActiveJobs)) {
                    delete job;
                } else if (DeleteFromQueue(job, &ErrorJobs)) {
                    delete job;
                } else if (DeleteFromQueue(job, &FinishedJobs)) {
                    delete job;
                }
            }
            Set();
        }
    }
}

void cBurnMenuBurn::SetCurrentPending(void)
{
    if (Pending != NULL) {
        return;
    } else if (mBurnState == bsStatus) {
        cBurnMenuJobItem *item = (cBurnMenuJobItem*)Get(Current());
        Pending = item->Job();
        if (Pending != NULL) {
            mBurnState = bsRecordings;
        }
        Set();
    }
}

void cBurnMenuBurn::RepeatCurrent(void)
{
    if (Pending != NULL && mBurnState == bsRecordings) {
        return;
    } else if (mBurnState == bsStatus) {
        cBurnMenuJobItem *item = (cBurnMenuJobItem*)Get(Current());
        if (item && Interface->Confirm(tr("Repeat job?"))) {
            Pending = item->Job();
            EnqueuePending();
            Set();
        }
    }
}

bool cBurnMenuBurn::IsQueued(cBurnJob *Job, cBurnJobQueue *Queue)
{
    if (!Job || !Queue) {
        return false;
    }
    return Queue->Get(Job->Index()) == Job;
}

bool cBurnMenuBurn::IsQueued(cBurnJob *Job)
{
    return IsQueued(Job, &ActiveJobs) ||
            IsQueued(Job, &FinishedJobs) ||
            IsQueued(Job, &ErrorJobs);
}

cBurnJob* cBurnMenuBurn::DeleteFromQueue(cBurnJob *Job, cBurnJobQueue *Queue)
{
    if (IsQueued(Job, Queue)) {
        Queue->Del(Job, false);
        return Job;
    }
    return NULL;
}

void cBurnMenuBurn::EnqueuePending()
{

    if (!Pending) {
        esyslog("BURN: Nothing to enqueue: must not happen.");
        return;
    }
    if (Pending == ActiveJobs.First()) {
        esyslog("BURN: Job already in work: skip to enqueue it.");
        return;
    }

    double sizeMB;

    sizeMB = Pending->SizeMB() * 3.f;
    if (sizeMB >= FreeDiskSpaceMB(Datadir)) {
        ERROR(tr("Not enough free disk space!"));
        FLUSH();
        return;
    }

    DeleteFromQueue(Pending, &ActiveJobs);
    DeleteFromQueue(Pending, &FinishedJobs);
    DeleteFromQueue(Pending, &ErrorJobs);

    ActiveJobs.Add(Pending);
    if (ActiveJobs.Count() == 1) {
        Pending->Start();
    }
    Pending = NULL;
    mBurnState = bsStatus;
    Set();
}

void cBurnMenuBurn::ToggleDisplay()
{
    if (IsQueued(Pending)) {
        Pending = NULL;
    }
    mBurnState = (eBurnState)((mBurnState + 1) % __STATE_COUNT__);
    Set();
}

eOSState cBurnMenuBurn::ProcessKey(eKeys Key)
{
    cBurnMenuItem *current = (cBurnMenuItem*)Get(Current());
    if (current->Type() >= bitEditable) {
        if (Pending && Pending->IsProcessed()) {
            ((cBurnMenuEditItem*)current)->SetReadOnly(true);
        } else {
            ((cBurnMenuEditItem*)current)->SetReadOnly(false);
        }
    }
    eOSState state = cOsdMenu::ProcessKey(Key);

    current = (cBurnMenuItem*)Get(Current());
    Dprintf("key %d, state %d\n", Key, state);
    if (Key != kNone && Key != kMenu && mPrompt) {
        mMutex.Lock();
        mConfirmResult = (Key == kOk);
        if (mPrompt) {
            free(mPrompt);
            mPrompt = NULL;
        }
        mWait.Broadcast();
        mMutex.Unlock();
        return osContinue;
    }
    switch (state) {
        case osContinue:
            if (current != NULL && (current->Type() == bitEditStr ||
                    current->Type() == bitEditRecording))
            {
                if (!((cBurnMenuEditStrItem*)current)->Editing()) {
                    switch (Key) {
                        case kUp:
                        case kUp | k_Repeat:
                        case kDown:
                        case kDown | k_Repeat:
                        case kOk:
                            SetHelpKeys();
                            break;

                        case kRed:
                            if (Pending != NULL) {
                                EnqueuePending();
                            }
                            break;

                        case kGreen:
                            if (Pending && Pending->IsProcessed()) {
                                break;
                            }
                            if (current->Type() == bitEditRecording) {
                                cBurnRecording *rec = Pending->GetByFileName(
                                        ((cBurnMenuEditRecordingItem*)current)->FileName());
                                if (rec != Pending->Last()) {
                                    Pending->Move(rec, Pending->Next(rec));
                                }
                            }
                            Set();
                            break;

                        case kYellow:
                            if (Pending && Pending->IsProcessed()) {
                                break;
                            }
                            if (current->Type() == bitEditRecording) {
                                RemoveCurrent();
                            }
                            break;

                        case kBlue:
                            ToggleDisplay();
                            break;

                        default:
                            SetHelpKeys();
                            break;
                    }
                }
            } else {
                if ((current->Type() == bitEditStra) &&
                        (mDiskType != Pending->mDiskType))
                {
                    if ((mDiskType == dtVDR) || (Pending->mDiskType == dtVDR)) {
                        Set();
                    }
                }
            }
            break;

        case osUnknown:
            switch (Key) {
                case kNone:
                    if (current == NULL || (current->Type() != bitEditStr &&
                            current->Type() != bitEditRecording))
                    {
                        Set();
                    }
                    // send dummy key to prevent menu timeout
                    if (mBurnState == bsStatus && !mPrompt) {
                        cRemote::Put(kKeepAlive);
                    }
                    break;

                case kOk:
                    if (current->Type() == bitJob) {
                        SetCurrentPending();
                    }
                    break;

                case kRed:
                    if (mBurnState == bsRecordings && Pending != NULL) {
                        EnqueuePending();
                    } else if (current->Type() == bitJob) {
                        RepeatCurrent();
                    }
                    break;

                case kYellow:
                    if (current->Type() == bitJob) {
                        RemoveCurrent();
                    }
                    break;

                case kBlue:
                    ToggleDisplay();
                    break;

                default:
                    break;
            }
            break;

        default:
            break;
    }
    return state;
}

