/*
 * Implementation of iso_file_src for:
 * 
 * a) read from local filesystem files
 * b) read from previous session / image files
 */

#include <stdlib.h>       
#include <stdio.h>       
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <limits.h>

#include "file_src.h"
#include "messages.h"
#include "libisofs.h"
#include "util.h"

#define BLOCK_SIZE 2048


void iso_file_src_free(struct iso_file_src *src)
{
	src->free_data(src);
	free(src);
}

/*
 * ==========================================================================
 * A) FILE SRC FOR LOCAL FILESYSTEM FILES
 * ==========================================================================
 */

struct local_file_data {
	int fd; /* the file descriptor, once the file is opened */
	off_t size; /* file size */
	off_t bytes_read; /* bytes already read from file */
	int error;
	char *path; /* path of the file on local filesystem */
};

static
int lf_open(struct iso_file_src *src)
{
	struct local_file_data *data;
	assert(src);
	
	data = (struct local_file_data*) src->data;
	
	assert(data->fd == -1);
	
	data->fd = open(data->path, O_RDONLY);
	data->bytes_read = (off_t) 0;
	data->error = 0;
	return (data->fd != -1 ? 1 : 0);
}

static
void lf_close(struct iso_file_src *src)
{
	struct local_file_data *data;
	assert(src);
	
	data = (struct local_file_data*) src->data;
	assert(data->fd != -1);
	
	close(data->fd);
	data->fd = -1;
}

static
int lf_read_block(struct iso_file_src *src, unsigned char *buffer)
{
	ssize_t bytes;
	struct local_file_data *data;
	assert(src);
	assert(buffer);
	
	data = (struct local_file_data*) src->data;
	assert(data->fd != -1);
	
	if (data->bytes_read >= data->size) {
		return 0;
	}
	
	if (data->error) {
		memset(buffer, 0, BLOCK_SIZE);
		data->bytes_read += BLOCK_SIZE;
		return -2;
	}
	
	bytes = 0;
	
	do {
		ssize_t result;
		result = read(data->fd, buffer + bytes, BLOCK_SIZE - bytes);
		if (result < 0) {
			char msg[PATH_MAX + 32];
			sprintf(msg, "Problem reading from %s", data->path);
			iso_msg_sorry(LIBISO_CANT_READ_FILE, msg);
			
			/* fill buffer with 0s and return */ 
			memset(buffer + bytes, 0, BLOCK_SIZE - bytes);
			data->bytes_read += BLOCK_SIZE;
			return -1;
		}
		if (!result)
			break;
		bytes += result;	
	} while (bytes < BLOCK_SIZE);
	
	if (bytes < BLOCK_SIZE) {
		/* eof */
		memset(buffer + bytes, 0, BLOCK_SIZE - bytes);
	}
	
	data->bytes_read += (off_t) bytes;
	if (data->bytes_read >= data->size) {
		return 0;
	} else {
		return 1;
	}
}

static
off_t lf_get_size(struct iso_file_src *src)
{
	struct local_file_data *data;
	assert(src);
	
	data = (struct local_file_data*) src->data;
	return data->size;
}

static
void lf_free_data(struct iso_file_src *src)
{
	struct local_file_data *data;
	data = (struct local_file_data*) src->data;
	free(data->path);
	free(data);
}

struct iso_file_src* 
iso_file_src_from_path(const char *path)
{
	struct stat info;
	struct iso_file_src *src;
	struct local_file_data *data;
	
	assert(path);
	
	if (lstat(path, &info) < 0) {
		iso_msg_fatal(LIBISO_FILE_DOESNT_EXIST, "File doesn't exist");
		return NULL;
	}
	if (access(path, R_OK) < 0) {
		iso_msg_fatal(LIBISO_FILE_NO_READ_ACCESS, "No read access");
		return NULL;
	}
	
	data = malloc(sizeof(struct local_file_data));
	if (!data)
		return NULL;
	
	src = malloc(sizeof(struct iso_file_src));	
	if (!src) {
		free(data);
		return NULL;
	}
	
	data->fd = -1;
	data->path = strdup(path);
	data->size = info.st_size;
	
	src->open = lf_open;
	src->close = lf_close;
	src->read_block = lf_read_block;
	src->get_size = lf_get_size;
	src->free_data = lf_free_data;
	
	src->data = data;
	return src;
}


/*
 * ==========================================================================
 * B) FILE SRC FOR PREVIOUS IMAGE FILES
 * ==========================================================================
 */

struct prev_img_file_data {
	int block; /* block where image begins */
	off_t size; /* file size */
	int nblocks; /* file size in blocks */
	int current; /* last block read from file */
	int error;
	struct data_source *src; /* source for reading from previous image */
};

static
int pi_open(struct iso_file_src *src)
{
	struct prev_img_file_data *data;
	assert(src);
	
	data = (struct prev_img_file_data*) src->data;
	data->current = data->block;
	data->error = 0;
	return 1;
}

static
void pi_close(struct iso_file_src *src)
{
	/* nothing needed */
	return;
}

static
int pi_read_block(struct iso_file_src *src, unsigned char *buffer)
{
	int res;
	struct prev_img_file_data *data;
	assert(src);
	assert(buffer);
	
	data = (struct prev_img_file_data*) src->data;
	
	if (data->current >= data->block + data->nblocks) {
		return 0;
	}
	
	if (data->error) {
		memset(buffer, 0, BLOCK_SIZE);
		data->current++;
		return -2;
	}
	
	res = data->src->read_block(data->src, data->current++, buffer);
	if (res < 0) {
		iso_msg_sorry(LIBISO_CANT_READ_IMG, 
			"Problem reading file from previous image");
		/* fill buffer with 0s and return */ 
		memset(buffer, 0, BLOCK_SIZE);
		return -1;
	} 
	
	if (data->current >= data->block + data->nblocks) {
		return 0;
	} else {
		return 1;
	}
}

static
off_t pi_get_size(struct iso_file_src *src)
{
	struct prev_img_file_data *data;
	assert(src);
	
	data = (struct prev_img_file_data*) src->data;
	return data->size;
}

static
void pi_free_data(struct iso_file_src *src)
{
	free(src->data);
}

struct iso_file_src* 
iso_file_src_from_prev_img(struct data_source* data_src, int block, off_t size)
{
	struct iso_file_src *src;
	struct prev_img_file_data *data;
	
	data = malloc(sizeof(struct prev_img_file_data));
	if (!data)
		return NULL;
	
	src = malloc(sizeof(struct iso_file_src));	
	if (!src) {
		free(data);
		return NULL;
	}
	
	data->block = block;
	data->size = size;
	data->nblocks = div_up(size, BLOCK_SIZE);
	data->src = data_src;
	
	src->open = pi_open;
	src->close = pi_close;
	src->read_block = pi_read_block;
	src->get_size = pi_get_size;
	src->free_data = pi_free_data;
	
	src->data = data;
	return src;
}