/***************************************************************************
 *            burn-libburn-common.c
 *
 *  mer aoû 30 16:35:40 2006
 *  Copyright  2006  Rouquier Philippe
 *  bonfire-app@wanadoo.fr
 ***************************************************************************/

/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <glib.h>
#include <glib-object.h>
#include <glib/gi18n-lib.h>

#include "burn-basics.h"
#include "burn-job.h"
#include "burn-libburn-common.h"
#include "brasero-ncb.h"

#ifdef HAVE_LIBBURN

#include <libburn/libburn.h>

static void brasero_libburn_common_class_init (BraseroLibburnCommonClass *klass);
static void brasero_libburn_common_init (BraseroLibburnCommon *sp);
static void brasero_libburn_common_finalize (GObject *object);

static BraseroBurnResult
brasero_libburn_common_get_written (BraseroJob *job,
				    gint64 *size);
static BraseroBurnResult
brasero_libburn_common_check_status (BraseroJob *job);

struct _BraseroLibburnCommonPrivate {
	struct burn_drive_info *drive_info;
	struct burn_drive *drive;
	struct burn_disc *disc;

	enum burn_drive_status status;

	/* used detect track hops */
	gint track_num;

	/* used to report current written sector */
	gint64 sectors;
	gint64 cur_sector;
	gint64 track_sectors;

	gint has_leadin;
};

static BraseroJobClass *parent_class = NULL;

GType
brasero_libburn_common_get_type()
{
	static GType type = 0;

	if(type == 0) {
		static const GTypeInfo our_info = {
			sizeof (BraseroLibburnCommonClass),
			NULL,
			NULL,
			(GClassInitFunc)brasero_libburn_common_class_init,
			NULL,
			NULL,
			sizeof (BraseroLibburnCommon),
			0,
			(GInstanceInitFunc)brasero_libburn_common_init,
		};

		type = g_type_register_static (BRASERO_TYPE_JOB, 
					       "BraseroLibburnCommon",
					       &our_info,
					       0);
	}

	return type;
}

static void
brasero_libburn_common_class_init (BraseroLibburnCommonClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	BraseroJobClass *job_class = BRASERO_JOB_CLASS (klass);

	parent_class = g_type_class_peek_parent (klass);
	object_class->finalize = brasero_libburn_common_finalize;

	job_class->check_status = brasero_libburn_common_check_status;
	job_class->get_written = brasero_libburn_common_get_written;
}

static void
brasero_libburn_common_init (BraseroLibburnCommon *obj)
{
	obj->priv = g_new0 (BraseroLibburnCommonPrivate, 1);
	//burn_set_verbosity (666);
}

static void
brasero_libburn_common_stop_real (BraseroLibburnCommon *self)
{
	if (self->priv->drive) {
		enum burn_drive_status status;

		status = burn_drive_get_status (self->priv->drive, NULL);
		if (status == BURN_DRIVE_READING || status == BURN_DRIVE_WRITING) {
			burn_drive_cancel (self->priv->drive);
			status = burn_drive_get_status (self->priv->drive, NULL);
		}

		/* wait for the drive to be idle */
		while (status != BURN_DRIVE_IDLE) {
			g_main_context_iteration (NULL, FALSE);
			status = burn_drive_get_status (self->priv->drive, NULL);
		}

		burn_drive_release (self->priv->drive, 0);
	}
}

static void
brasero_libburn_common_finalize (GObject *object)
{
	BraseroLibburnCommon *cobj;

	cobj = BRASERO_LIBBURN_COMMON (object);

	brasero_libburn_common_stop_real (cobj);

	if (cobj->priv->disc) {
		burn_disc_free (cobj->priv->disc);
		cobj->priv->disc = NULL;
	}

	if (cobj->priv->drive_info) {
		burn_drive_info_free (cobj->priv->drive_info);
		cobj->priv->drive_info = NULL;
		cobj->priv->drive = NULL;
	}	

	g_free (cobj->priv);
	G_OBJECT_CLASS (parent_class)->finalize (object);
}

BraseroLibburnCommon *
brasero_libburn_common_new ()
{
	BraseroLibburnCommon *obj;
	
	obj = BRASERO_LIBBURN_COMMON (g_object_new(BRASERO_TYPE_LIBBURN_COMMON, NULL));
	
	return obj;
}

BraseroBurnResult
brasero_libburn_common_set_drive (BraseroLibburnCommon *self,
				  NautilusBurnDrive *drive,
				  GError **error)
{
	NautilusBurnMediaType media;

	if (self->priv->drive_info) {
		burn_drive_info_free (self->priv->drive_info);
		self->priv->drive_info = NULL;
		self->priv->drive = NULL;
	}

	if (!drive)
		return BRASERO_BURN_OK;

	media = nautilus_burn_drive_get_media_type (drive);
	if (media > NAUTILUS_BURN_MEDIA_TYPE_CDRW)
		return BRASERO_BURN_NOT_SUPPORTED;

	/* we just want to scan the drive proposed by NCB drive */
	if (burn_drive_scan_and_grab (&self->priv->drive_info, NCB_DRIVE_GET_DEVICE (drive), 0) <= 0) {
		g_set_error (error,
			     BRASERO_BURN_ERROR,
			     BRASERO_BURN_ERROR_GENERAL,
			     _("the drive couldn't be initialized"));
		return BRASERO_BURN_ERR;
	}

	self->priv->drive = self->priv->drive_info->drive;
	return BRASERO_BURN_OK;
}

BraseroBurnResult
brasero_libburn_common_get_drive (BraseroLibburnCommon *self,
				  struct burn_drive **drive)
{
	g_return_val_if_fail (drive != NULL, BRASERO_BURN_ERR);

	*drive = self->priv->drive;
	return BRASERO_BURN_OK;
}

BraseroBurnResult
brasero_libburn_common_set_disc (BraseroLibburnCommon *self,
				 struct burn_disc *disc)
{
	if (self->priv->disc)
		burn_disc_free (self->priv->disc);

	self->priv->disc = disc;
	return BRASERO_BURN_OK;
}

BraseroBurnResult
brasero_libburn_common_get_disc (BraseroLibburnCommon *self,
				 struct burn_disc **disc)
{
	*disc = self->priv->disc;
	return BRASERO_BURN_OK;
}

static gboolean
brasero_libburn_common_process_message (BraseroLibburnCommon *self,
					struct burn_message *mes)
{
	GError *error;
	gboolean retval = TRUE;

	switch (mes->type) {
		case BURN_MESSAGE_ERROR:
			if (mes->detail.error.message == BURN_ERROR_CANCELLED) {
				retval = TRUE;
				break;
			}

		case BURN_MESSAGE_WARNING:
			error = g_error_new (BRASERO_BURN_ERROR,
					     BRASERO_BURN_ERROR_GENERAL,
					     _("an unknown error occured with libburn"));
			brasero_job_error (BRASERO_JOB (self), error);
			retval = FALSE;
			break;

		case BURN_MESSAGE_INFO:
			BRASERO_JOB_LOG (self, _("(%s) libburn tried to say something"));
			retval = TRUE;
			break;
	}

	burn_message_free (mes);
	return retval;
}

static void
brasero_libburn_common_status_changed (BraseroLibburnCommon *self,
				       enum burn_drive_status status,
				       struct burn_progress *progress)
{
	BraseroBurnAction action = BRASERO_BURN_ACTION_NONE;

	switch (status) {
		case BURN_DRIVE_WRITING:
			/* we ignore it if it happens after leadout */
			if (self->priv->status == BURN_DRIVE_WRITING_LEADOUT)
				return;

			if (self->priv->status == BURN_DRIVE_WRITING_LEADIN) {
				self->priv->sectors += self->priv->track_sectors;
				self->priv->track_sectors = progress->sectors;
				self->priv->track_num = progress->track;
			}

			action = BRASERO_BURN_ACTION_WRITING;
			brasero_job_set_dangerous (BRASERO_JOB (self), TRUE);
			break;

		case BURN_DRIVE_WRITING_LEADIN:
			self->priv->has_leadin = 1;
			action = BRASERO_BURN_ACTION_PREPARING;
			brasero_job_set_dangerous (BRASERO_JOB (self), FALSE);
			break;

		case BURN_DRIVE_WRITING_LEADOUT:
			self->priv->sectors += self->priv->track_sectors;
			self->priv->track_sectors = progress->sectors;

			action = BRASERO_BURN_ACTION_FIXATING;
			brasero_job_set_dangerous (BRASERO_JOB (self), FALSE);
			break;

		case BURN_DRIVE_ERASING:
			action = BRASERO_BURN_ACTION_BLANKING;
			brasero_job_set_dangerous (BRASERO_JOB (self), TRUE);
			break;

		case BURN_DRIVE_IDLE:
			brasero_job_finished (BRASERO_JOB (self));
			brasero_job_set_dangerous (BRASERO_JOB (self), FALSE);
			break;

		case BURN_DRIVE_SPAWNING:
			if (self->priv->status == BURN_DRIVE_IDLE)
				action = BRASERO_BURN_ACTION_PREPARING;
			else
				action = BRASERO_BURN_ACTION_FIXATING;
			brasero_job_set_dangerous (BRASERO_JOB (self), FALSE);
			break;

		case BURN_DRIVE_READING:
			action = BRASERO_BURN_ACTION_CREATING_IMAGE;
			brasero_job_set_dangerous (BRASERO_JOB (self), FALSE);
			break;

		default:
			return;
	}

	self->priv->status = status;
	brasero_job_task_set_action (BRASERO_JOB (self), action, FALSE);
}

static gint64
brasero_libburn_common_get_session_size (BraseroLibburnCommon *self)
{
	struct burn_session **sessions;
	gint64 sectors = 0;
	int num_sessions;
	int i;

	sessions = burn_disc_get_sessions (self->priv->disc, &num_sessions);
	for (i = 0; i < num_sessions; i ++) {
		sectors += burn_session_get_sectors (sessions [i]);

		/* add the size for lead-out in case of raw */
		if (self->priv->has_leadin)
			sectors += (i == 0) ? 6750:2250;
	}

	/* add the size for lead-in in case of raw */
	if (self->priv->has_leadin)
		sectors += 11475;

	return sectors;
}

static BraseroBurnResult
brasero_libburn_common_check_status (BraseroJob *job)
{
	BraseroLibburnCommon *self;
	struct burn_message *message;
	enum burn_drive_status status;
	struct burn_progress progress;

	self = BRASERO_LIBBURN_COMMON (job);

	/* see if there is any pending message */
	message = burn_get_message ();
	if (message && !brasero_libburn_common_process_message (self, message))
		return BRASERO_BURN_OK;

	if (!self->priv->drive)
		return BRASERO_BURN_OK;

	status = burn_drive_get_status (self->priv->drive, &progress);
	if (self->priv->status != status)
		brasero_libburn_common_status_changed (self, status, &progress);

	if (status == BURN_DRIVE_IDLE
	||  status == BURN_DRIVE_SPAWNING
	||  !progress.sectors
	||  !progress.sector) {
		self->priv->cur_sector = -1;
		return BRASERO_BURN_OK;
	}

	if (brasero_job_task_get_elapsed (BRASERO_JOB (self), NULL) != BRASERO_BURN_NOT_READY) {
		gdouble fraction;
		gint64 sectors;

		if (status != BURN_DRIVE_ERASING) {
			if (self->priv->track_num != progress.track) {
				self->priv->sectors += self->priv->track_sectors;
				self->priv->track_sectors = progress.sectors;
				self->priv->track_num = progress.track;
			}

			self->priv->cur_sector = progress.sector + self->priv->sectors;
			sectors = brasero_libburn_common_get_session_size (self);
		}
		else {
			self->priv->cur_sector = progress.sector;
			sectors = progress.sectors;
		}

		fraction = (gdouble) (self->priv->cur_sector) /
			   (gdouble) (sectors);

		brasero_job_task_set_progress (BRASERO_JOB (self), fraction);
		return BRASERO_BURN_OK;
	}

	self->priv->sectors = 0;
	self->priv->cur_sector = 0;

	self->priv->track_num = progress.track;
	self->priv->track_sectors = progress.sectors;

	brasero_job_task_start_progress (BRASERO_JOB (self));
	return BRASERO_BURN_OK;
}

static BraseroBurnResult
brasero_libburn_common_get_written (BraseroJob *job,
				    gint64 *size)
{
	BraseroLibburnCommon *self;

	g_return_val_if_fail (size != NULL, BRASERO_BURN_ERR);

	self = BRASERO_LIBBURN_COMMON (job);

	if (self->priv->cur_sector < 0)
		return BRASERO_BURN_NOT_READY;

	*size = self->priv->cur_sector * 2048;

	return BRASERO_BURN_OK;
}

#endif /* HAVE_LIBBURN */
