#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//#include <zlib.h>

#define __MSCW32__
#ifdef __WIN32__
#include <windows.h>
#endif

#include "PS2Etypes.h"
//#include "CDVDiso.h"
#include "libiso.h"

/* some structs from libcdvd by Hiryu & Sjeep (C) 2002 */

#if defined(__WIN32__)
#pragma pack(1)
#endif

struct rootDirTocHeader
{
	u16	length;			//+00
	u32 tocLBA;			//+02
	u32 tocLBA_bigend;	//+06
	u32 tocSize;		//+0A
	u32 tocSize_bigend;	//+0E
	u8	dateStamp[8];	//+12
	u8	reserved[6];	//+1A
	u8	reserved2;		//+20
	u8	reserved3;		//+21
#if defined(__WIN32__)
};						//+22
#else
} __attribute__((packed));
#endif

struct asciiDate
{
	char	year[4];
	char	month[2];
	char	day[2];
	char	hours[2];
	char	minutes[2];
	char	seconds[2];
	char	hundreths[2];
	char	terminator[1];
#if defined(__WIN32__)
};
#else
} __attribute__((packed));
#endif

struct cdVolDesc
{
	u8		filesystemType;	// 0x01 = ISO9660, 0x02 = Joliet, 0xFF = NULL
	u8		volID[5];		// "CD001"
	u8		reserved2;
	u8		reserved3;
	u8		sysIdName[32];
	u8		volName[32];	// The ISO9660 Volume Name
	u8		reserved5[8];
	u32		volSize;		// Volume Size
	u32		volSizeBig;		// Volume Size Big-Endian
	u8		reserved6[32];
	u32		unknown1;
	u32		unknown1_bigend;
	u16		volDescSize;									//+80
	u16		volDescSize_bigend;								//+82
	u32		unknown3;										//+84
	u32		unknown3_bigend;								//+88
	u32		priDirTableLBA;	// LBA of Primary Dir Table		//+8C
	u32		reserved7;										//+90
	u32		secDirTableLBA;	// LBA of Secondary Dir Table	//+94
	u32		reserved8;										//+98
	struct rootDirTocHeader	rootToc;
	u8		volSetName[128];
	u8		publisherName[128];
	u8		preparerName[128];
	u8		applicationName[128];
	u8		copyrightFileName[37];
	u8		abstractFileName[37];
	u8		bibliographyFileName[37];
	struct	asciiDate	creationDate;
	struct	asciiDate	modificationDate;
	struct	asciiDate	effectiveDate;
	struct	asciiDate	expirationDate;
	u8		reserved10;
	u8		reserved11[1166];
#if defined(__WIN32__)
};
#else
} __attribute__((packed));
#endif


#ifdef __WIN32__
void *_openfile(const char *filename, int flags) {
	HANDLE handle;

//	printf("_openfile %s, %d\n", filename, flags & O_RDONLY);
	if (flags & O_WRONLY) {
		int _flags = CREATE_NEW;
		if (flags & O_CREAT) _flags = CREATE_ALWAYS;
		handle = CreateFile(filename, GENERIC_WRITE, 0, NULL, _flags, 0, NULL);
	} else {
		handle = CreateFile(filename, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
	}

	return handle == INVALID_HANDLE_VALUE ? NULL : handle;
}

u64 _tellfile(void *handle) {
	u64 ofs;
	DWORD *_ofs = (DWORD*)&ofs;
	_ofs[1] = 0;
	_ofs[0] = SetFilePointer(handle, 0, &_ofs[1], FILE_CURRENT);
	return ofs;
}

int _seekfile(void *handle, u64 offset, int whence) {
	u64 ofs = (u64)offset;
	DWORD *_ofs = (DWORD*)&ofs;
//	printf("_seekfile %p, %d_%d\n", handle, _ofs[1], _ofs[0]);
	if (whence == SEEK_SET) {
		SetFilePointer(handle, _ofs[0], &_ofs[1], FILE_BEGIN);
	} else {
		SetFilePointer(handle, _ofs[0], &_ofs[1], FILE_END);
	}
	return 0;
}

int _readfile(void *handle, void *dst, int size) {
	DWORD ret;

//	printf("_readfile %p %d\n", handle, size);
	ReadFile(handle, dst, size, &ret, NULL);
//	printf("_readfile ret %d; %d\n", ret, GetLastError());
	return ret;
}

int _writefile(void *handle, void *src, int size) {
	DWORD ret;

//	printf("_writefile %p, %d\n", handle, size);
//	_seekfile(handle, _tellfile(handle));
	WriteFile(handle, src, size, &ret, NULL);
//	printf("_writefile ret %d\n", ret);
	return ret;
}

void _closefile(void *handle) {
	CloseHandle(handle);
}

#else

void *_openfile(const char *filename, int flags) {
	printf("_openfile %s %x\n", filename, flags);
	if (flags & O_WRONLY)
		 return fopen(filename, "wb");
	else return fopen(filename, "rb");
}

u64 _tellfile(void *handle) {
	return ftell(handle);
}

int _seekfile(void *handle, u64 offset, int whence) {
	return fseek(handle, offset, whence);
}

int _readfile(void *handle, void *dst, int size) {
	return fread(dst, 1, size, handle);
}

int _writefile(void *handle, void *src, int size) {
	return fwrite(src, 1, size, handle);
}

void _closefile(void *handle) {
	fclose(handle);
}

#endif

int detect(isoFile *iso) {
	char buf[2448];
	struct cdVolDesc *volDesc;

	if (isoReadBlock(iso, buf, 16) == -1) return -1;
	volDesc = (struct cdVolDesc *)(buf + 24);
	if (strncmp(volDesc->volID, "CD001", 5)) return 0;

	if (volDesc->rootToc.tocSize == 2048) {
		iso->type = ISOTYPE_CD;
	} else {
		iso->type = ISOTYPE_DVD;
	}

	return 1;
}

int _isoReadZtable(isoFile *iso) {
	void *handle;
	char table[256];
	int size;

	sprintf(table, "%s.table", iso->filename);
	handle = _openfile(table, O_RDONLY);
	if (handle == NULL) {
		printf("Error loading %s\n", table);
		return -1;
	}

	_seekfile(handle, 0, SEEK_END);
	size = (int)_tellfile(handle);
	iso->Ztable = (char*)malloc(size);
	if (iso->Ztable == NULL) {
		_closefile(handle);
		return -1;
	}

	_seekfile(handle, 0, SEEK_SET);
	_readfile(handle, iso->Ztable, size);
	_closefile(handle);

	iso->blocks = size / 6;

	return 0;
}

int _isoReadZ2table(isoFile *iso) {
	void *handle;
	char table[256];
	u32 *Ztable;
	int ofs;
	int size;
	int i;

	sprintf(table, "%s.table", iso->filename);
	handle = _openfile(table, O_RDONLY);
	if (handle == NULL) {
		printf("Error loading %s\n", table);
		return -1;
	}

	_seekfile(handle, 0, SEEK_END);
	size = (int)_tellfile(handle);
	Ztable = (u32*)malloc(size);
	if (Ztable == NULL) {
		_closefile(handle);
		return -1;
	}

	_seekfile(handle, 0, SEEK_SET);
	_readfile(handle, Ztable, size);
	_closefile(handle);

	iso->Ztable = (char*)malloc(iso->blocks*8);
	if (iso->Ztable == NULL) {
		free(Ztable);
		return -1;
	}

	ofs=16;
	for (i=0; i<iso->blocks; i++) {
		*(u32*)&iso->Ztable[i*8+0] = ofs;
		*(u32*)&iso->Ztable[i*8+4] = Ztable[i];
		ofs+= Ztable[i];
	}
	free(Ztable);

	return 0;
}

int _isoReadDtable(isoFile *iso) {
	int ret;
	int i;

	_seekfile(iso->handle, 0, SEEK_END);
	iso->dtablesize = (int)(_tellfile(iso->handle) - 16) / (iso->blocksize+4);
	iso->dtable = (u32*)malloc(iso->dtablesize*4);

	for (i=0; i<iso->dtablesize; i++) {
		_seekfile(iso->handle, 16+(iso->blocksize+4)*i, SEEK_SET);
		ret = _readfile(iso->handle, &iso->dtable[i], 4);
		if (ret < 4) {
			return -1;
		}
	}

	return 0;
}

int isoDetect(isoFile *iso) { // based on florin's CDVDbin detection code :)
	char buf[32];
	int len;

	iso->type = ISOTYPE_ILLEGAL;

	len = strlen(iso->filename);
	if (len >= 2) {
		if (!strncmp(iso->filename+(len-2), ".Z", 2)) {
			iso->flags = ISOFLAGS_Z;
			iso->blocksize = 2352;
			_isoReadZtable(iso);
			return detect(iso) == 1 ? 0 : -1;
		}
	}

	_seekfile(iso->handle, 0, SEEK_SET);
	_readfile(iso->handle, buf, 4);
	if (strncmp(buf, "BDV2", 4) == 0) {
		iso->flags = ISOFLAGS_BLOCKDUMP;
		_readfile(iso->handle, &iso->blocksize, 4);
		_readfile(iso->handle, &iso->blocks, 4);
		_readfile(iso->handle, &iso->blockofs, 4);
		_isoReadDtable(iso);
		return detect(iso) == 1 ? 0 : -1;
	} else
	if (strncmp(buf, "Z V2", 4) == 0) {
		iso->flags = ISOFLAGS_Z2;
		_readfile(iso->handle, &iso->blocksize, 4);
		_readfile(iso->handle, &iso->blocks, 4);
		_readfile(iso->handle, &iso->blockofs, 4);
		_isoReadZ2table(iso);
		return detect(iso) == 1 ? 0 : -1;
	} else {
		iso->blocks = 16;
	}

	// ISO 2048
	iso->blocksize = 2048; iso->offset = 0; iso->blockofs = 24;
	if (detect(iso) == 1) return 0;

	// RAW 2336
	iso->blocksize = 2336; iso->offset = 0; iso->blockofs = 16;
	if (detect(iso) == 1) return 0;

	// RAW 2352
	iso->blocksize = 2352; iso->offset = 0; iso->blockofs = 0;
	if (detect(iso) == 1) return 0;

	// RAWQ 2448
	iso->blocksize = 2448; iso->offset = 0; iso->blockofs = 0;
	if (detect(iso) == 1) return 0;

	// NERO ISO 2048
	iso->blocksize = 2048; iso->offset = 150*2048; iso->blockofs = 24;
	if (detect(iso) == 1) return 0;

	// NERO RAW 2352
	iso->blocksize = 2352; iso->offset = 150*2048; iso->blockofs = 0;
	if (detect(iso) == 1) return 0;

	// NERO RAWQ 2448
	iso->blocksize = 2448; iso->offset = 150*2048; iso->blockofs = 0;
	if (detect(iso) == 1) return 0;

	// ISO 2048
	iso->blocksize = 2048; iso->offset = -8; iso->blockofs = 24;
	if (detect(iso) == 1) return 0;

	// RAW 2352
	iso->blocksize = 2352; iso->offset = -8; iso->blockofs = 0;
	if (detect(iso) == 1) return 0;

	// RAWQ 2448
	iso->blocksize = 2448; iso->offset = -8; iso->blockofs = 0;
	if (detect(iso) == 1) return 0;

	iso->offset = 0;
	iso->blocksize = 2352;
	iso->type = ISOTYPE_AUDIO;
	return 0;

	return -1;
}

isoFile *isoOpen(const char *filename) {
	isoFile *iso;

	iso = (isoFile*)malloc(sizeof(isoFile));
	if (iso == NULL) return NULL;

	memset(iso, 0, sizeof(isoFile));
	strcpy(iso->filename, filename);

	iso->handle = _openfile(iso->filename, O_RDONLY);
	if (iso->handle == NULL) {
		printf("Error loading %s\n", iso->filename);
		free(iso);
		return NULL;
	}

	if (isoDetect(iso) == -1) return NULL;

	if (iso->flags & (ISOFLAGS_Z | ISOFLAGS_Z2 | ISOFLAGS_BLOCKDUMP)) {
	} else {
		_seekfile(iso->handle, 0, SEEK_END);
		iso->blocks = (u32)((_tellfile(iso->handle) - iso->offset) /
					        (iso->blocksize));
	}
/*
	if (strlen(IsoFile) > 3 &&
		!strncmp(IsoFile + (strlen(IsoFile) - 3), "I00", 3)) {
		int i;
		int llsn=0;

		for (i=0; i<8; i++) {
			IsoFile[strlen(IsoFile) - 1] = '0' + i;
			if (stat(IsoFile, &buf) == -1) break;
			cdIndexs[i].slsn = llsn;
			llsn+= buf.st_size / cdblocksize;
			cdIndexs[i].elsn = llsn-1;
			cdHandle[i] = fopen(IsoFile, "rb");
			if (cdHandle[i] == NULL) break;
		}

		if (i == 0) {
			SysMessage("Error loading %s\n", IsoFile);
			return -1;
		}
		fmode = 3;
	} else*//* {
		iso->handle = _openfile(iso->filename, O_RDONLY);
		if (iso->handle == NULL) {
			printf("Error loading %s\n", iso->filename);
			return NULL;
		}
	}*/

	printf("isoOpen: %s ok\n", iso->filename);
	printf("offset = %d\n", iso->offset);
	printf("blockofs = %d\n", iso->blockofs);
	printf("blocksize = %d\n", iso->blocksize);
	printf("blocks = %d\n", iso->blocks);
	printf("type = %d\n", iso->type);

	return iso;
}

isoFile *isoCreate(const char *filename, int flags) {
	isoFile *iso;
	char Zfile[256];

	iso = (isoFile*)malloc(sizeof(isoFile));
	if (iso == NULL) return NULL;

	memset(iso, 0, sizeof(isoFile));
	strcpy(iso->filename, filename);
	iso->flags = flags;
	iso->offset = 0;
	iso->blockofs = 24;
	iso->blocksize = CD_FRAMESIZE_RAW;
	iso->blocksize = 2048;

	if (iso->flags & (ISOFLAGS_Z | ISOFLAGS_Z2)) {
		sprintf(Zfile, "%s.table", iso->filename);
		iso->htable = _openfile(Zfile, O_WRONLY);
		if (iso->htable == NULL) {
			free(iso);
			return NULL;
		}
	}

	iso->handle = _openfile(iso->filename, O_WRONLY);
	if (iso->handle == NULL) {
		printf("Error loading %s\n", iso->filename);
		free(iso);
		return NULL;
	}
	printf("isoCreate: %s ok\n", iso->filename);
	printf("offset = %d\n", iso->offset);

	return iso;
}

int  isoSetFormat(isoFile *iso, int blockofs, int blocksize, int blocks) {
	iso->blocksize = blocksize;
	iso->blocks = blocks;
	iso->blockofs = blockofs;
	printf("blockofs = %d\n", iso->blockofs);
	printf("blocksize = %d\n", iso->blocksize);
	printf("blocks = %d\n", iso->blocks);
	if (iso->flags & ISOFLAGS_Z2) {
		if (_writefile(iso->handle, "Z V2", 4) < 4) return -1;
		if (_writefile(iso->handle, &blocksize, 4) < 4) return -1;
		if (_writefile(iso->handle, &blocks, 4) < 4) return -1;
		if (_writefile(iso->handle, &blockofs, 4) < 4) return -1;
	}
	if (iso->flags & ISOFLAGS_BLOCKDUMP) {
		if (_writefile(iso->handle, "BDV2", 4) < 4) return -1;
		if (_writefile(iso->handle, &blocksize, 4) < 4) return -1;
		if (_writefile(iso->handle, &blocks, 4) < 4) return -1;
		if (_writefile(iso->handle, &blockofs, 4) < 4) return -1;
	}

	return 0;
}

s32 MSFtoLSN(u8 *Time) {
	u32 lsn;

	lsn = Time[2];
	lsn+=(Time[1] - 2) * 75;
	lsn+= Time[0] * 75 * 60;
	return lsn;
}

void LSNtoMSF(u8 *Time, s32 lsn) {
	u8 m, s, f;

	lsn += 150;
	m = lsn / 4500; 		// minuten
	lsn = lsn - m * 4500;	// minuten rest
	s = lsn / 75;			// sekunden
	f = lsn - (s * 75);		// sekunden rest
	Time[0] = itob(m); Time[1] = itob(s); Time[2] = itob(f);
}

int _isoReadBlock(isoFile *iso, char *dst, int lsn) {
	u64 ofs = (u64)lsn * iso->blocksize + iso->offset;
	int ret;

//	printf("_isoReadBlock %d, blocksize=%d, blockofs=%d\n", lsn, iso->blocksize, iso->blockofs);
	memset(dst, 0, iso->blockofs);
	_seekfile(iso->handle, ofs, SEEK_SET);
	ret = _readfile(iso->handle, dst + iso->blockofs, iso->blocksize);
	if (ret < iso->blocksize) {
		printf("read error %d\n", ret);
		return -1;
	}

	return 0;
}
/*
int _isoReadBlockZ(isoFile *iso, char *dst, int lsn) {
	u32 pos, p;
	uLongf size;
	u8  Zbuf[CD_FRAMESIZE_RAW*2];
	int ret;

//	printf("_isoReadBlockZ %d, %d\n", lsn, iso->blocksize);
	pos = *(unsigned long*)&iso->Ztable[lsn * 6];
	p = *(unsigned short*)&iso->Ztable[lsn * 6 + 4];
//	printf("%d, %d\n", pos, p);
	_seekfile(iso->handle, pos, SEEK_SET);
	ret = _readfile(iso->handle, Zbuf, p);
	if (ret < p) {
		printf("error reading block!!\n");
		return -1;
	}

	size = CD_FRAMESIZE_RAW;
	uncompress(dst, &size, Zbuf, p);

	return 0;
}

int _isoReadBlockZ2(isoFile *iso, char *dst, int lsn) {
	u32 pos, p;
	uLongf size;
	u8  Zbuf[16*1024];
	int ret;

//	printf("_isoReadBlockZ2 %d, %d\n", lsn, iso->blocksize);
	pos = *(u32*)&iso->Ztable[lsn*8];
	p = *(u32*)&iso->Ztable[lsn*8+4];
//	printf("%d, %d\n", pos, p);
	_seekfile(iso->handle, pos, SEEK_SET);
	ret = _readfile(iso->handle, Zbuf, p);
	if (ret < p) {
		printf("error reading block!!\n");
		return -1;
	}

	size = iso->blocksize;
	uncompress(dst + iso->blockofs, &size, Zbuf, p);

	return 0;
}
*/
int _isoReadBlockD(isoFile *iso, char *dst, int lsn) {
	int ret;
	int i;

//	printf("_isoReadBlockD %d, blocksize=%d, blockofs=%d\n", lsn, iso->blocksize, iso->blockofs);
	memset(dst, 0, iso->blockofs);
	for (i=0; i<iso->dtablesize;i++) {
		if (iso->dtable[i] != lsn) continue;

		_seekfile(iso->handle, 16+i*(iso->blocksize+4)+4, SEEK_SET);
		ret = _readfile(iso->handle, dst + iso->blockofs, iso->blocksize);
		if (ret < iso->blocksize) return -1;

		return 0;
	}
	printf("block %d not found in dump\n", lsn);

	return -1;
}

int isoReadBlock(isoFile *iso, char *dst, int lsn) {
	int ret;

	if (lsn > iso->blocks) {
		printf("isoReadBlock: %d > %d\n", lsn, iso->blocks);
		return -1;
	}
/*	if (iso->flags & ISOFLAGS_Z) {
		ret = _isoReadBlockZ(iso, dst, lsn);
	} else
	if (iso->flags & ISOFLAGS_Z2) {
		ret = _isoReadBlockZ2(iso, dst, lsn);
	} else
*/	if (iso->flags & ISOFLAGS_BLOCKDUMP) {
		ret = _isoReadBlockD(iso, dst, lsn);
	} else
		ret = _isoReadBlock(iso, dst, lsn);
	if (ret == -1) return ret;

	if (iso->type == ISOTYPE_CD) {
		LSNtoMSF(dst+12, lsn);
		dst[15] = 2;
	}

	return 0;
}


int _isoWriteBlock(isoFile *iso, u8 *src, int lsn) {
	u64 ofs = (u64)lsn * iso->blocksize + iso->offset;
	int ret;

//	printf("_isoWriteBlock %d (ofs=%d)\n", iso->blocksize, ofs);
	_seekfile(iso->handle, ofs, SEEK_SET);
	ret = _writefile(iso->handle, src + iso->blockofs, iso->blocksize);
//	printf("_isoWriteBlock %d\n", ret);
	if (ret < iso->blocksize) return -1;

	return 0;
}
/*
int _isoWriteBlockZ(isoFile *iso, u8 *src, int lsn) {
	u32 pos;
	uLongf size;
	u8  Zbuf[CD_FRAMESIZE_RAW];
	int ret;

//	printf("_isoWriteBlockZ %d\n", iso->blocksize);
	size = 2352;
	compress(Zbuf, &size, src, 2352);
//	printf("_isoWriteBlockZ %d\n", size);

	pos = (u32)_tellfile(iso->handle);
	ret = _writefile(iso->htable, (u8*)&pos, 4);
	if (ret < 4) return -1;
	ret = _writefile(iso->htable, (u8*)&size, 2);
	if (ret < 2) return -1;

	ret = _writefile(iso->handle, Zbuf, size);
//	printf("_isoWriteBlockZ %d\n", ret);
	if (ret < size) {
		printf("error writing block!!\n");
		return -1;
	}

	return 0;
}

int _isoWriteBlockZ2(isoFile *iso, u8 *src, int lsn) {
	uLongf size;
	u8  Zbuf[1024*16];
	int ret;

//	printf("_isoWriteBlockZ %d\n", iso->blocksize);
	size = 1024*16;
	compress(Zbuf, &size, src + iso->blockofs, iso->blocksize);
//	printf("_isoWriteBlockZ %d\n", size);

	ret = _writefile(iso->htable, (u8*)&size, 4);
	if (ret < 4) return -1;
	ret = _writefile(iso->handle, Zbuf, size);
//	printf("_isoWriteBlockZ %d\n", ret);
	if (ret < size) {
		printf("error writing block!!\n");
		return -1;
	}

	return 0;
}
*/
int _isoWriteBlockD(isoFile *iso, u8 *src, int lsn) {
	int ret;

//	printf("_isoWriteBlock %d (ofs=%d)\n", iso->blocksize, ofs);
	ret = _writefile(iso->handle, &lsn, 4);
	if (ret < 4) return -1;
	ret = _writefile(iso->handle, src + iso->blockofs, iso->blocksize);
//	printf("_isoWriteBlock %d\n", ret);
	if (ret < iso->blocksize) return -1;

	return 0;
}

int isoWriteBlock(isoFile *iso, char *src, int lsn) {
	int ret;

/*	if (iso->flags & ISOFLAGS_Z) {
		ret = _isoWriteBlockZ(iso, src, lsn);
	} else
	if (iso->flags & ISOFLAGS_Z2) {
		ret = _isoWriteBlockZ2(iso, src, lsn);
	} else
*/	if (iso->flags & ISOFLAGS_BLOCKDUMP) {
		ret = _isoWriteBlockD(iso, src, lsn);
	} else
		ret = _isoWriteBlock(iso, src, lsn);
	if (ret == -1) return ret;

	return 0;
}

void isoClose(isoFile *iso) {
	if (iso->handle) {
		_closefile(iso->handle);
	}
	if (iso->htable) {
		_closefile(iso->htable);
	}
	free(iso);
}