/* -*- indent-tabs-mode: t; tab-width: 8; c-basic-offset: 8; -*- */
/* vim: set ts=8 sts=8 sw=8 noet : */

#define _GNU_SOURCE

#include "libisofs.h"
#include "libburn/libburn.h"
#include <getopt.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include <err.h>

#define SECSIZE 2048

const char * const optstring = "JRL:h";
extern char *optarg;
extern int optind;

static struct data_source *libburn_data_source_new(struct burn_drive *d);

void usage()
{
	printf("test [OPTIONS] DISC DIRECTORY\n");
}

void help()
{
	printf(
"Options:\n"
"  -J       Add Joliet support\n"
"  -R       Add Rock Ridge support\n"
"  -L <num> Set the ISO level (1 or 2)\n"
"  -h       Print this message\n"
);
}

int main(int argc, char **argv)
{
	struct burn_drive_info *drives;
	struct burn_drive *drive;
	struct ecma119_source_opts wopts;
	struct ecma119_read_opts ropts;
	struct data_source *rsrc;
	struct iso_volset *volset;
	struct iso_tree_node_dir *root;
	struct burn_source *wsrc;
	int c;
	struct iso_tree_radd_dir_behavior behav = {0,0,0};
	int level=1, flags=0;
	int ret = 0;

	while ((c = getopt(argc, argv, optstring)) != -1) {
		switch(c) {
		case 'h':
			usage();
			help();
			exit(0);
			break;
		case 'J':
			flags |= ECMA119_JOLIET;
			break;
		case 'R':
			flags |= ECMA119_ROCKRIDGE;
			break;
		case 'L':
			level = atoi(optarg);
			break;
		case '?':
			usage();
			exit(1);
			break;
		}
	}

	if (argc < optind + 1) {
		printf ("Please supply device name\n");
		usage();
		return 1;
	}
	if (argc < optind + 2) {
		printf ("Please supply directory to add to disc\n");
		usage();
		return 1;
	}
	
	if (!iso_init()) {
		err(1, "Can't init libisofs");
	}
	if (!burn_initialize()) {
		err(1, "Can't init libburn");
	}
	iso_msgs_set_severities("NEVER", "ALL", "");
	burn_msgs_set_severities("NEVER", "SORRY", "libburner : ");
	
	printf("Reading from %s\n", argv[optind]);
	
	if (burn_drive_scan_and_grab(&drives, argv[optind], 0) != 1) {
		err(1, "Can't open device. Are you sure it is a valid drive?\n");
	}
	
	drive = drives[0].drive;
	
	{
		/* some check before going on */
		enum burn_disc_status state;
		int pno;
		char name[80];
		
		state = burn_disc_get_status(drive);
		burn_disc_get_profile(drive, &pno, name);
		
		// my drives report BURN_DISC_BLANK on a DVD+RW with data.
		// is that correct?
		if ( (pno != 0x1a) /*|| (state != BURN_DISC_FULL)*/ ) {
			printf("You need to insert a DVD+RW with some data.\n");
			printf("Profile: %x, state: %d.\n", pno, state);
			ret = 1;
			goto exit_cleanup;
		}
	}
	
	rsrc = libburn_data_source_new(drive);
	if (rsrc == NULL) {
		printf ("Can't create data source.\n");
		ret = 1;
		goto exit_cleanup;
	}
	
	ropts.block = 0; /* image always start on first block */
	ropts.norock = 0;
	ropts.nojoliet = 0;
	ropts.preferjoliet = 0;
	ropts.mode = 0555;
	ropts.uid = 0;
	ropts.gid = 0;
	volset = iso_volset_read(rsrc, &ropts);
	
	if (volset == NULL) {
		printf ("Error reading image\n");
		ret = 1;
		goto exit_cleanup;
	}

	printf("Image size: %d blocks.\n", ropts.size);

	/* close source, no more needed */
	data_source_free(rsrc);

	root = iso_volume_get_root(iso_volset_get_volume(volset, 0));
	
	/* add a new dir */
	iso_tree_radd_dir(root, argv[optind+1], &behav);
	
	memset(&wopts, 0, sizeof(struct ecma119_source_opts));
	wopts.level = level;
	wopts.flags = flags;
	wopts.relaxed_constraints = 0;
	wopts.input_charset = "UTF-8";
	wopts.ouput_charset = "UTF-8";
	/* round up to 32kb aligment = 16 block*/
	wopts.ms_block = ((ropts.size + 15) / 16 ) * 16;
	wopts.overwrite = calloc(32, 2048);
	
	wsrc = iso_source_new_ecma119(volset, &wopts);

	/* a. write the new image */
	printf("Adding new data...\n");
	{
		struct burn_disc *target_disc;
		struct burn_session *session;
		struct burn_write_opts *burn_options;
		struct burn_track *track;
		struct burn_progress progress;
		char reasons[BURN_REASONS_LEN];
		
		target_disc = burn_disc_create();
		session = burn_session_create();
		burn_disc_add_session(target_disc, session, BURN_POS_END);
		
		track = burn_track_create();
		burn_track_set_source(track, wsrc);
	  	burn_session_add_track(session, track, BURN_POS_END);
	  	
	  	burn_options = burn_write_opts_new(drive);
		burn_drive_set_speed(drive, 0, 0);
		burn_write_opts_set_underrun_proof(burn_options, 1);
		
		//mmm, check for 32K alignment?
	  	burn_write_opts_set_start_byte(burn_options, wopts.ms_block * 2048);
		
		if (burn_write_opts_auto_write_type(burn_options, target_disc,
					reasons, 0) == BURN_WRITE_NONE) {
			printf("Failed to find a suitable write mode:\n%s\n", reasons);
			ret = 1;
			goto exit_cleanup;
		}
		
		/* ok, write the new track */
		burn_disc_write(burn_options, target_disc);
		burn_write_opts_free(burn_options);
		
		while (burn_drive_get_status(drive, NULL) == BURN_DRIVE_SPAWNING)
			usleep(1002);
		
		while (burn_drive_get_status(drive, &progress) != BURN_DRIVE_IDLE) {
			printf("Writing: sector %d of %d\n", progress.sector, progress.sectors);
			sleep(1);
		}
		
	}
	
	/* b. write the new vol desc */
	printf("Writing the new vol desc...\n");
	if ( burn_random_access_write(drive, 0, wopts.overwrite, 32*2048, 0) != 1) {
 		printf("Ups, new vol desc write failed\n");
 	}
 	
	free(wopts.overwrite);
	iso_volset_free(volset);
	
exit_cleanup:;
	burn_drive_release(drives[0].drive, 0);
	burn_finish();
	iso_finish();
	
	exit(ret);
}

struct disc_data_src {
	struct burn_drive *d;
	int nblocks;
};

static int 
libburn_ds_read_block(struct data_source *src, int lba, unsigned char *buffer)
{
	struct disc_data_src *data;
	off_t data_count;
	
	assert(src && buffer);
	
	data = (struct disc_data_src*)src->data;
	
	/* if (lba >= data->nblocks) 
	 *	return BLOCK_OUT_OF_FILE;
	 */
	
	if ( burn_read_data(data->d, (off_t) lba * (off_t) 2048, buffer, 2048, 
	                    &data_count, 0) < 0 ) {
         return -1; //error
    }
		
	return 0;
}
	
static int 
libburn_ds_get_size(struct data_source *src)
{
	struct disc_data_src *data;
	
	assert(src);
	
	data = (struct disc_data_src*)src->data;
	return data->nblocks;
}
	
static void 
libburn_ds_free_data(struct data_source *src)
{
	free(src->data);
}
 
static struct data_source *
libburn_data_source_new(struct burn_drive *d)
{
	struct disc_data_src *data;
	struct data_source *ret;
	
	assert(d);
	
	data = malloc(sizeof(struct disc_data_src));
	data->d = d;
	
	//should be filled with the size of disc (or track?)
	data->nblocks = 0;
	
	ret = malloc(sizeof(struct data_source));
	ret->refcount = 1;
	ret->read_block = libburn_ds_read_block;
	ret->get_size = libburn_ds_get_size;
	ret->free_data = libburn_ds_free_data;
	ret->data = data;
	return ret;
}