/*
 * See the files COPYING and README for copyright information and how to reach
 * the author.
 *
 *  $Id: jobs.c,v 1.16 2006/05/17 18:20:11 lordjaxom Exp $
 */

#include "jobs.h"
#include "setup.h"
#include "chain-dvd.h"
#include "chain-archive.h"
#include "proctools/format.h"
#include "proctools/functions.h"
#include "proctools/logger.h"
#include "proctools/extract.h"
#include "boost/bind.hpp"
#include <string>
#include <functional>
#include <numeric>
#include <iomanip>
#include <algorithm>
#include <cstdio>
#include <vdr/recording.h>

namespace vdr_burn
{
	using namespace std;
	using boost::bind;
	using proctools::format;
	using proctools::convert;
	using proctools::logger;
	using proctools::sum;

	// --- recording -----------------------------------------------------------

	recording::recording(const cRecording *recording_, bool cut):
			m_fileName(recording_->FileName()),
			m_datetime(get_recording_datetime(recording_, ' ')),
			m_index(-1)
	{
#if VDRVERSNUM < 10300
		m_summary = recording_->Summary() != 0 
				  ? recording_->Summary() : "";
#else
		m_summary = recording_->Info()->Description() != 0
				  ? recording_->Info()->Description() : "";
#endif

		string title = recording_->Name();
		string::size_type pos;
		if (BurnSetup.RemovePath && (pos = title.find('~')) != string::npos)
			title.erase(0, pos + 1);
		strncpy(m_name, title.c_str(), JOBNAMELEN);
		trim_left(m_name, "%@");

		scan_tracks(cut);
	}

	void recording::freeze(void)
	{
		m_usedAudioTracks.clear();
		m_unusedAudioTracks.clear();
		remove_copy_if(m_audioTracks.begin(), m_audioTracks.end(),
					   back_inserter(m_usedAudioTracks),
					   !bind( &track_info::used, _1 ));
		remove_copy_if(m_audioTracks.begin(), m_audioTracks.end(),
					   back_inserter(m_unusedAudioTracks),
					   bind( &track_info::used, _1 ));
	}

	bool recording::scan_tracks(bool cut)
	{
		static const string video_stream("video stream ");
		static const string audio_stream("audio stream ");
		static const string app_size("App. Size (bytes)    ");

		m_videoTracks.clear();
		m_audioTracks.clear();

		ostringstream command;
		command << "vdrsync.pl ";
		if (cut) 
			command << "-cut ";
		command << "-i '" << m_fileName << "/'";
		FILE* pipe = popen(command.str().c_str(), "r");

		if (pipe == NULL) {
			logger::error(format("Error starting sh -c \"{0}\"") % command);
			return false;
		}

		char *buffer = 0;
		int result;
		size_t bufsize = 0;
		track_info_list::iterator track;
		int ac3Tracks = 0;
		int mp2Tracks = 0;
		while ((result = getline(&buffer, &bufsize, pipe)) != -1) {
			string line(buffer);

			if (line.substr(0, video_stream.length()) == video_stream) {
				istringstream parser(line.substr(video_stream.length()));
				int cid;
				parser >> setbase(16) >> cid;

				track = m_videoTracks.insert(m_videoTracks.begin(), 
										track_info(video, cid, "vdrsync.mpv"));

				logger::info(format("Found video stream {0}")
							 % track->cid);
			}
			else if (line.substr(0, audio_stream.length()) == audio_stream) {
				istringstream parser(line.substr(audio_stream.length()));
				int cid;
				parser >> setbase(16) >> cid;
				parser.ignore(6); // " info "
				string type;
				parser >> type;

				track_info data(unknown, cid, "");
				if (type == "(AC3_audio):") {
					data.type = ac3_audio;
					data.filename = "vdrsync.ac3";
					++ac3Tracks; // XXX
				} else {
					data.type = mpeg_audio;
					data.filename = proctools::format("vdrsync{0}.mpa")
								     % mp2Tracks++;
				}
				track = m_audioTracks.insert(m_audioTracks.begin(), data);
			}
			else if (line.substr(0, app_size.length()) == app_size) {
				istringstream parser(line.substr(app_size.length()));
				int64_t size;
				parser >> size;
				track->size = size;
			}
		}
		free(buffer);

		if (!feof(pipe)) {
			logger::error(format("Error running sh -c \"{0}\"") % command);
			pclose(pipe);
			return false;
		}
		pclose(pipe);

		freeze();
		return true;
	}

	void recording::set_paths(const path_pair& paths)
	{

		m_paths = path_pair(format("{0}/VDRSYNC.{1}") % paths.temp % m_index,
							format("{0}/VDRSYNC.{1}") % paths.data % m_index);
	}

	int64_t recording::get_video_size(void) const
	{
		return m_videoTracks[0].size;
	}

	int64_t recording::get_audio_size(void) const
	{
		return sum(m_usedAudioTracks.begin(), m_usedAudioTracks.end(), 
				   int64_t(0),
				   bind( &track_info::size, _1 ));
	}

	string recording::get_audio_paths(void) const
	{
		return join_strings(m_usedAudioTracks.begin(), m_usedAudioTracks.end(),
				bind( &recording::get_track_path, this, _1 ), " ");
	}

	string recording::get_used_cids(void) const
	{
		return join_strings(m_videoTracks.begin(), m_videoTracks.end(),
				bind( int_to_string, bind( &track_info::cid, _1 ), 16, true ), 
				",")
				+ "," + 
				join_strings(m_usedAudioTracks.begin(), m_usedAudioTracks.end(),
				bind( int_to_string, bind( &track_info::cid, _1 ), 16, true ), 
				",");
		// XXX too much knowledge: used_cids, used by ProjectX, must be 
		// standard-parseable, so prefix with 0x
		// additionally, ProjectX needs video here, too
	}

	string recording::get_ignored_cids(void) const
	{
		return join_strings(m_unusedAudioTracks.begin(), 
				m_unusedAudioTracks.end(),
				bind( int_to_string, bind( &track_info::cid, _1 ), 16, false ),
				",");
		// XXX too much knowledge: ignored_cids, used by vdrsync.pl, must be 
		// pure hex, so do not prefix
	}

	string recording::get_chapters(eChaptersMode ChaptersMode)  const
	{
#define MAXCHAPTER (4 * 60 * 60)
		int step = 0;
		switch (ChaptersMode) {
		case cmEditingMarks:
		{
			cMarks marks;
			if (!marks.Load(m_fileName.c_str())) {
				logger::warning("loading marks failed, setting chapters every "
								"ten minutes.");
				return get_chapters(cm10Mins);
			}
			marks.Sort();

			if (marks.Count() % 2 != 0)
				logger::warning("an odd number of marks has been loaded, which "
								"usually leads to undefined behaviour.");

			cMark *mark = marks.GetNext(0); // get 2nd mark
			int count = 0;
			stringstream result("0");
			while (mark != NULL) {
				if (++count % 2 == 0 && marks.GetNext(mark->position) != NULL)
					result << "," << *IndexToHMSF(mark->position, false);
				mark = marks.GetNext(mark->position);
			}
			logger::info("chapter marks generated: " + result.str());
			return result.str();
		}

		case cm5Mins:  step =  5 * 60; break;
		case cm10Mins: step = 10 * 60; break;
		case cm15Mins: step = 15 * 60; break;
		case cm30Mins: step = 30 * 60; break;
		case cm60Mins: step = 60 * 60; break;
		default:       break;
		}

		if (step == 0)
			return "";

		stringstream result;
		for (int i = 0; i <= MAXCHAPTER; i += step) {
			if (i > 0)
				result << ",";
			result << i / 3600 << ":" << (i / 60) % 60 << ":"
				   << i % 60;
		}
		return result.str();
	}

	// --- job ---------------------------------------------------------------

	bool operator==(const recording& lhs, const recording& rhs)
	{
		return lhs.m_index == rhs.m_index;
	}

	bool operator!=(const recording& lhs, const recording& rhs)
	{
		return lhs.m_index != rhs.m_index;
	}

	bool operator<(const recording& lhs, const recording& rhs)
	{
		return lhs.m_index < rhs.m_index;
	}

	job::job(void):
			m_paths(),
			m_skinIndex(0),
			m_diskType(disk_type::dvd_menu),
			m_storeMode(BurnSetup.StoreMode),
			m_chaptersMode(BurnSetup.ChaptersMode),
			m_diskSize(BurnSetup.DiskSize),
			m_customDiskSizeMB(BurnSetup.CustomDiskSize),
			m_cutOnDemux(BurnSetup.CutOnDemux),
			m_process(NULL),
			m_canceled(false)
	{
		m_title[0] = '\0';
		if (BurnParameters.DvdDevice.empty())
			m_storeMode = smCreateISO;
		else if (BurnParameters.IsoPath.empty())
			m_storeMode = smBurnISO;
	}

	job::~job()
	{
		delete m_process;
	}

	void job::freeze(void)
	{
		for_each(m_recordings.begin(), m_recordings.end(),
				 bind( &recording::freeze, _1 ));
	}

	void job::reset(void)
	{
		DELETENULL(m_process);
		m_canceled = false;
	}

	void job::start(void)
	{
		if (m_process) {
			esyslog("ERROR[burn]: process already running "
					"(this shouldn't happen)");
			return;
		}

		switch (get_disk_type()) {
		case disk_type::dvd_menu:
		case disk_type::dvd_no_menu:
			m_process = new chain_dvd(*this);
			break;

		case disk_type::archive:
			m_process = new chain_archive(*this);
			break;

		default:
			break;
		};
		m_process->start();
	}

	void job::stop(void)
	{
		m_canceled = true;
		if (m_process)
			m_process->stop();
	}

	void job::append_recording(const cRecording* vdrRecording)
	{
		recording_list::iterator rec = m_recordings.insert(m_recordings.end(),
				recording( vdrRecording, m_cutOnDemux ));
		rec->set_index(m_recordings.size() - 1);
		if (m_title[0] == '\0')
			strncpy(m_title, rec->get_name(), JOBNAMELEN);
	}

	void job::erase_recording(recording_list::iterator rec)
	{
		m_recordings.erase(rec);
	}

	void job::set_paths(const path_pair& paths)
	{
		m_paths = paths;
		for_each(m_recordings.begin(), m_recordings.end(),
				 bind( &recording::set_paths, _1, m_paths ));
	}

	recording_list::const_iterator job::get_by_filename(const string& fileName)
	{
		return find_if(m_recordings.begin(), m_recordings.end(),
					   bind(
						   equal_to<string>(),
						   bind( &recording::get_filename, _1 ),
						   fileName
					   ));
	}

	int job::get_disk_size_mb(void) const
	{
		if (m_diskSize == disk_size::custom_dvd)
			return m_customDiskSizeMB;
		return disk_size::sizes[m_diskSize];
	}

}
