#include <zlib.h>
#include <libBAG.h>
#include "ndsheader.h"

/*==========================================
Misc related functions
==========================================*/
static int _nds_checkID(const char *ID){
	if(!strcasecmp(ID, "####") || !strcasecmp(ID, "ENG0"))/*Supercard GAME.nds id*/
		return ROM_HB;//homebrew	
	return ROM_CM;//commercial
}

static int _nds_getLangPos(int language){
	switch(language){
		default://eng
			return LANG_ENG;
		break;
		case 0://jap
			return LANG_JAP;
		break;
		case 2://french
			return LANG_FRE;
		break;
		case 3://german
			return LANG_GER;
		break;
		case 4://italian
			return LANG_ITN;
		break;
		case 5://spain
			return LANG_SPN;
		break;
	}
	return LANG_ENG;//return english by default
}

static void _nds_makeIcon(unsigned short *dest, const unsigned char *data, const unsigned short *pal){
	int yy = 0, index = 0, y = 0, x = 0, xz = 0, yz = 0;
	do{
		x = 0;
		y = 0;
		do{
			xz = ((y>>3) << 3) + x;
			yz =((yy << 3) + (y&7)) << 5;
			dest[(xz + 1) + yz] = (unsigned short)pal[(data[ index ] >> 4) & 15 ];
			dest[ (xz) + yz] = (unsigned short)pal[ data[ index ] & 15 ];
			
			index++;
			y+=((x+=2)>>3);
			x = x&7;
		}while(y < 32);	
	}while(yy++ <  3);
}



/*==========================================
Standard Rom functions
==========================================*/
static FILE *_nds_getFile(struct _ndsrom *Rom){
	return Rom->romFile;
}


static int _nds_getBannerOffset(struct _ndsrom *Rom){
	return Rom->banner_offset;
}

static void _nds_setBannerOffset(struct _ndsrom *Rom, int offset){
	Rom->banner_offset = offset;
}


/*Retrieve info from rom header*/
static char *_nds_getID(struct _ndsrom *Rom){
	return Rom->Header.nds.game_code;
}


static int _nds_getType(struct _ndsrom *Rom){
	return Rom->Header.nds.type;
}

static void _nds_setType(struct _ndsrom *Rom, int type){
	Rom->Header.nds.type = type;
}


static char *_nds_getBanner(struct _ndsrom *Rom){
	return Rom->Header.nds.text;
}

static char *_nds_getTitle(struct _ndsrom *Rom){
	return Rom->Header.nds.game_title;
}


/*Read data into header*/
static char _nds_readTitle(struct _ndsrom *Rom){
	if(_nds_getFile(Rom) == NULL)
		return 0;
		
	fseek(_nds_getFile(Rom), 0, SEEK_SET);
	fread(_nds_getTitle(Rom), 1, NDS_TITLE_LEN, _nds_getFile(Rom) );
	return 1;
}

static char _nds_readID(struct _ndsrom *Rom){
	if(_nds_getFile(Rom) == NULL)
		return 0;
		
	fseek(_nds_getFile(Rom), NDS_TITLE_LEN, SEEK_SET);
	
	char *ID = _nds_getID(Rom);
	fread( ID, 1, NDS_ID_LEN, _nds_getFile(Rom));
	ID[NDS_ID_LEN] = '\0';
		
	_nds_setType(Rom, _nds_checkID(_nds_getID(Rom)));//determine if rom is homebrew or commercial
	return 1;
}



static char _nds_readBannerOffset(struct _ndsrom *Rom)//used for both reading game text and icon
{
	if(_nds_getBannerOffset(Rom) == 0){
		int banner_offset = 0;
		fseek(_nds_getFile(Rom), BANNER_DATA, SEEK_SET);
		fread(&banner_offset, 1, sizeof(int), _nds_getFile(Rom));
		
		if(banner_offset == 0)//no banner to be found
			return -1;
			
		_nds_setBannerOffset(Rom, banner_offset);
	}
	return 1;
}


static char _nds_readBanner(struct _ndsrom *Rom, int language){
	if(_nds_getFile(Rom) == NULL)
		return 0;
		
	if(_nds_readBannerOffset(Rom) == -1)
		return 0;
	
	int seekpos = _nds_getLangPos(language);
	//read language text		
	fseek(_nds_getFile(Rom), _nds_getBannerOffset(Rom) + seekpos, SEEK_SET);
	char *text = _nds_getBanner(Rom);
	fread(text, 1, NDS_BANNER_SIZE, _nds_getFile(Rom));
	
	//convert text
	int temp = 0;
	for(; temp < (NDS_BANNER_SIZE>>1); temp++)
		text[temp] = text[temp << 1];
			
	return 1;
}

static int _nds_readIconEx(struct _ndsrom *Rom, unsigned short *dest, unsigned short transp){
	if(_nds_getFile(Rom) == NULL)
		return -1;

	if(Rom->Header.Icon == NULL){
		Rom->Header.Icon = (int*)calloc(sizeof(int), NDS_ICON_SIZE);
		if(Rom->Header.Icon == NULL)
			return -1;
	}
	else
		memset(Rom->Header.Icon, 0, NDS_ICON_SIZE);

	if(_nds_readBannerOffset(Rom) == -1)
		return -1;

	//read palette and data in one go
	fseek(_nds_getFile(Rom), (_nds_getBannerOffset(Rom) + TILE_DATA), SEEK_SET);
	fread(Rom->Header.Icon, sizeof(int), NDS_ICON_SIZE, _nds_getFile(Rom));	

	//construct icon
	unsigned short *pal = (unsigned short*)&Rom->Header.Icon[PAL_DATA];
	if(transp > 0)//if a transparent color is set, then set the palette to that color, otherwise use defualt transparent color
		pal[0] = transp;
	
	if(dest != NULL)
		_nds_makeIcon(dest, (unsigned char*)Rom->Header.Icon, pal);
		
	return pal[0];
}

static char _nds_createIconEx(struct _ndsrom *Rom, GFXObj_t *Object, unsigned short transp){
	int pal = _nds_readIconEx(Rom, BAG_Display_GetGfxBuf(Object), transp);
	if(pal != -1){
		(*BAG_Display_GetGfxTranspCol(Object))  = pal;
		return 1;
	}
		
	return 0;
}


static char _nds_createIcon(struct _ndsrom *Rom, GFXObj_t *Object)//load rom with default transparency color
{
	return _nds_createIconEx(Rom, Object, 0);
}



inline char NDS_OpenFile(const char *filename, struct _ndsrom *Rom){
	if(filename != NULL)
		Rom->romFile = fopen(filename, "rb");

	if(_nds_getFile(Rom) == NULL)
		return 0;
		
	//function definitions
	Rom->readTitle =		&_nds_readTitle;
	Rom->readID =			&_nds_readID;
	Rom->readBanner =		&_nds_readBanner;
	Rom->createIconEx =		&_nds_createIconEx;
	Rom->createIcon =		&_nds_createIcon;
	Rom->readHeader =		&Rom_ReadHeader;	
	
	
	Rom->getID = 			&_nds_getID;
	Rom->getType = 			&_nds_getType;
	Rom->getBanner = 		&_nds_getBanner;
	Rom->getTitle =			&_nds_getTitle;
	
	Rom->closeRom =			&Rom_CloseFile;
	
	_nds_setBannerOffset(Rom, 0);
	return 1;
}


/*==========================================
Nitro Zip header reading
==========================================*/

static void _nzip_setFooterStart(struct _ndsrom *Rom, int start){
	Rom->footerStart = start;
}

static int _nzip_getFooterStart(struct _ndsrom *Rom){
	return Rom->footerStart;
}


static int _nzip_fseek(struct _ndsrom *Rom, int offset, int origin){
	if(origin == SEEK_SET){
		int temp = offset;
		origin = SEEK_END;
		offset = _nzip_getFooterStart(Rom) - temp;
	}	
	return fseek(_nds_getFile(Rom), -offset, origin);
}

static unsigned short *_nzip_getReserved(struct _ndsrom *Rom){
	return (unsigned short*)Rom->Header.Reserved;
}

static char _nzip_readReserved(struct _ndsrom *Rom){
	if(_nds_getFile(Rom) == NULL)
		return 0;
		
	_nzip_fseek(Rom, 0, SEEK_SET);
	//check if there is an icon
	unsigned short *reserved = _nzip_getReserved(Rom);
	fread(reserved, 1, NZIP_RESERVED, _nds_getFile(Rom));
	return 1;
}

static char _nzip_readTitle(struct _ndsrom *Rom){
	if(_nds_getFile(Rom) == NULL)
		return 0;

	//seek to game title
	int pos = NZIP_RESERVED + NDS_BANNER_SIZE;
	_nzip_fseek(Rom, pos, SEEK_SET);
	fread(_nds_getTitle(Rom), 1, NDS_TITLE_LEN, _nds_getFile(Rom));
	return 1;
}

static char _nzip_readID(struct _ndsrom *Rom){
	if(_nds_getFile(Rom) == NULL)
		return 0;
		
	int pos = NZIP_RESERVED + NDS_BANNER_SIZE + NDS_TITLE_LEN;
	_nzip_fseek(Rom, pos, SEEK_SET);
	char *ID = _nds_getID(Rom);
	fread( ID, 1, NDS_ID_LEN, _nds_getFile(Rom));
	ID[NDS_ID_LEN] = '\0';
		
	_nds_setType(Rom, _nds_checkID(_nds_getID(Rom)));//determine if rom is homebrew or commercial
	return 1;
}

static char _nzip_readBanner(struct _ndsrom *Rom, int lang){
	if(_nds_getFile(Rom) == NULL)
		return 0;
		
	int pos = NZIP_RESERVED;
	_nzip_fseek(Rom, pos, SEEK_SET);
		
	fread(_nds_getBanner(Rom), 1, NDS_BANNER_SIZE, _nds_getFile(Rom));
	return 1;
}


static int _nzip_readIcon(struct _ndsrom *Rom, unsigned short *dest, unsigned short transp){
	if(_nds_getFile(Rom) == NULL)
		return -1;
		
	unsigned short *Reserved = _nzip_getReserved(Rom);
	if(Reserved[NZIP_VER] == 0)//reserved hasn't been read yet
		if(_nzip_readReserved(Rom) != 1)
			return -1;
		
	if(Reserved[NZIP_HASICON] == 0)
		return -1;

	//read the icon now
	_nzip_fseek(Rom, NZIP_RESERVED + sizeof(NDSHEADER), SEEK_SET);//fseek to icon location
	
	if(Reserved[NZIP_VER] == 1){
		//NZIP v1 stored icons as raw 16 bit values
		fread(dest, sizeof(unsigned short), NZIP_ICONSIZE_V1, _nds_getFile(Rom));	
		return Rom->Header.Reserved[NZIP_ICONTRANS];
	}
	else if(Reserved[NZIP_VER] == 2){
		//NZIP v2 stores icons as native nds icons for smallest read size
		if(Rom->Header.Icon == NULL){
			Rom->Header.Icon = (int*)calloc(sizeof(int), NDS_ICON_SIZE);
			if(Rom->Header.Icon == NULL)
				return 0;
		}
		
		fread(Rom->Header.Icon, sizeof(int), NDS_ICON_SIZE, _nds_getFile(Rom));	
		//construct icon
		unsigned short *pal = (unsigned short*)&Rom->Header.Icon[PAL_DATA];
		if(transp > 0)//if a transparent color is set, then set the palette to that color, otherwise use defualt transparent color
			pal[0] = transp;
		
		if(dest != NULL)
			_nds_makeIcon(dest, (unsigned char*)Rom->Header.Icon, pal);
			
		return pal[0];
	}

	return -1;
}

static char _nzip_createIconEx(struct _ndsrom *Rom, GFXObj_t *Object, unsigned short transp){
	int pal = _nzip_readIcon(Rom, BAG_Display_GetGfxBuf(Object), transp);
	if(pal != -1){
		(*BAG_Display_GetGfxTranspCol(Object))  = pal;
		return 1;
	}
	return 0;
}

static char _nzip_createIcon(struct _ndsrom *Rom, GFXObj_t * Object){
	 return _nzip_createIconEx(Rom, Object, 0);
}


char NZIP_OpenFile(const char *filename, struct _ndsrom *Rom){
	if(filename != NULL)
		Rom->romFile = fopen(filename, "rb");

	if(_nds_getFile(Rom) == NULL)
		return 0;
			
	_nzip_setFooterStart(Rom, sizeof(NZIPFOOTER_SIZE));
	//function definitions
	Rom->readTitle =		&_nzip_readTitle;
	Rom->readID =			&_nzip_readID;
	Rom->readBanner =		&_nzip_readBanner;
	Rom->createIconEx =		&_nzip_createIconEx;
	Rom->createIcon =		&_nzip_createIcon;
	Rom->readHeader =		&Rom_ReadHeader;	
	
	
	Rom->getID = 			&_nds_getID;
	Rom->getType = 			&_nds_getType;
	Rom->getBanner = 		&_nds_getBanner;
	Rom->getTitle =			&_nds_getTitle;
	
	Rom->closeRom =			&Rom_CloseFile;
	
	Rom->readReserved=		&_nzip_readReserved;
	Rom->zipReserved =		&_nzip_getReserved;
	
	_nds_setBannerOffset(Rom, 0);
	return 1;
}


/*==========================================
Auto opening of files
==========================================*/
char Rom_OpenFile(const char *filename, struct _ndsrom *Rom){
	//if file is already opened, don't do anything
	if(_nds_getFile(Rom) != NULL)
		return 1;

	Rom->romFile = fopen(filename, "rb");
	if(_nds_getFile(Rom) == NULL)
		return 0;
		
	Rom->Header.Icon = NULL;
	//check if file is compressed or not via magic number of gzip
	unsigned char magic_number[2];
	fread(magic_number, sizeof(unsigned char), 2, _nds_getFile(Rom));
	
	if(magic_number[0] == 31 && magic_number[1] == 139)
		return NZIP_OpenFile( NULL, Rom);

	return NDS_OpenFile( NULL, Rom);
}

void Rom_CloseFile(struct _ndsrom *Rom){
	if(_nds_getFile(Rom) != NULL){
		fclose(_nds_getFile(Rom));
		
		if(Rom->Header.Icon != NULL)
			free(Rom->Header.Icon);
	}
	memset(Rom, 0, sizeof(struct _ndsrom));
	Rom->romFile = NULL;
	Rom->Header.Icon = NULL;
}

char Rom_ReadHeader(struct _ndsrom *Rom, GFXObj_t *Icon, int language){
	int err = 0;

	err = Rom->readTitle(Rom);
	if(err != 1)
		return -1;
		
	err = Rom->readID(Rom);
	if(err != 1)
		return -2;

	err = Rom->readBanner(Rom, language);
	if(err != 1)
		return -3;
		
	err = Rom->createIcon(Rom, Icon);
	if(err != 1)
		return -4;
	
	return 1;
}




/*==========================================
Nitro Zip Extraction and Compression
==========================================*/
/*appends bits of the nds header to the end of the zip file
Data appended:
Reserved data
[0] - zip identification version
[1] - icon check, if the nds file does have an icon
[2] - transparent color of the icon

-game title: 12 bytes
-game code: 4 bytes
-one language of the title as specified: 256 bytes
-game icon as raw 16 bit data
*/

static int append_nds_header(const char *inputFilename, const char *outputFilename, int language){
	/*collect header info from input*/
	struct _ndsrom Input;
	memset(&Input, 0, sizeof(struct _ndsrom));
	
	if(NDS_OpenFile( inputFilename, &Input) != 1)
		return 0;

	int err = Input.readTitle(&Input);
	if(err != 1)
		return -1;
		
	err = Input.readID(&Input);
	if(err != 1)
		return -2;

	err = Input.readBanner(&Input, language);
	if(err != 1)
		return -3;	
	
	
	int pal = _nds_readIconEx(&Input, NULL, 0);
	
	
	/*Open output file to append data to*/
	FILE *DSZIP = fopen(outputFilename, "a+");
	if(!DSZIP)
		return 0;
		
	
	Input.Header.Reserved[NZIP_VER] = NZIP_VERSION;
	Input.Header.Reserved[NZIP_HASICON] = 0;
	if(pal > -1){
		Input.Header.Reserved[NZIP_HASICON] = 1;
		Input.Header.Reserved[NZIP_ICONTRANS] = pal;
	}
	
	//now write the data to end of zip file
	fwrite(&Input.Header, 1, sizeof(NZIPFOOTER), DSZIP);
	//write padded data (expected  header size from nzip 1)
	char padding[NZIP_ICONPAD];
	memset(padding, 0, sizeof(padding));
	fwrite(&padding, 1, NZIP_ICONPAD, DSZIP);
	fclose(DSZIP);
	
	Rom_CloseFile(&Input);
	return 1;
}


int BAG_DSZIP_Deflate(const char *inputFilename, const char *outputFilename, int language, char *mode){
    int err = Z_OK;
    //open output file
	gzFile outFile = gzopen(outputFilename, mode);
    if(!outFile){
        printf("failed to open output\n");
        gzerror(outFile, &err);
        if(err != Z_OK)
            return err;
    }    
    
    
	FILE *inFile = fopen(inputFilename, "rb");
    if(!inFile){
		gzclose(outFile);
		printf("failed to open input\n");
        return -99;
	}
        
    //grab file size
    int FileSize=0;
    fseek (inFile , 0 , SEEK_END);
    FileSize = ftell (inFile);
	fseek(inFile, 0L, SEEK_SET);

    int readSize = 0;
	
	//allocate data buffer
	char *buffer = (char*)calloc( BUFSIZE, sizeof(char));
	if(buffer == NULL){
		fclose(inFile);
		gzclose(outFile);
		return -98;
	}
	
	do{
		readSize = fread(buffer, 1, BUFSIZE, inFile);
		if(readSize == 0)
			break;
	
		int writeSize = gzwrite(outFile, buffer, readSize);
        if(writeSize != readSize){
            gzerror(outFile, &err);
            printf("error! %d\n", err);
            break;
        }
    }while(readSize > 0);
	
	free(buffer);
    err = gzclose(outFile);
    fclose(inFile);
	
	append_nds_header(inputFilename, outputFilename, language);
	return err;
}


//get size of zipped data
int BAG_DSZIP_GetDataSize(const char * filename){
	int FileSize = 0;
	FILE *testFile = fopen(filename, "rb");
    if(testFile)
    {
        fseek (testFile , 0 , SEEK_END);
        fseek (testFile , -4 - sizeof(NZIPFOOTER), SEEK_CUR);
        fread (&FileSize,4, 1,testFile);
        fclose(testFile);
    }
	return FileSize;
}


int BAG_DSZIP_Inflate(const char *inputFilename, const char *outputFilename){
	int err = Z_OK;
	  
	 //grab file size which is stored in the last 4 bytes of a gz file
	int FileSize=BAG_DSZIP_GetDataSize(inputFilename);
    if(FileSize == 0)
        return Z_ERRNO;
        
	//open the input file
	gzFile inFile = gzopen(inputFilename, "rb");
	if(!inFile){
		printf("failed to open input\n");
        gzerror(inFile, &err);
        if(err != Z_OK)
            return err;
	}
	
	//open output file
	FILE *outFile = fopen(outputFilename, "wb");
	if(!outFile){
		printf("failed to open output\n");
		gzclose(inFile);
		return -99;
	}
    
    int readSize = 0, writeSize = 0, result=0, total_read = 0;
	
	//allocate buffer
	char *buffer = (char*)calloc( BUFSIZE, sizeof(char));
	if(buffer == NULL){
		fclose(outFile);
		gzclose(inFile);
		return -98;
	}
	
	//decompression loop
	do{
		readSize = gzread(inFile, buffer, BUFSIZE);
		total_read += readSize;
		
		//make sure to not read the nzip footer data
		if(total_read > FileSize)
			readSize -= (total_read - FileSize);
		
		if(readSize < 0){
			gzerror(inFile, &result);
			break;
		}
		else if(readSize == 0){
			result = EOF;
			break;
		}	
		if((writeSize = fwrite(buffer, 1, readSize, outFile)) != writeSize){
			result = Z_ERRNO;
			break;
		}
	}while(readSize > 0 && total_read < FileSize );
	
	//cleanup
	free(buffer);
	result = gzclose(inFile);
	fclose(outFile);
  
    return result;
}
