/* A class for dealing with .NFO files */

#include <ctype.h>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <map>
#include <climits>

#include "info.h"
#include "sprites.h"
#include "error.h"
#include "grfcomm.h"

#define GRFCODEC
#include "mapescapes.h"

extern bool _interactive;

int makeint(U8 low, S8 high)
{
	S16 combined;

	combined = (high << 8) | low;
	return combined;
}

void read_file(istream&in,int infover,int grfcontversion,AllocArray<Sprite>&sprites);

nfe_map nfo_escapes;

inforeader::inforeader(char *fn, int grfcontversion)
{
	ifstream f;
	f.open(fn);

	string buffer;
	int infover;

	imgfile = NULL;
	imgname = "";

	getline(f,buffer);		// read first line, a comment

	if (strncmp(buffer.c_str(), "// ", 3)) {
		printf("NFO file missing header lines and version info\n");
		exit(1);
	}
	getline(f,buffer);		// second line used to get info version
	if (strncmp(buffer.c_str(), "// (Info version ", 17)) {
		infover = 1;
		f.seekg(0);
	} else
		infover = atoi(buffer.c_str() + 17);

	if (infover > 2)
		getline(f,buffer);	// skip "format: " line

	if (infover > 6) {
		while (strncmp(buffer.c_str(), "// Format: ", 11)) {
			if (!strncmp(buffer.c_str(), "// Escapes: ", 12)) { // Nope. That was an escapes line.
				istringstream esc(buffer);
				string str;
				int byte = 0;
				esc.ignore(12);
				while (esc>>str){
					if(str == "=") byte--;
					else if (str[0] == '@')
						byte = strtol(str.c_str()+1, NULL, 16);
					else nfo_escapes.insert(nfe_pair(str, byte++));
				}
			}
			getline(f, buffer);	// Try again to skip "format: " line
		}
	}

	colourmap = NULL;

	try{
		read_file(f,infover,grfcontversion,nfofile);
	}catch(Sprite::unparseable e){
		printf("%s", e.reason.c_str());
		exit(1);
	}
}

inforeader::~inforeader()
{
	delete imgfile;
}

void inforeader::PrepareReal(const SpriteInfo&info){
	int oldy=inf.ypos;
	inf=info;
	if ( inf.forcereopen || !imgfile || inf.name != imgname || oldy > inf.ypos) {
		// new file

		delete imgfile;

		imgname = inf.name;
		if (_interactive) {
			printf("Loading %s\n", imgname.c_str());
		}

		imgfile = MakeReader();
		if (!imgfile) {
			printf("\nError: can't open %s\n", imgname.c_str());
			exit(2);
		}

		if (colourmap)
			imgfile->installreadmap(colourmap);

		imgfile->startimage(inf.depth!=DEPTH_32BPP, 0, 0, 0, 0);
	}

	sx = inf.xdim;
	sy = inf.ydim;

	imgfile->startsubimage(inf.xpos, inf.ypos, sx, sy);

	imgsize = (long) sx * (long) sy;
}

pcxread* inforeader::MakeReader()const{
#ifdef WITH_PNG
	if(toupper(imgname[imgname.length()-1])=='X')//pcx
		return new pcxread(new singlefile(imgname.c_str(), "rb", NULL));
	else //png
		return new pngread(new singlefile(imgname.c_str(), "rb", NULL));
#else
	return new pcxread(new singlefile(imgname.c_str(), "rb", NULL));
#endif
}

int inforeader::getsprite(CommonPixel *sprite)
{
	imgfile->streamgetpixel(sprite, imgsize);
	return 1;
}

void inforeader::installmap(int *map)
{
	colourmap = map;
}

extern int _useexts;

infowriter::infowriter(FILE *info, int maxboxes, int useplaintext, const char *directory)
{
	this->directory = directory;
	infowriter::info = info;
	fputs("// Automatically generated by GRFCODEC. Do not modify!\n", info);
	fprintf(info,"// (Info version %d)", _useexts ? 32 : 6);
	if(_useexts) {
		// (re)insert default escapes
		foreach(const esc& e, escapes)
			nfo_escapes.insert(nfe_pair(e.str+1, e.byte));
		fputs("\n// Escapes:", info);
		int oldbyte = -1;

		for (int act = 0; act < 255; act++) {
			foreach (const nfe_rpair& p, nfo_escapes.right) {
				if (p.second[0] != act) continue;

				if (p.first == oldbyte) {
					fprintf(info, " =");
					--oldbyte;
				} else if (p.first < oldbyte) {
					fputs("\n// Escapes:", info);
					oldbyte = -1;
				}
				while (++oldbyte != p.first)
					fprintf(info," %s", nfo_escapes.right.begin()->second.c_str());
				fprintf(info, " %s", p.second.c_str());
			}
		}
	}
	if (_useexts) {
		fputs("\n// Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags\n", info);
	}else {
		fputs("\n// Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel\n", info);
	}

	infowriter::useplaintext = useplaintext;
	infowriter::maxboxes = maxboxes;
	boxes = new Box*[maxboxes];
	boxnum = 0;
	spriteno = 0;
	for (int i=0; i<maxboxes; i++)
		boxes[i] = NULL;
}

infowriter::~infowriter()
{
	delete[] boxes;
}

void infowriter::flush()
{
	int i;
	uint j;

	for (i=0; i<boxnum; i++) {
		switch (boxes[i]->type) {
		case Box::issprite:
		case Box::isspriteextension: {
			if (boxes[i]->type == Box::issprite) {
				fprintf(info, "%5d ", spriteno++);
			} else {
				fprintf(info, "    | ");
			}
			BoxSprite *s = (BoxSprite*)boxes[i];

			if (_useexts) {
				if (s->info.depth == 2) {
					fprintf(info, "%s %5s %4d %4d",
							s->filename, depths[s->info.depth], s->x, s->y);
				} else {
					fprintf(info, "%s %5s %4d %4d %4d %4d %4d %4d %s",
							s->filename, depths[s->info.depth], s->x, s->y,
							s->info.xdim, s->info.ydim,
							s->info.xrel, s->info.yrel,
							zoom_levels[s->info.zoom]);
					if (HASTRANSPARENCY(s->info.info)) fputs(" chunked", info);
					if (DONOTCROP(s->info.info)) fputs(" nocrop", info);
				}
			} else {
				fprintf(info, "%s %d %d %02X %d %d %d %d",
						s->filename, s->x, s->y,
						s->info.info,
						s->info.ydim, s->info.xdim,
						s->info.xrel, s->info.yrel);
			}
			fputs("\n", info);
			break;  }

		case Box::isdata:	{
			fprintf(info, "%5d ", spriteno++);
			BoxData *d = (BoxData*)boxes[i];
			int instr = 0;
			int count = 0;

			if (d->data[0] == 0xff && d->size>4 && spriteno>1) {	// binary include file
				int namelen = d->data[1];
				int len = strlen(this->directory) + namelen + 1;
				char *filename = new char[len];

				snprintf(filename, len, "%s%s", this->directory, (char*) d->data + 2);
				// Strip trailing whitespace
				int last = 0;
				for (int j=0; j<len-1; j++)
					if (!isspace(filename[j])) last = j;
				filename[last+1] = '\0';
				fprintf(info, "**\t %s\n", filename);

				FILE *bin = fopen(filename, "wb");
				if (!bin) {
					fperror("Cannot write to %s", filename);
					exit(2);
				}
				cfwrite("writing binary include", d->data+3+namelen, d->size-3-namelen, 1, bin);
				delete[]filename;
				fclose(bin);
			} else {
				fprintf(info, "* %d\t", d->size);
				for (j=0; j<d->size; j++) {
					// Readable characters are 32..126 and 158..255
#define istxt(x) (j+(x)<d->size && ((d->data[j+(x)]>=32 && (_useexts || d->data[j+(x)] != 34) && d->data[j+(x)]<127) || d->data[j+(x)] > 158))
					//int thistxt = (d->data[j] >= 32 && d->data[j] < 127) || d->data[j] > 158;
					//int nexttxt = j+1<d->size && ((d->data[j+1]>=32 && d->data[j+1]<127) || d->data[j+1] > 158);

					if (spriteno>1 &&
							istxt(0) && useplaintext && (instr ||
							//two in a row for 8 and E
							(istxt(1) && (d->data[0]==8 || d->data[0]==0xE ||
							//four in a row for everything else.
							(istxt(2) && istxt(3))
							))
							)) {
						if (!instr) {
							fputs(" \"", info); instr = 1;
						}
						if(_useexts && d->data[j]=='\\')
							fputs("\\\\", info);
						else if(_useexts && d->data[j]=='"')
							fputs("\\\"", info);
						else
							fputc(d->data[j], info);
					} else {
						if (instr) {
							fputs("\"", info); instr = 0;
						}
						uint k=0;
						if (_useexts == 2 && spriteno>1) {
							for(;k<num_esc;k++)
								if(escapes[k].byte==d->data[j]&&
									escapes[k].action==d->data[0]&&
									(escapes[k].override?escapes[k].override(d->data,j):escapes[k].pos==j)&&
									(escapes[k].additional==NULL||escapes[k].additional(d->data,d->size))){
										fprintf(info," %s",escapes[k].str);
										break;
									}
						}else
							k = num_esc;
						if(k==num_esc)
							fprintf(info, " %02X", d->data[j]);
					}

					// break lines after 32 non-text characters
					// or at spaces after 32 text characters or
					// after 60 text characters
					count++;
					if ( ((!instr && count>=32) ||
							(instr && ((count>=32 && d->data[j]==' ')
							|| count>=50)))
							&& j<d->size-1) {
						if (instr) {
							if(istxt(1)){
								fputs("\"\n\t \"", info);
							}else{
								fputs("\"\n\t", info); instr = 0;
							}
						}else
							fputs("\n\t", info);
						count = 0;
					}
				}

				if (instr) fputs("\"", info);
				fputs("\n", info);
			}
			free(d->data);
			break;	}

		default:
			printf("\nHuh? What kind of box is that?\n");
			exit(2);
		}
	}

	boxnum = 0;

	for (i=0; i<maxboxes; i++) {
		delete boxes[i];
		boxes[i] = NULL;
	}
}

void infowriter::resize(int newmaxboxes)
{
	printf("Reallocating memory for %d boxes\n", newmaxboxes);
	infowriter::maxboxes = newmaxboxes;
	Box **newboxes = new Box*[newmaxboxes];
	int i;
	for (i=0; i<maxboxes; i++)
		newboxes[i] = boxes[i];
	for (; i<newmaxboxes; i++)
		newboxes[i] = NULL;
	delete[](boxes);
	boxes = newboxes;
}

void infowriter::addsprite(bool first, const char *filename, int x, int y, SpriteInfo info)
{
	if (boxnum >= maxboxes)
		resize(maxboxes*2);

	boxes[boxnum++] = new BoxSprite(first, filename, x, y, info);
}

void infowriter::adddata(uint size, U8 *data)
{
	if (boxnum >= maxboxes)
		resize(maxboxes*2);

	boxes[boxnum++] = new BoxData(size, data);
}

void infowriter::done(int count)
{
	if (count != spriteno) {
		fprintf(stderr, "\nError: GRFCodec thinks it has written %d sprites but the info file only got %d\n", count, spriteno);
		exit(2);
	}
}
