#include "ecdb.h"

typedef struct Burn_Data Burn_Data;
struct Burn_Data
{
	BurnDisc *disc;
	BurnSession *session;
	Ecore_List *sources;
	Ecore_List *tracks;
	Ecdb_Project *proj;
};

void *_progress_update(void *d);
int _progress_gui_update(void *d);
int ecdb_burn_finished(void *data, int type, void *event);
int ecdb_burn_project_init(Ecdb_Burn_Project *proj);
int ecdb_erase_project_init(Ecdb_Erase_Project *proj);

Ecdb_Burn_Project *
ecdb_burn_project_new(void)
{
	Ecdb_Burn_Project *proj;

	proj = calloc(1, sizeof(Ecdb_Burn_Project));
	if (!proj)
		return NULL;
	if (!ecdb_burn_project_init(proj))
	{
		FREE(proj);
		return NULL;
	}

	return proj;
}

int
ecdb_burn_project_init(Ecdb_Burn_Project *proj)
{
	if (!ecdb_project_init(ECDB_PROJECT(proj)))
		return FALSE;

	/* Create some sane defaults */
	ecdb_project_type_set(ECDB_PROJECT(proj), ECDB_BURN_PROJECT);
	proj->burn_mode = BURN_MODE1;
	proj->fifo_chunksize = 2048;
	proj->fifo_chunks = 2048;
	proj->underrun_proof = TRUE;
	proj->opc = TRUE;
	proj->multi = TRUE;
	proj->files = ecdb_source_new();

	return TRUE;
}

Ecdb_Erase_Project *
ecdb_erase_project_new(void)
{
	Ecdb_Erase_Project *proj;

	proj = calloc(1, sizeof(Ecdb_Erase_Project));
	if (!proj)
		return NULL;
	if (!ecdb_erase_project_init(proj))
	{
		FREE(proj);
		return NULL;
	}

	return proj;
}

int
ecdb_erase_project_init(Ecdb_Erase_Project *proj)
{
	if (!ecdb_project_init(ECDB_PROJECT(proj)))
		return FALSE;

	/* Proper defaults */
	ecdb_project_type_set(ECDB_PROJECT(proj), ECDB_ERASE_PROJECT);
	proj->quick = TRUE;
	proj->format = FALSE;

	return TRUE;
}

void
ecdb_burn_project_destroy(Ecdb_Burn_Project *proj)
{
	ecdb_source_destroy(proj->files);
	FREE(proj->volume_id);
	FREE(proj->publisher_id);
	FREE(proj->data_preparer_id);
	FREE(proj->system_id);
	FREE(proj->application_id);
	FREE(proj->copywrite_id);
	FREE(proj->abstract_id);
	FREE(proj->biblio_id);
	ecdb_project_destroy(ECDB_PROJECT(proj));
	free(proj);
}

void
ecdb_erase_project_destroy(Ecdb_Erase_Project *proj)
{
	ecdb_project_destroy(ECDB_PROJECT(proj));
	free(proj);
}

/* Erase and Burn Function */
int
ecdb_burn_project(Ecdb_Burn_Project *proj)
{
	char reasons[BURN_REASONS_LEN];
	int padding, i;
	Burn_Data *data;
	BurnTrack *track;
	BurnSource *source;
	BurnWriteOpts *opts;
	pthread_t progress_update;

	i = 0;
	data = calloc(1, sizeof(Burn_Data));
	if (!data)
	{
		printf("Error: Cannot create burn data structure!\n");
		return FALSE;
	}
	data->sources = ecore_list_new();
	data->tracks = ecore_list_new();
	data->proj = ECDB_PROJECT(proj);

	if (proj->burn_mode != BURN_AUDIO)
		padding = 300*1024;

	data->disc = burn_disc_create();
	data->session = burn_session_create();
	burn_disc_add_session(data->disc, data->session, BURN_POS_END);

	track = burn_track_create();
	burn_track_define_data(track, 0, padding, 1, proj->burn_mode);

	source = ecdb_image_project(proj);
	if (!source)
	{
		printf("Failed to add any files to burn disc!\n");
		return FALSE;
	}

	if (burn_track_set_source(track, source) != BURN_SOURCE_OK)
	{
		printf("Error: Cannot attach source object to track "
				"object!\n");
		return FALSE;
	}

	burn_session_add_track(data->session, track, BURN_POS_END);
	ecore_list_append(data->sources, source);
	ecore_list_append(data->tracks, track);

	opts = burn_write_opts_new(ECDB_PROJECT(proj)->drive->
							tangible[0].drive);
	burn_write_opts_set_perform_opc(opts, proj->opc);
	burn_write_opts_set_multi(opts, proj->multi);
	if (proj->simulate)
		printf("Simulating Burn!\n");
	burn_write_opts_set_simulate(opts, proj->simulate);
	burn_drive_set_speed(ECDB_PROJECT(proj)->drive->tangible->drive, 0,
		       					proj->speed);
	burn_write_opts_set_underrun_proof(opts, proj->underrun_proof);

	printf("Searching for burn mode\n");
	if (burn_write_opts_auto_write_type(opts, data->disc, reasons, 0)
			== BURN_WRITE_NONE)
	{
		printf("Error: Failed to find a suitable write mode for "
				"disc!\n");
		return FALSE;
	}

	burn_disc_write(opts, data->disc);
	burn_write_opts_free(opts);

	printf("Disc now burning\n");
	pthread_create(&progress_update, NULL, _progress_update, proj);
	pthread_detach(progress_update);
	ecore_timer_add(0.5, _progress_gui_update, proj);
	ECDB_PROJECT(proj)->ev_handler = ecore_event_handler_add
			(ECDB_DRIVE_ACTION_FINISHED, ecdb_burn_finished,
			data);

	return TRUE;
}

int
ecdb_erase_disc(Ecdb_Erase_Project *proj)
{
	BurnDriveStatus disc_state;
	pthread_t progress_update;

	disc_state = burn_disc_get_status(ECDB_PROJECT(proj)->drive->
							tangible[0].drive);
	if (disc_state == BURN_DISC_BLANK)
	{
		printf("Disc is already blank!\n");
		return FALSE;
	}
	else if (disc_state == BURN_DISC_EMPTY)
	{
		printf("No disc!\n");
		return FALSE;
	}
	else if (!burn_disc_erasable(ECDB_PROJECT(proj)->drive->
							tangible[0].drive))
	{
		printf("Not able to erase!\n");
		return FALSE;
	}
	else if (disc_state == BURN_DISC_FULL || BURN_DISC_APPENDABLE)
	{
		printf("Beginning to erase disc!\n");
		burn_disc_erase(ECDB_PROJECT(proj)->drive->
					tangible[0].drive, proj->quick);
		pthread_create(&progress_update, NULL, _progress_update,
								proj);
		pthread_detach(progress_update);
		ecore_timer_add(0.5, _progress_gui_update, proj);
		return TRUE;
	}
	else
	{
		printf("Not of erasable type\n");
		return FALSE;
	}
}

/* Hopefully at some point EFL will become thread-safe, or ecore_timer will
 * work with burn_drive_get_status without segfaulting.  At that point we can
 * do away with this.
 */

/* Event handlers */
void *
_progress_update(void *d)
{
	Ecdb_Project *proj;
	BurnProgress p;
	struct burn_drive *drive;

	proj = d;

	if (!proj->drive->tangible)
	{
		printf("No tangible drive!\n");
		pthread_exit(NULL);
	}
	drive = proj->drive->tangible[0].drive;

	printf("Progress update active\n");
	while (TRUE)
	{
		proj->stat = burn_drive_get_status(drive, &p);
		if (proj->stat == BURN_DRIVE_SPAWNING)
		{
			sleep(3);
			continue;
		}
		else if (proj->stat == BURN_DRIVE_IDLE)
		{
			pthread_exit(NULL);
			break;
		}

		proj->progress = p;
		sleep(5);
	}
}

int
_progress_gui_update(void *data)
{
	Ecdb_Project *proj;

	proj = data;
	
	if (proj->stat == BURN_DRIVE_IDLE)
	{
		/* These don't enjoy being called before
		 * ecore_main_loop_begin
		 */
		//ecore_event_add(ECDB_DRIVE_ACTION_FINISHED, data, NULL, NULL);
		ecore_event_add(ECDB_DRIVE_ACTION_FINISHED, NULL, NULL, NULL);
		printf("Burn finished\n");
		return ECORE_CALLBACK_CANCEL;
	}
	else
		ecore_event_add(ECDB_DRIVE_ACTION_UPDATE, NULL, NULL, NULL);
		//ecore_event_add(ECDB_DRIVE_ACTION_UPDATE, data, NULL, NULL);
	
	return ECORE_CALLBACK_RENEW;
}


int
ecdb_burn_finished(void *data, int type, void *event)
{
	Burn_Data *proj;
	BurnTrack *track;
	BurnSource *source;

	proj = data;

	ecore_list_first_goto(proj->sources);
	ecore_list_first_goto(proj->tracks);

	printf("Freeing source and tracks\n");
	while ((source = ecore_list_remove(proj->sources)))
	{
		burn_source_free(source);
		track = ecore_list_remove(proj->tracks);
		burn_track_free(track);
	}
	ecore_list_destroy(proj->sources);
	ecore_list_destroy(proj->tracks);

	printf("Freeing session and disc\n");
	burn_session_free(proj->session);
	burn_disc_free(proj->disc);

	printf("Releasing drive\n");
	burn_drive_release(proj->proj->drive->tangible[0].drive, 1);
	burn_drive_info_free(proj->proj->drive->tangible);
	printf("Burn Complete\n");

	ecore_event_handler_del(ECDB_PROJECT(proj)->ev_handler);

	/* To be removed from here at some point */
	Ecdb_Burn_Project *t;
	t = ECDB_BURN(proj->proj);
	FREE(t->files);
	FREE(t);
	FREE(proj);
	ecore_event_add(ECORE_EVENT_SIGNAL_EXIT, NULL, NULL, NULL);
	return TRUE;
}	
