/*  TA3D, a remake of Total Annihilation
    Copyright (C) 2005  Roland BROCHARD

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA*/

/*-----------------------------------------------------------------------------------\
|                                         hpi.cpp                                    |
|  ce fichier contient les structures, classes et fonctions nécessaires à la lecture |
| des fichiers hpi/ufo/... de total annihilation mais sans fournir les fonctions de  |
| lecture des fichiers contenus dans ces archives!                                   |
|                                                                                    |
\-----------------------------------------------------------------------------------*/

#include <allegro.h>
#include "console.h"
#include "hpi.h"

FILE_NODE	archive_struct;		// Structure des archives

ARCH arch_manager;

	void FILE_NODE::add_file(char *filename,char *src)		// Ajoute un fichier
	{
		if(filename[0]=='\\')	filename++;
		if(strstr(filename,"\\")) {		// Si le fichier est dans un sous dossier
			char *dirname=strdup(filename);
			int i=0;
			while(dirname[i]!='\\')	i++;		// Isole le nom du dossier
			dirname[i]=0;

			int index=-1;		// Cherche s'il n'est pas déjà répertorié
			if(nb_nodes>0 && node)
				for(i=0;i<nb_nodes;i++)
					if(strcasecmp(dirname,node[i]->name)==0) {
						index=i;
						break;
						}
			if(index==-1) {		// S'il n'est pas repertorié on l'ajoute
				nb_nodes++;
				FILE_NODE *f_node=(FILE_NODE*) malloc(sizeof(FILE_NODE));
				FILE_NODE **n_node=(FILE_NODE**) malloc(sizeof(FILE_NODE*)*nb_nodes);
				if(nb_nodes>1 && node)
					for(int i=0;i<nb_nodes-1;i++)
						n_node[i]=node[i];
				if(node)	free(node);
				node=n_node;
				node[nb_nodes-1]=f_node;
				f_node->init();
				f_node->dir=true;
				f_node->name=dirname;
				f_node->archive=strdup(src);
				int arch_idx=-1;
				for(i=0;i<arch_manager.nb_arch;i++)
					if(strcasecmp(arch_manager.arch_name[i],f_node->archive)==0) {
						arch_idx=i;
						break;
						}
				if(arch_idx==-1)
					arch_idx=arch_manager.add(src);
				f_node->arch_idx=arch_idx;
				index=nb_nodes-1;
				}
			else
				free(dirname);
			node[index]->add_file(strstr(filename,"\\"),src);		// Ajoute récursivement le fichier
			}
		else {			// Si c'est juste un fichier
			nb_nodes++;
			FILE_NODE *f_node=(FILE_NODE*) malloc(sizeof(FILE_NODE));
			FILE_NODE **n_node=(FILE_NODE**) malloc(sizeof(FILE_NODE*)*nb_nodes);
			if(nb_nodes>1 && node)
				for(int i=0;i<nb_nodes-1;i++)
					n_node[i]=node[i];
			if(node)	free(node);
			node=n_node;
			node[nb_nodes-1]=f_node;
			f_node->init();
			f_node->name=strdup(filename);
			f_node->archive=strdup(src);
			int arch_idx=-1;
			for(int i=0;i<arch_manager.nb_arch;i++)
				if(strcasecmp(arch_manager.arch_name[i],f_node->archive)==0) {
					arch_idx=i;
					break;
					}
			if(arch_idx==-1)
				arch_idx=arch_manager.add(src);
			f_node->arch_idx=arch_idx;
			}
	}

	FILE_NODE *FILE_NODE::find_file(char *filename)		// Recherche un fichier
	{
		if(filename[0]=='/')	filename++;
		if(strstr(filename,"/")) {		// Si le fichier est dans un sous dossier
			char *dirname=strdup(filename);
			int i=0;
			while(dirname[i]!='/')	i++;		// Isole le nom du dossier
			dirname[i]=0;

			int index=-1;		// Cherche s'il n'est pas déjà répertorié
			if(nb_nodes>0 && node)
				for(i=0;i<nb_nodes;i++)
					if(strcasecmp(dirname,node[i]->name)==0) {
						index=i;
						break;
						}
			free(dirname);
			if(index!=-1)		// S'il est pas repertorié on continue
				return node[index]->find_file(strstr(filename,"/"));
			else
				return NULL;
		}
		else {			// Si c'est juste un fichier on vérifie si on l'a dans ce noeud
			if(nb_nodes>0 && node)
				for(int i=0;i<nb_nodes;i++) {
					if(strcasecmp(filename,node[i]->name)==0)
						return node[i];
					}
			return NULL;
			}
	}

bool archive_struct_built=false;	// Indique si la structure des archives a été construite

HPIHeader read_hpi_header(char *filename)			// Lit l'entête d'un fichier hpi
{
	HPIHeader header;
	FILE *f;
	f=fopen(filename,"rb");
	fread(&header,sizeof(header),1,f);
	fclose(f);
	return header;
}

	int HPI_FILE::Decompress(char *out, char *in, int len)
	{
	/*
		Decomprime le tampon "in" de taille "len" octets dans le tampon "out" (précédemment
		alloué) retourne le nombre d'octets décomprimés.
	*/

		int x;
		int outbufptr;
		int mask;
		int tag;
		int inptr;
		int outptr;
		int count;
		int done;
		char Window[4096];
		int inbufptr;

//		done = FALSE;

		inptr = 0;
		outptr = 0;
		outbufptr = 1;
		mask = 1;
		tag = in[inptr++];
	
//		while (!done) {
		while (true) {
			if ((mask & tag) == 0) {
				out[outptr++] = in[inptr];
				Window[outbufptr] = in[inptr];
				outbufptr = (outbufptr + 1) & 0xFFF;
				inptr++;
				}
			else {
				count = *((unsigned short *) (in+inptr));
				inptr += 2;
				inbufptr = count >> 4;
				if (inbufptr == 0)
					return outptr;
				else {
					count = (count & 0x0f) + 2;
					if (count >= 0) {
						for (x = 0; x < count; x++) {
							out[outptr++] = Window[inbufptr];
							Window[outbufptr] = Window[inbufptr];
							inbufptr = (inbufptr + 1) & 0xFFF;
							outbufptr = (outbufptr + 1) & 0xFFF;
							}
						}
					}
				}
//			mask *= 2;
			mask<<=1;
			if (mask & 0x0100) {
				mask = 1;
				tag = in[inptr++];
				}
			}
		return outptr;
	}

	int HPI_FILE::read_and_decrypt(FILE *f,byte *dat,int fpos,int size)
	/*
		lit et décrypte le contenu du fichier f dont l'entête a été lue dans header
	*/
	{
		int Key= ~((header.HeaderKey * 4) | (header.HeaderKey >> 6));
		int count;
		int tkey;
		int result;

		/* commence par positionner le curseur dans le fichier */
		fseek(f, fpos, SEEK_SET);

		/* lit les données dans le tampon */
		result = fread(dat, 1, size, f);

		/* pour chaque caractère du tampon... */
		if(header.HeaderKey)						// Décrypte les données si elles sont cryptées
			for (count = 0; count < size; count++) {

				/* calcule la clef de décryptage tkey = (fpos + count) XOR Key */
				tkey = (fpos + count) ^ Key;

				/* puis décode l'octet:  
				data[count] = tkey XOR (NOT data[count]) */
				dat[count] = tkey ^ ~dat[count];
				}

		/* result est le nombre d'octets lus depuis le fichier, 
		et devrait être égal à header.DirectorySize */
		return result;
	}

void build_archive_struct()		// Lit l'arborescence des archives
{
	if(archive_struct_built)	return;
	archive_struct_built=true;
	archive_struct.destroy();	// Au cas où
	al_ffblk search;

	if(al_findfirst("*.gp3",&search,FA_RDONLY | FA_ARCH)==0)
	do
	{
		HPI_FILE hpi;
		hpi.load(search.name);
		hpi.build_struct(search.name);
		hpi.destroy();
	}while(al_findnext(&search)==0);
	al_findclose(&search);
	if(al_findfirst("*.hpi",&search,FA_RDONLY | FA_ARCH)==0)
	do
	{
		HPI_FILE hpi;
		hpi.load(search.name);
		hpi.build_struct(search.name);
		hpi.destroy();
	}while(al_findnext(&search)==0);
	al_findclose(&search);
	if(al_findfirst("*.ufo",&search,FA_RDONLY | FA_ARCH)==0)
	do
	{
		HPI_FILE hpi;
		hpi.load(search.name);
		hpi.build_struct(search.name);
		hpi.destroy();
	}while(al_findnext(&search)==0);
	al_findclose(&search);
	if(al_findfirst("*.ccx",&search,FA_RDONLY | FA_ARCH)==0)
	do
	{
		HPI_FILE hpi;
		hpi.load(search.name);
		hpi.build_struct(search.name);
		hpi.destroy();
	}while(al_findnext(&search)==0);
	al_findclose(&search);
}

char *find_hpi(char *file)			// Cherche l'archive qui contient le fichier file
{
	if(!archive_struct_built)	build_archive_struct();

	FILE_NODE *dst=archive_struct.find_file(file);
	if(dst)
		return dst->archive;
	return NULL;
}

byte *load_file(char *filename,int *size)			// Trouve l'archive contenant le fichier demandé et en extrait le fichier cherché
{
	if(!archive_struct_built)	build_archive_struct();

	FILE_NODE *dst=archive_struct.find_file(filename);
	if(dst)
		return arch_manager.arch[dst->arch_idx].extract_memory_file(filename,size);
	return NULL;
}

int get_nb_file(HPI_FILE *hpi,char *dir,char *ext)
{
	int n=0,i=0;
	char *name=NULL;
	char *ext_m=strlwr(strdup(ext));
	while(name=hpi->find(dir,i++)) {
		if(strstr(strlwr(name),ext_m))
			n++;
		delete name;
		}
	free(ext_m);
	return n;
}

char **file_list(char *dir,char *ext,int *nb)
{
	int n=0;
	HPI_FILE hpi;

	al_ffblk search;

	if(al_findfirst("*.hpi",&search,FA_RDONLY | FA_ARCH)==0)
	do
	{
		hpi.load(search.name);
		n+=get_nb_file(&hpi,dir,ext);
		hpi.destroy();
	}while(al_findnext(&search)==0);
	al_findclose(&search);
	if(al_findfirst("*.ufo",&search,FA_RDONLY | FA_ARCH)==0)
	do
	{
		hpi.load(search.name);
		n+=get_nb_file(&hpi,dir,ext);
		hpi.destroy();
	}while(al_findnext(&search)==0);
	al_findclose(&search);
	if(al_findfirst("*.ccx",&search,FA_RDONLY | FA_ARCH)==0)
	do
	{
		hpi.load(search.name);
		n+=get_nb_file(&hpi,dir,ext);
		hpi.destroy();
	}while(al_findnext(&search)==0);
	al_findclose(&search);

	hpi.load("rev31.gp3");
	n+=get_nb_file(&hpi,dir,ext);
	hpi.destroy();

	char **list=(char**) malloc(sizeof(char*)*n);
	char *name=NULL;
	int i;
	int p=0;
	char *ext_m=strlwr(strdup(ext));
	if(al_findfirst("*.hpi",&search,FA_RDONLY | FA_ARCH)==0)
	do
	{
		hpi.load(search.name);
		i=0;
		while(name=hpi.find(dir,i++)) {
			if(strstr(strlwr(name),ext_m))
				list[p++]=name;
			else
				delete name;
			}
		hpi.destroy();
	}while(al_findnext(&search)==0);
	al_findclose(&search);
	if(al_findfirst("*.ufo",&search,FA_RDONLY | FA_ARCH)==0)
	do
	{
		hpi.load(search.name);
		i=0;
		while(name=hpi.find(dir,i++)) {
			if(strstr(strlwr(name),ext_m))
				list[p++]=name;
			else
				delete name;
			}
		hpi.destroy();
	}while(al_findnext(&search)==0);
	al_findclose(&search);
	if(al_findfirst("*.ccx",&search,FA_RDONLY | FA_ARCH)==0)
	do
	{
		hpi.load(search.name);
		i=0;
		while(name=hpi.find(dir,i++)) {
			if(strstr(strlwr(name),ext_m))
				list[p++]=name;
			else
				delete name;
			}
		hpi.destroy();
	}while(al_findnext(&search)==0);
	al_findclose(&search);

	hpi.load("rev31.gp3");
		i=0;
		while(name=hpi.find(dir,i++)) {
			if(strstr(strlwr(name),ext_m))
				list[p++]=name;
			else
				delete name;
			}
	hpi.destroy();
	*nb=n;
	return list;
}
