/*
 * /packages/iso9660-files filesystem handler
 *
 * (c) 2009 Laurent Vivier <Laurent@vivier.eu>
 * (c) 2010 Mark Cave-Ayland <mark.cave-ayland@siriusit.co.uk>
 */

#include "config.h"
#include "libopenbios/bindings.h"
#include "libiso9660.h"
#include "fs/fs.h"
#include "libc/vsprintf.h"
#include "libc/diskio.h"

extern void     iso9660_init( void );

typedef struct {
	enum { FILE, DIR } type;
	union {
		iso9660_FILE *file;
		iso9660_DIR * dir;
	};
} iso9660_COMMON;

typedef struct {
	iso9660_VOLUME *volume;
	iso9660_COMMON *common;
} iso9660_info_t;

DECLARE_NODE( iso9660, 0, sizeof(iso9660_info_t), "+/packages/iso9660-files" );

/* ( -- success? ) */
static void
iso9660_files_open( iso9660_info_t *mi )
{
	int fd;
	char *path = my_args_copy();

	if ( ! path )
		RET( 0 );
 
	fd = open_ih( my_parent() );
	if ( fd == -1 ) {
		free( path );
		RET( 0 );
	}
 
	mi->volume = iso9660_mount( fd );
	if ( mi->volume == NULL ) {
		free( path );
		close_io( fd );
		RET( 0 );
	}

	mi->common->dir = iso9660_opendir( mi->volume, path );
	if ( mi->common->dir == NULL ) {
		mi->common->file = iso9660_open( mi->volume, path );
		if (mi->common->file == NULL) {
			iso9660_umount( mi->volume );
			close_io( fd );
			free( path );
			RET( 0 );
		}
		mi->common->type = FILE;
		free( path );
		RET( -1 );
 	}
	mi->common->type = DIR;
	free( path );

	RET( -1 );
}

/* ( -- ) */
static void
iso9660_files_close( iso9660_info_t *mi )
{
	int fd = mi->volume->fd;
 
	if (mi->common->type == FILE )
		iso9660_close( mi->common->file );
	else if ( mi->common->type == DIR )
		iso9660_closedir( mi->common->dir );
	iso9660_umount( mi->volume );
	close_io( fd );
}

/* ( buf len -- actlen ) */
static void
iso9660_files_read( iso9660_info_t *mi )
{
	int count = POP();
	char *buf = (char *)cell2pointer(POP());
	int ret;
 
	if ( mi->common->type != FILE )
		PUSH( 0 );
 
	ret = iso9660_read( mi->common->file, buf, count );
 
	PUSH( ret );
}

/* ( pos.d -- status ) */
static void
iso9660_files_seek( iso9660_info_t *mi )
{
	long long pos = DPOP();
	cell ret;
	int offs = (int)pos;
	int whence = SEEK_SET;

	if (mi->common->type != FILE)
		PUSH( -1 );

	if( offs == -1 ) {
		offs = 0;
		whence = SEEK_END;
	}
 
	ret = iso9660_lseek(mi->common->file, offs, whence);
 
	PUSH( (ret < 0)? -1 : 0 );
}

/* ( -- filepos.d ) */
static void
iso9660_files_offset( iso9660_info_t *mi )
{
	if ( mi->common->type != FILE )
		DPUSH( -1 );
 
	DPUSH( mi->common->file->offset );
}

/* ( addr -- size ) */
static void
iso9660_files_load( iso9660_info_t *mi)
{
	char *buf = (char*)cell2pointer(POP());
	int ret, size;
 
	if ( mi->common->type != FILE )
		PUSH( 0 );
 
	size = 0;
	while(1) {
		ret = iso9660_read( mi->common->file, buf, 512 );
		if (ret <= 0)
			break;
		buf += ret;
		size += ret;
		if (ret != 512)
			break;
	}
	PUSH( size );
}

/* static method, ( pathstr len ihandle -- ) */
static void
iso9660_files_dir( iso9660_info_t *dummy )
{
	iso9660_VOLUME *volume;
	iso9660_COMMON *common;
	struct iso_directory_record *idr;
	char name_buf[256];
	int fd;
 
	ihandle_t ih = POP();
	char *path = pop_fstr_copy();

	fd = open_ih( ih );
	if ( fd == -1 ) {
		free( path );
		return;
	}
 
	volume = iso9660_mount( fd );
	if ( volume == NULL ) {
		free ( path );
		close_io( fd );
		return;
	}

	common = malloc(sizeof(iso9660_COMMON));
	common->dir = iso9660_opendir( volume, path );

	forth_printf("\n");
	while ( (idr = iso9660_readdir(common->dir)) ) {
 
		forth_printf("% 10d ", isonum_733(idr->size));
		forth_printf("%d-%02d-%02d %02d:%02d:%02d ",
			     idr->date[0] + 1900, /* year */
			     idr->date[1], /* month */
                             idr->date[2], /* day */
			     idr->date[3], idr->date[4], idr->date[5]);
		iso9660_name(common->dir->volume, idr, name_buf);
		if (idr->flags[0] & 2)
			forth_printf("%s\\\n", name_buf);
		else
			forth_printf("%s\n", name_buf);
	}

	iso9660_closedir( common->dir );
	iso9660_umount( volume );

	close_io( fd );

	free( common );
	free( path );
}

/* static method, ( pos.d ih -- flag? ) */
static void
iso9660_files_probe( iso9660_info_t *dummy )
{
	ihandle_t ih = POP_ih();
	long long offs = DPOP();
	int fd, ret = 0;

	fd = open_ih(ih);
        if (fd >= 0) {
                if (iso9660_probe(fd, offs)) {
                        ret = -1;
                }
                close_io(fd);
        } else {
                ret = -1;
        }

	RET (ret);
}

static void
iso9660_files_block_size( iso9660_info_t *dummy )
{
	PUSH(2048);
}
 
static void
iso9660_initializer( iso9660_info_t *dummy )
{
	fword("register-fs-package");
}
 
NODE_METHODS( iso9660 ) = {
	{ "probe",	iso9660_files_probe		},
	{ "open",	iso9660_files_open		},
	{ "close",	iso9660_files_close		},
	{ "read",	iso9660_files_read		},
	{ "seek",	iso9660_files_seek		},
	{ "offset",	iso9660_files_offset		},
	{ "load",	iso9660_files_load		},
	{ "dir",	iso9660_files_dir		},
	{ "block-size",	iso9660_files_block_size	},
	{ NULL,		iso9660_initializer	},
};

void
iso9660_init( void )
{
	REGISTER_NODE( iso9660 );
}