/*  Tilem, TI-Linux Emulator
 *  Copyright (C) 2001 Solignac Julien <x1cygnus@xcalc.org>
 *  Portions copyright (C) 2004 Benjamin Moody <benjamin@ecg.mit.edu>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/* *** Ti .8?p File Format ***
 * data is indexed by models {3, p, 6}
 00 - 07 {"**TI83**", "**TI83F*", "**TI86**"}
 08 - 0B 1A 0A 00 (for the 85/86: 0C instead of 0A?)
 0C - 34 Comment
 35 - 36 file length - $39  =  Size of all data in the .8?? file, from byte $37
 to last byte before the checksum

 37 - 38 Variable Header length: {0B, 0D} 00
 39 - {43, 45} Variable Header
    |- 39 - 3A Length of data (word)
    |- 3B      program type : 5 (6 for protected)
    |- 3C - 43 program name (0-filled) 
If 8p:
    \- 44 - 45 program version (can be 00 00)

 46 - 47 Length of data
 48 - 49 Length of program
 4A - .. Program data
 .. - .. checksum (word)

 * All lengths are words, with LSB first
 * Length of program = the length of all the program data
 * Length of data = length of program + 2
 * The checksum is one word, the sum of all the bytes from byte 37 to
   the last byte before the checksum, modulo 2^16 - 1 to fit in one word
 *** Ti .8?p File Format **/

#include "../../config.h"

#include<stdio.h>
#include<string.h>
#include<stdlib.h>

#include<glib.h>
/* >>> dependencies */
#include "main.h"
#include "core/Z80.h"
#include "tools.h"
/* dependencies <<< */

/* >>> Imported */
#include "core/dep/link.h"
#include "core/dep/inter.h"
/* Imported <<< */


/* >>> Private */
struct alff {
	char *calcs;
	char *str;
	byte backup_type;
};
/* Private <<< */

static byte **reverse(byte **list);

/* >>> Resources */
static const byte ticomment[42] = "File created with Tilem\0                 ";

static struct alff headers[] = {
	{"ps4z", "**TI83F*\x1A\x0A", 0x13},
	{"3ps4z", "**TI83**\x1A\x0A", 0x13},
	{"23ps4z", "**TI82**\x1A\x0A", 0x0f},
	{"7", "**TI73**\x1A\x0A", 0x13},
	{"6", "**TI86**\x1A\x0A", 0x0f},
	{"6", "**TI86**\x1A\x0C", 0x0f},
	{"56", "**TI85**\x1A\x0A", 0x1d},
	{"56", "**TI85**\x1A\x0C", 0x1d}
};
#define num_formats sizeof(headers) / sizeof(struct alff)
/* Resources <<< */


/* >>> Exported */
void unravel(byte **list)
{
	int i;

	i = 0;
	while (*(list + i)) {
		free(*(list + i));
		*(list + i) = 0;
		i++;
	}
	free(list);
}


void dumpfile(char *filename, byte *logi, byte *data)
{
	extern struct hardware *hw;
	/* extern struct hwmodel hwmodels[nummodels]; */

	byte cid;
	FILE *dump;
	int filesize, x;
	word checksum, vsize;

	if (!(dump = fopen(filename, "wb"))) {
		fprintf(stderr, "unable to open file, aborting save\n");
		return;
	}

	for (cid = 0; cid < num_formats; cid++) {
		if (strchr(headers[cid].calcs, hw->model))
			break;
	}

	filesize = *data + (*(data + 1) << 0x08);
	vsize = *logi + (*(logi + 1) << 0x08);

/* 00 - 07 {"**TI83**", "**TI83F*"} */
/* TODO: check if 0C case is really true */
/* 08 - 0B 1A 0A 00 (for the 85/86: 0C instead of 0A) */
	fwrite(headers[cid].str, 1, 11, dump);
/* 0C - 34 Comment */
	fwrite(ticomment, 1, sizeof(ticomment), dump);

/* 35 - 36 file length - $39  =  Size of all data in the .8?? file, from byte $37 */
/* varh size | varh | data size | data | chk */
	putc((2 + vsize + 2 + filesize + 2) & 0xFF, dump);
	putc((2 + vsize + 2 + filesize + 2) >> 0x08, dump);

/* 37 - 38 Variable Header length */
/* 39 - {43, 45} Variable Header */
	fwrite(logi, 1, vsize + 2, dump);

/* The checksum is one word, the sum of all the bytes from byte 37 to
   the last byte before the checksum, modulo 2^16 - 1 to fit in one word */
	checksum = 0;
	for (x = 0; x < vsize + 2; x++)
		checksum += *(logi + x);

/* <48 - 49 Length of data> */
	checksum += ((filesize + 2) & 0xFF) + ((filesize + 2) >> 0x08);

/* <4A - .. Program data> */
	for (x = 0; x < filesize + 2; x++)
		checksum += *(data + x);

/* 48 - 49 Length of data */
	putc((filesize + 2) & 0xFF, dump);
	putc((filesize + 2) >> 0x08, dump);

/* 4A - .. Program data */
	fwrite(data, 1, 2 + filesize, dump);

/* .. - .. checksum (word) */
	putc(checksum & 0xFF, dump);
	putc(checksum >> 0x08, dump);

	fclose(dump);
}

void upload(char *file)
{
	G_LOCK_EXTERN(running);
	G_LOCK_EXTERN(runctrl);
	extern struct hardware *hw;
	/* extern struct hwmodel hwmodels[nummodels]; */

	FILE *logi;
	int ft, /*cid,*/ i, c;
	word hsize, vsize, chk, tchk;
	byte sread[11], *data, **list;
	byte backup_mode = 0;


	if (check_extension(file, ".app")
	    || check_extension(file, ".hex")
	    || check_extension(file, ".8xk")
	    || check_extension(file, ".73k")
	    || check_extension(file, ".8xu")
	    || check_extension(file, ".73u")) {

		G_LOCK(running);
		G_LOCK(runctrl);

		if (hw->xloadapp) {
			(hw->xloadapp)(file);
		}
		else
			fprintf(stderr, "Calculator does not support Flash!\n");

		G_UNLOCK(runctrl);
		G_UNLOCK(running);

		return;
	}

	if (!(list = malloc(20 * sizeof(byte*)))) {
		fprintf(stderr, "Insufficient memory, aborting\n");
		return;
	}
	for (i = 0; i < 20; i++)
		*(list + i) = 0;
	c = 0;

	if (!(logi = fopen(file, "rb"))) {
		fprintf(stderr, "unable to open file, aborting\n");
		return;
	}
	fread(sread, 1, 11, logi);	/* : 11 */

	for (ft = 0; ft < num_formats; ft++) {
		if ((!strcmp((char*)sread, headers[ft].str)) &&
		    strchr(headers[ft].calcs, hw->model))
			break;
	}

	if (ft == num_formats) {
		fprintf(stderr, "Unknown or invalid file format, aborting\n");
		fclose(logi);
		free(list);
		return;
	}

	/* seek to variable header*/
	fseek(logi, 0x37 + 3, SEEK_SET);


	tchk = 0;
	while (!(feof(logi))) {
		fseek(logi, - 3, SEEK_CUR);

		fread(sread, 1, 5, logi);	/* : 0x37 + 4 */

		/* variable header size */
		vsize = sread[0] + (sread[1] << 8);
		/* data block size */
		if (backup_mode)
			/* If it's a backup, there are three parts to
			   the data (four on the 86.)  The "variable
			   length" is just the length of the first
			   part.  Subsequent parts can be handled
			   essentially as very long variable headers
			   with no data attached. */
			hsize = 0;
		else {
			hsize = 2 + sread[2] + (sread[3] << 8);

			if (sread[4] == headers[ft].backup_type) {
				backup_mode = 1;
			}
		}

	/* Variable Header ------------------------------------\
	 * 83 | 0       | 2        | -------- | 3    | ------- | 11
	 * 8x | 0       | 2        | -------- | 3    | 11      | 13
	 * 86 | 0       | 2        | 3        | 4    | ------- | 5-12
	 *    | varsize | var type | name len | name | version | */

	/* File Header --------------------------------------------------\
	 * 83 | 0          | 2        | 4    | -------- | 5    | ------- | 13      
	 * 8x | 0          | 2        | 4    | -------- | 5    | 13      | 15
	 * 86p| 0          | 2        | 4    | 5        | 6    | ------- | 14 		Ti-86 prgm
	 * 86o| 0          | 2        | 4    | 5        | 6    | ------- | 714		Ti-86 other (eg string)
	 *    | header len | data len | type | name len | name | version | */

		/* | id-len, varh, chk | (<< id) data, chk | */
		if (!(data = malloc(2 + 2 + vsize + 2 + hsize + 2))) {
			fprintf(stderr, "Insufficient memory, aborting\n");
			unravel(list);
			fclose(logi);
			return;
		}

		/* seek to varh */
		fseek(logi, - 5, SEEK_CUR);
		fread(data + 2, 1, vsize + 2, logi);
		fread(data + 2 + (vsize + 2) + 2, 1, hsize, logi);

		chk = 0;
		for (i = 0; i < vsize; i++) {
/*			printf("vh %02x\n", *(data + 4 +i)); */
			chk += *(data + 4 + i);
		}

		*(data + 4 + vsize) = chk & 0xFF;
		*(data + 4 + vsize + 1) = chk >> 8;
		tchk += chk;
		tchk += vsize & 0xFF;
		tchk += vsize >> 8;

		chk = 0;

/*		printf("dtb %02x\n", *(data + 4 + vsize + 2 + 2));
		printf("dtb %02x\n", *(data + 4 + vsize + 2 + 3));*/
		for (i = 2; i < hsize; i++)
			chk += *(data + 4 + vsize + 2 + i);

/*		printf("dte %02x\n", *(data + 4 + vsize + 2 + i - 2));
		printf("dte %02x\n", *(data + 4 + vsize + 2 + i - 1));
		printf("%X\n", chk);*/

		*(data + 4 + vsize + 2 + hsize) = chk & 0xFF;
		*(data + 4 + vsize + 2 + hsize + 1) = chk >> 8;

		tchk += chk;
		tchk += (hsize - 2) & 0xFF;
		tchk += (hsize - 2) >> 8;

		*(list + c++) = data;

		if (c == 20) {
			fprintf(stderr, "Unexpected: more than 19 vars in group file\n");
			break;
		}

		fseek(logi, 2, SEEK_CUR);
		fgetc(logi);
	}
/*	fprintf(stderr, "checksum eye %04X, %d\n", tchk, c); */
	fseek(logi, -2, SEEK_CUR);
	tchk -= fgetc(logi);
	tchk -= fgetc(logi) << 8;

	fclose(logi);
	if ((tchk)) {
		fprintf(stderr, "** Warning: Invalid checksum **\n");
		/* unravel(list); */
	} /* else { */

	if (backup_mode && (!(list = reverse(list)))) {
		unravel(list);
		return;
	}

	G_LOCK(running);
	G_LOCK(runctrl);
		activate_send(list);
	G_UNLOCK(runctrl);
	G_UNLOCK(running);

	/* } */
}
/* Exported <<< */


static byte **reverse(byte **list)
{
	byte **reversed;
	int i, max;

	if (!(reversed = malloc(20 * sizeof(byte*)))) {
		fprintf(stderr, "Insufficient memory, aborting\n");
		return NULL;
	}

	for (max=0; list[max]; max++);
	reversed[max] = 0;

	for (i=0; i<max; i++) {
		reversed[max-1-i] = list[i];
	}
	free(list);
	return reversed;
}
