/*
 * See the files COPYING and README for copyright information and how to reach
 * the author.
 *
 *  $Id: chain-dvd.c,v 1.6 2006/04/24 19:43:37 lordjaxom Exp $
 */

#include "chain-dvd.h"
#include "logger-vdr.h"
#include "jobs.h"
#include "render.h"
#include "config.h"
#include "cdio.h"
#include "menuburn.h"
#include "boost/bind.hpp"
#include "proctools/shellprocess.h"
#include "proctools/functions.h"
#include "proctools/logger.h"
#include "proctools/format.h"
#include "proctools/extract.h"
#include <string>
#include <sstream>
#include <algorithm>

namespace vdr_burn
{
	using namespace std;
	using boost::bind;
	using proctools::shellprocess;
	using proctools::logger;
	using proctools::process;
	using proctools::format;

	chain_dvd::chain_dvd(job& job_):
			chain_vdr("DVD", job_),
			m_step(build),
			m_current(get_job().get_recordings().begin()),
			m_totalSize(0),
			m_totalDone(0),
			m_factor(1),
			m_videoSize(0),
			m_audioSize(0),
			m_lastRecSize(0),
			m_lastProgress(0)
	{
	}

	void chain_dvd::process_line(const string& line)
	{
		static const string stat_vobu("STAT: VOBU ");
		static const string stat_fixing("STAT: fixing VOBU at ");
		static const string burn_done("% done, estimate finish");

		switch (m_step) {
		case build:
			{
				if (line.substr(0, stat_vobu.length()) == stat_vobu) {
					if (!elapsed_since(m_lastProgress, 1))
						return;

					istringstream parser(line.substr(stat_vobu.length()));
					int mbDone;
					parser >> mbDone;
					parser.ignore(4); // " at "
					parser >> mbDone;

					double done = (100.f * m_totalDone
								  + 90.f * m_factor * mbDone * MEGABYTE(1))
								  / m_totalSize / 2.f;

					set_progress(static_cast<int>(done));
				}
				else if (line.substr(0, stat_fixing.length()) == stat_fixing) {
					if (!elapsed_since(m_lastProgress, 1))
						return;

					istringstream parser(line.substr(stat_fixing.length()));
					int mbDone;
					parser >> mbDone;

					double done = (100.f * (m_totalDone - m_lastRecSize)
								  + 90.f * m_lastRecSize
								  + 10.f * m_factor * mbDone * MEGABYTE(1))
								  / m_totalSize / 2.f;

					set_progress(static_cast<int>(done));
				}
			}
			break;

		case burn:
			{
				istringstream parser(line);
				double percentDone;
				parser >> percentDone;
				char rest[burn_done.length() + 1];
				parser.get(rest, burn_done.length() + 1, '\n');
				if (rest == burn_done)
					set_progress(static_cast<int>(50.f + percentDone / 2.f));
			}
			break;
		};
	}

	bool chain_dvd::initialize(void)
	{
		return prepare_job();
	}

	bool chain_dvd::finished(const process* proc)
	{
		logger::debug("process ended: " + proc->name());
		if (proc->return_status() == process::ok) {
			// positive return
			if (proc->name() == "mplex") {
				m_lastRecSize = m_current->get_size();
				m_totalDone += m_lastRecSize;
				++m_current;
				if (m_current != get_job().get_recordings().end())
					prepare_recording();
			}
			else if (proc->name() == "author")
				return prepare_burning();
			return true;
		}

		// process failed
		if (proc->name() == "burn") {
		}

		return false;
	}

	void chain_dvd::create_files(void)
	{
		for_each(get_job().get_recordings().begin(),
				 get_job().get_recordings().end(),
				 bind( &chain_dvd::create_files, this, _1 ));
	}

	void chain_dvd::create_files(const recording& rec)
	{
		// temp folders
		make_dir(rec.get_paths().temp);
		make_dir(rec.get_paths().data);

		// fifos for video track and muxed movie
		make_fifo(rec.get_video_path());
		make_fifo(rec.get_movie_path());
		make_fifo(rec.get_requant_path());

		// fifos for each audio track
		for_each(rec.get_used_audio_tracks().begin(),
				 rec.get_used_audio_tracks().end(),
				 bind( &chain_dvd::make_fifo, this,
				 	   bind( &recording::get_track_path, rec, _1 )
				));

	}

	bool chain_dvd::prepare_job(void)
	{
		create_files();

		// freeze job
		get_job().freeze();

		// cache sizes
		m_videoSize = get_job().get_video_size();
		m_audioSize = get_job().get_audio_size();
		m_totalSize = get_job().get_size();

		// calculate factor
		int64_t diskFree = static_cast<int64_t>(get_job().get_disk_size_mb())
						  * MEGABYTE(1) - m_audioSize;
		if (m_videoSize > diskFree)
			m_factor = (static_cast<double>(m_videoSize) / diskFree) + .12;

		// prepare first recording
		prepare_recording();

		if (get_job().get_disk_type() == disk_type::dvd_menu) {
			// render menu's
			cBurnRender render(get_job());
			execute(render);
			if (render.return_status() != process::ok) {
				logger::error("rendering menu failed - aborting job");
#warning error handling
				return false;
			}
		}

		dvdauthor_xml xml(get_job());
		xml.write();

		// start authoring
		shellprocess* author = new shellprocess("author",
												"vdrburn-dvd.sh author");
		make_dir(xml.get_author_path());
		author->put_environment("DVDAUTHOR_XML",  xml.get_xml_path());
		author->put_environment("DVDAUTHOR_PATH", xml.get_author_path());
		author->put_environment("ISO_FILE",       get_job().get_iso_path());
		author->put_environment("DVD_DEVICE",     BurnParameters.DvdDevice);
		add_process(author);
		return true;
	}

	void chain_dvd::prepare_recording(void)
	{
		string demux_call( "vdrburn-dvd.sh " );
		switch (BurnSetup.DemuxType) {
		case demux_type::vdrsync: demux_call += "demux"; break;
		case demux_type::projectx: demux_call += "demuxpx"; break;
		}
			
		// processes
		shellprocess* demux = new shellprocess("demux", demux_call);
		demux->put_environment("RECORDING_PATH", m_current->get_filename());
		demux->put_environment("IGNORE_TRACKS",  m_current->get_ignored_cids());
		demux->put_environment("USED_TRACKS",    m_current->get_used_cids());
		demux->put_environment("MPEG_TMP_PATH",  m_current->get_paths().temp);
		demux->put_environment("MPEG_DATA_PATH", m_current->get_paths().data);
		if (m_factor > 1)
			demux->put_environment("REQUANT_FACTOR", m_factor);
		add_process(demux);

		/*if (m_factor > 1) {
			shellprocess* requant = new shellprocess("requant", 
													 "vdrburn-dvd.sh requant");
			requant->put_environment("VIDEO_FILE", m_current->get_video_path());
			requant->put_environment("REQUANT_FILE", 
									 m_current->get_requant_path());
			requant->put_environment("REQUANT_FACTOR", m_factor);
			add_process(requant);
		}*/

		shellprocess* mplex = new shellprocess("mplex", "vdrburn-dvd.sh mplex");
		mplex->put_environment("MOVIE_FILE",     m_current->get_movie_path());
		mplex->put_environment("VIDEO_FILE",     m_current->get_video_path());
				//m_factor > 1 ? m_current->get_requant_path()
							 //: m_current->get_video_path());
		mplex->put_environment("AUDIO_FILES",    m_current->get_audio_paths());
		add_process(mplex);
	}

	bool chain_dvd::prepare_device(void)
	{
		if (get_job().get_store_mode() == smCreateISO)
			return true;

		while (true) {
			wrappers::cdio::device cdio(BurnParameters.DvdDevice);
			if (!cdio && errno != ENOMEDIUM) {
				logger::error(format("error while accessing {0}")
							  % BurnParameters.DvdDevice);
				return false;
			}

			switch (cdio.get_media_status()) {
			case wrappers::cdio::writeable:
			case wrappers::cdio::unknown:
				return true;

			case wrappers::cdio::no_dvd:
			case wrappers::cdio::readable:
				if (!cdio.eject()) {
					logger::error(format("error while ejecting {0}")
								  % BurnParameters.DvdDevice);
					return false;
				}

			case wrappers::cdio::error:
				Skins.QueueMessage(mtInfo, tr("Please enter the burn plugin"),
								   10, -1);
				if (!menu::pagebase::wait_for_user(tr("Please insert an empty "
						 						      "disk and press Ok")))
					return false;
				break;

			/*case wrappers::cdio::unknown:
				logger::error(format("unknown condition while accessing {0}")
							  % BurnParameters.DvdDevice);
				return false; XXX Double Layer */
			}
		}
		return true;
	}

	bool chain_dvd::prepare_burning(void)
	{
		m_step = burn;

		if (!prepare_device())
			return false;

		switch (get_job().get_store_mode())
		{
		case smCreateISO:
			{
				shellprocess* burn = new shellprocess("burn",
													  "vdrburn-dvd.sh mkiso");
				burn->put_environment("DVDAUTHOR_PATH",
								  dvdauthor_xml::get_author_path(get_job()));
				burn->put_environment("ISO_FILE", get_job().get_iso_path());
				burn->put_environment("DISC_ID", get_job().get_volume_id());
				add_process(burn);
			}
			break;

		case smBurnISO:
			{
				const char* cmdline;
				if (get_job().get_disk_size() == disk_size::mini_dvd)
					cmdline = "vdrburn-dvd.sh burndircd";
				else
					cmdline = "vdrburn-dvd.sh burndir";

				shellprocess* burn = new shellprocess("burn", cmdline);
				burn->put_environment("DVDAUTHOR_PATH",
								  dvdauthor_xml::get_author_path(get_job()));
				burn->put_environment("DVD_DEVICE", BurnParameters.DvdDevice);
				burn->put_environment("BURN_SPEED", BurnSetup.BurnSpeed);
				burn->put_environment("DISC_ID", get_job().get_volume_id());
				add_process(burn);
			}
			break;

		case smCreateBurn:
			{
				string fifo = format("{0}/burnfifo") % get_paths().temp;
				make_fifo(fifo);

				shellprocess* pipe = new shellprocess("pipe",
													  "vdrburn-dvd.sh pipeiso");
				pipe->put_environment("DVDAUTHOR_PATH",
								  dvdauthor_xml::get_author_path(get_job()));
				pipe->put_environment("ISO_FILE",   get_job().get_iso_path());
				pipe->put_environment("ISO_PIPE",   fifo);
				pipe->put_environment("DISC_ID", get_job().get_volume_id());
				add_process(pipe);

 				char *cmdline;
 				if (get_job().get_disk_size() == disk_size::mini_dvd)
 					cmdline = "vdrburn-dvd.sh burnisocd";
 				else
 					cmdline = "vdrburn-dvd.sh burniso";
 
 				shellprocess* burn = new shellprocess("burn", cmdline);
				burn->put_environment("DVD_DEVICE", BurnParameters.DvdDevice);
				burn->put_environment("ISO_PIPE",   fifo);
				burn->put_environment("BURN_SPEED", BurnSetup.BurnSpeed);
				add_process(burn);
			}
			break;
		}
		return true;
	}
}
