/*
 * 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 = NULL;
cMutex *cBurnMenuBurn::mMutex = NULL;
cMutex cBurnMenuBurn::mProtect;

cBurnMenuBurn::cBurnMenuBurn(void):
		cOsdMenu(tr("Write DVDs"), /*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();
	if (VDRRecordings.Load()) {
		for (cRecording *rec = VDRRecordings.First(); rec; 
				rec = VDRRecordings.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() {
	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) {
		cCondVar wait;
		cMutex mutex;
		mutex.Lock();
		free(mPrompt);
		mConfirmResult = false;
		mPrompt = Prompt ? strdup(Prompt) : NULL;
		mWait = &wait;
		mMutex = &mutex;
		wait.Wait(mutex);
		mutex.Unlock();
		return mConfirmResult;
	} else
		return Interface->Confirm(Prompt, 100);
}

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() > 3) {
		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 (DVDdev != NULL && ISOdir != NULL)
			Add(new cBurnMenuEditStraItem(tr("Target:"), (int*)&Pending->mStoreMode, __STORE_COUNT__, StoreModes));
		Add(new cBurnMenuTextItem(""));
                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) {
			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 (mBurnState == bsStatus) {
		if (current && current->Type() == bitJob) {
			if (ActiveJobs.Count() > 0
					&& ((cBurnMenuJobItem*)current)->Job() == ActiveJobs.First())
				Yellow = tr("Cancel");
			else {
				Yellow = tr("Remove");
				Green = tr("Repeat");
			}
		}
	}
	Blue = tr(BurnStates[(mBurnState + 1) % __STATE_COUNT__]);

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

void cBurnMenuBurn::Remove(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;
			for (job = ActiveJobs.First(); job; job = ActiveJobs.Next(job)) {
				if (job == item->Job()) {
					if (job == ActiveJobs.First()) {
						if (Interface->Confirm(tr("Job still active - really cancel?")))
							job->Stop();
						break;
					}
					ActiveJobs.Del(job);
					break;
				}
			}
			if (job == NULL) {
				for (job = ErrorJobs.First(); job; job = ErrorJobs.Next(job)) {
					if (job == item->Job()) {
						ErrorJobs.Del(job);
						break;
					}
				}
			}
			if (job == NULL) {
				for (job = FinishedJobs.First(); job; job = FinishedJobs.Next(job)) {
					if (job == item->Job()) {
						FinishedJobs.Del(job);
						break;
					}
				}
			}
			Set();
		}
	}
}

void cBurnMenuBurn::Repeat(void) {
	if (Pending != NULL && mBurnState == bsRecordings) {
		return;
	} else if (mBurnState == bsStatus) {
		cBurnMenuJobItem *item = (cBurnMenuJobItem*)Get(Current());
		if (item && Interface->Confirm(tr("Repeat job?"))) {
			cBurnJob *job;
			for (job = ErrorJobs.First(); job; job = ErrorJobs.Next(job)) {
				if (job == item->Job()) {
					ErrorJobs.Del(job, false);
					ActiveJobs.Add(job);
					if (ActiveJobs.Count() == 1)
						job->Start();
					break;
				}
			}
			if (job == NULL) {
				for (job = FinishedJobs.First(); job; job = FinishedJobs.Next(job)) {
					if (job == item->Job()) {
						FinishedJobs.Del(job, false);
						ActiveJobs.Add(job);
						if (ActiveJobs.Count() == 1)
							job->Start();
						break;
					}
				}
			}
			Set();
		}
	}
}

void cBurnMenuBurn::Enqueue(void) {

	double sizeMB;

	sizeMB = Pending->SizeMB() * 3.f;
	if (sizeMB >= FreeDiskSpaceMB(Datadir)) {
		ERROR(tr("Not enough free disk space!"));
		FLUSH();
		return;
	}
	
	ActiveJobs.Add(Pending);
	if (ActiveJobs.Count() == 1)
		Pending->Start();
	Pending = NULL;
	mBurnState = bsStatus;
	Set();
}

eOSState cBurnMenuBurn::ProcessKey(eKeys Key) {
	eOSState state = cOsdMenu::ProcessKey(Key);
	cBurnMenuItem *current = (cBurnMenuItem*)Get(Current());
	Dprintf("key %d, state %d\n", Key, state);
	if ( Key != kNone && Key != kMenu && mPrompt && mWait) {
		mMutex->Lock();
		mConfirmResult = (Key==kOk);;
		mWait->Broadcast();
		free(mPrompt); mPrompt = NULL;
		mWait = NULL;
		mMutex->Unlock();
		mMutex = NULL;
		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) {
						Enqueue();
					}
					break;
				case kGreen:
					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 (current->Type() == bitEditRecording)
						Remove();
					break;
				case kBlue:
					mBurnState = (eBurnState)((mBurnState + 1) % __STATE_COUNT__);
					Set();
					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 kRed:
			if (mBurnState == bsRecordings && Pending != NULL) {
				Enqueue();
			}
			break;
		case kGreen:
			if (current->Type() == bitJob)
				Repeat();
			break;
		case kYellow:
			if (current->Type() == bitJob)
				Remove();
			break;
		case kBlue:
			mBurnState = (eBurnState)((mBurnState + 1) % __STATE_COUNT__);
			Set();
			break;
		default:
			break;
		}
		break;

	default:
		break;
	}
	return state;
}

