/*
 Generateur de table de DB a partir d'un fichier binaire
 */

#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <strstream>
#include <vector>
#include <algorithm>

#include "CDataLink.h"

#include "CError.h"
#include "optionParser.h"

struct SDataLinker
{
	CDataLink data;
	int adr;

};

bool operator<(const SDataLinker &i_s, const SDataLinker &i_d)
{
	return (i_s.data.getMask() < i_d.data.getMask());
}

struct SDataZone
{
	std::string name;
	unsigned int start;
	unsigned int end;
	unsigned int bank;

	std::vector< SDataLinker > datas;

};

std::vector< SDataLinker >	linkerDataTable;
std::vector< SDataZone >	linkerZoneTable;
std::ostream				*linkerOutASM = &std::cout;

void linkData()
{
	int nbFilePlaced = 0;
	int i;
	std::vector<bool>	filePlaced(linkerDataTable.size(),false);
	std::vector<int>	zoneUsed(linkerZoneTable.size(),0); 
	
	for (i=0;i<zoneUsed.size();i++)
	{
		zoneUsed[i] = linkerZoneTable[i].start;
		linkerZoneTable[i].datas.clear();
	}

	std::sort(linkerDataTable.begin(),linkerDataTable.end());
	
	i=0;
	while (nbFilePlaced != linkerDataTable.size())
	{
		bool filePlaced = false;
		
		for (int j=0; j<linkerZoneTable.size() && !filePlaced; j++)
		{
			unsigned int start = zoneUsed[j];
			if (linkerDataTable[i].data.getSize() != 0)
			{
				start += 0xffff - linkerDataTable[i].data.getMask();
				start &= linkerDataTable[i].data.getMask();
				if (start + linkerDataTable[i].data.getSize() < linkerZoneTable[j].end)
				{
					filePlaced = true;
					linkerDataTable[i].adr = start;
					zoneUsed[j]= start + linkerDataTable[i].data.getSize();
					linkerZoneTable[j].datas.push_back(linkerDataTable[i]);
					nbFilePlaced++;
					i++;
				}
			}
			else
			{
				filePlaced = true;
				linkerDataTable[i].adr = start;
				linkerZoneTable[j].datas.push_back(linkerDataTable[i]);
				nbFilePlaced++;
				i++;
			}
		}
		if (!filePlaced)
		{
			WARNING("Unable to place " << linkerDataTable[i].data.getLabel());
			nbFilePlaced++;
		}
	}
}

void saveFile()
{
	for (int i=0; i<linkerZoneTable.size(); i++)
	{
		if (linkerZoneTable[i].datas.size() != 0)
		{
			std::ofstream data(linkerZoneTable[i].name.c_str(),std::ios::binary);
			(*linkerOutASM) << "; Bank #"<< std::hex << linkerZoneTable[i].bank << std::dec << " : Data file " << linkerZoneTable[i].name.c_str() << std::endl;
			int start = linkerZoneTable[i].start;
			for (int j=0;j<linkerZoneTable[i].datas.size();j++)
			{
				while (start < linkerZoneTable[i].datas[j].adr)
				{
					data << (char)0;
					start++;
				}
				data.write((char*)linkerZoneTable[i].datas[j].data.getDatas(),linkerZoneTable[i].datas[j].data.getSize());
				start+=linkerZoneTable[i].datas[j].data.getSize();
				(*linkerOutASM) << linkerZoneTable[i].datas[j].data.getLabel() << "\t\tEQU\t#" << std::hex << linkerZoneTable[i].datas[j].adr << std::endl;
			}
			(*linkerOutASM) << "; End data file " << linkerZoneTable[i].name.c_str() << " : #" << std::hex << start << std::endl;
		}
	}
	if (linkerOutASM != &(std::cout))
	{
		delete linkerOutASM;
	}
}

struct SOption dataLinkerOption[]=
{
	{'z',"zone",1,0,4,"Define a zone named $1 in bank $2 from $3 to $4"},
	{'d',"dataFilename",1,0,3,"Load a data file $1 with label $2 and mask $3 to be linked"},
	{'o',"output",0,1,1,"Z80 output filename"},
	{'c',"configFile",0,1,1,"Configuration file"},
	{0,NULL,0,0,0,NULL}
};

int main (int argc, char **argv)
{
	int i,o;
	std::cout << "dataLinker (c) Ramlaid 2003" << std::endl << std::endl;

	struct SOptionOut *cmdOption=NULL;

	if (parseCommandLineOption(&argc,&argv,dataLinkerOption,&cmdOption) != NO_ERROR)
	{
		if (!((o=optionIndex(cmdOption,'c')) != ERROR_UNKNOWN))
		{
			printf("%s\n\n",getErrorString());
			printUsage("dataLinker",
				"Link data and create a .z80 file",
				dataLinkerOption);
			exit(-1);
		}
	}

	if ((o=optionIndex(cmdOption,'c')) != ERROR_UNKNOWN)
	{
		if (parseConfigFileOption(cmdOption[o].argv[0],&argc,&argv,dataLinkerOption,&cmdOption) != NO_ERROR)
		{
			printf("%s\n\n",getErrorString());
			printUsage("dataLinker",
				"Link data and create a .z80 file",
				dataLinkerOption);
			exit(-1);
		}
	}
	
	if (argc != 1)
	{
		printUsage("dataLinker",
			"Link data and create a .z80 file",
			dataLinkerOption);
		exit(-1);
	}
	
	try
	{
		i=0;
		while (cmdOption[i].shortName != 0)
		{
			switch(cmdOption[i].shortName)
			{
			case 'd' :
				{
					SDataLinker file;
					std::string name,label;
					unsigned int mask;
					mask = strtol(cmdOption[i].argv[2],NULL,0);
					ASSERTMSG(errno != ERANGE, "Wrong value " << cmdOption[i].argv[2]);
					file.data = CDataLink(std::string(cmdOption[i].argv[0]),std::string(cmdOption[i].argv[1]),mask);
					file.data.loadFile();
					file.adr = -1;
					linkerDataTable.push_back(file);
					break;
				}
			case 'z':
				{
					SDataZone zone;
					zone.name = std::string(cmdOption[i].argv[0]);
					zone.bank = strtol(cmdOption[i].argv[1],NULL,0);
					ASSERTMSG(errno != ERANGE, "Wrong value " << cmdOption[i].argv[1]);
					zone.start = strtol(cmdOption[i].argv[2],NULL,0);
					ASSERTMSG(errno != ERANGE, "Wrong value " << cmdOption[i].argv[2]);
					zone.end = strtol(cmdOption[i].argv[3],NULL,0);
					ASSERTMSG(errno != ERANGE, "Wrong value " << cmdOption[i].argv[3]);
					linkerZoneTable.push_back(zone);
					break;
				}
			case 'o' : 
				{
					linkerOutASM = new std::ofstream(cmdOption[i].argv[0]);
					break;
				}
			case 'c' : {break;}
			default :
				{
					std::cerr << "Unknow option ! " << cmdOption[i].shortName << std::endl;
					freeOptionOut(cmdOption);
					return -1;
				}
			}
			i++;
		}
	
		linkData();
	
		saveFile();
	}
	catch(CException &e)
	{
		std::cerr << e << std::endl;
		freeOptionOut(cmdOption);
		return -1;
	}

	return 0;
}