#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <sys/stat.h>
#include <ctype.h>
#include "tool.h"
#include "link.h"
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>

#define ALLOC_MODULE	5000

#ifdef USE_NM
# ifndef NM_ABSPATH
# define NM_ABSPATH "/usr/bin/nm"
# endif

/*
 * BFD-support (esp. for Alpha) added by:
 *	Manabe Takashi <manabe@Roy.dsl.tutics.tut.ac.jp>
 */

#ifndef __alpha__ 
 /*
  * Check if the module is an a.out module, so that we can skip
  * the first '_'. We now use a unified ELF-like symbol table format
  */
static int is_a_out(const char *objname) // 1 == a.out, 0 == ELF
{
	int fd;
	char buf[4];
	int a_out = 1; //if open fails, the following call to mod_open fails too

	if ((fd = open(objname, O_RDONLY)) > 0) {
		if (read(fd, buf, 4) == 4) {
			if (buf[0] == 0177 && strncmp(buf +1, "ELF", 3) == 0)
				a_out = 0;
		}
		close(fd);
	}

	return a_out;
}

#endif

/*
	Ouvre le fichier et retourne NULL si erreur.
*/
static FILE *module_open (const char *objname)
{
	FILE *ret = NULL;
	struct stat buf;
	if (stat (objname,&buf) != -1){
		char cmd[300];
		sprintf (cmd, NM_ABSPATH " -p %s",objname);
		ret = popen_err (cmd,"r",0);
	}else{
		depmod_error ("%s does not exist",objname);
	}
	return ret;
}
#endif
/*
	GŠre la liste de tous les modules rencontr‚ durant le link
*/
PUBLIC MODULES::MODULES()
{
	/* #Sp‚cification: librairies / nombre maximum
		Un nombre maximum de 255 librairie est permis pour le link
	*/
	tblibs = (char**)malloc_err (255*sizeof(char*),1);
	nblib = 0;
	tbmod = (MODULE*)malloc_err (ALLOC_MODULE*sizeof(MODULE),1);
	nbmod = 0;
}

/*
	Finalise l'information sur un module.
*/
PRIVATE void MODULES::setmod (
	MODULE *mod,
	SYMBOL *tbpub[],
	int nbpub,
	SYMBOL *tbext[],
	int nbext,
	int module_requis)
{
	mod->is_load = module_requis;
	mod->pub.nb = nbpub;
	mod->ext.nb = nbext;
	if (nbpub > 0){
		int size = nbpub*sizeof(SYMBOL*);
		mod->pub.tb = (SYMBOL**)malloc_err(size,1);
		memcpy (mod->pub.tb,tbpub,size);
	}else{
		mod->pub.tb = NULL;
	}
	if (nbext > 0){
		int size = nbext*sizeof(SYMBOL*);
		mod->ext.tb = (SYMBOL**)malloc_err(size,1);
		memcpy (mod->ext.tb,tbext,size);
		if (module_requis){
			SYMBOL **ptext = mod->ext.tb;
			for (int i=0; i<nbext; i++,ptext++) (*ptext)->requis = 1;
		}
	}else{
		mod->ext.tb = NULL;
	}
}
/*
	Create a module entry in the list of module.
	This module generally do not is not related to any real file.
*/

PUBLIC MODULE *MODULES::setdummy (const char *name)
{
	MODULE *mod = tbmod+nbmod++;
 	mod->name = alloctxt_add (name);
	mod->lib = -1;
	mod->is_load = 0;
	mod->pub.nb = mod->ext.nb = 0;
	mod->ext.tb = NULL;
	mod->pub.tb = NULL;
	return mod;
}
/*
	Lit les symboles d'un objet et enregistre dans syms
	Retourne -1 si erreur.
*/
PUBLIC int MODULES::loadobj(SYMBOLS &syms, const char *objname)
{
	MODULE *mod = tbmod+nbmod++;
 	mod->name = alloctxt_add (objname);
	mod->lib = -1;
#ifndef USE_NM
	return load_obj(syms, objname, mod);
#else
	int ret = -1;
#ifdef __alpha__
	int skip_us = 0; // no need to skip
#else
	int skip_us = is_a_out(objname); // 1 == a.out, 0 == ELF
#endif
	FILE *fin = module_open (objname);
	if (fin != NULL){
		SYMBOL *tbext[5000];
		int nbext = 0;
		SYMBOL *tbpub[5000];
		int nbpub = 0;
		int module_requis=0;
		ret = 0;
		char rbuf[200];
		while (fgets(rbuf,sizeof(rbuf)-1,fin)!=NULL){
			char buf[200];
			str_strip (rbuf,buf);
			char *name = buf;

			if (buf[0] == ' ') {
			  while (*name == ' ' || *name == '\t') name ++;
			  name += 2; // skip '[CUTtdD] '
			} else {
			  while (*name != ' ' && *name) name ++;
			  name += 3; // skip ' [CUTtdD] '
			}
			if (skip_us) {
			  if (*name != '_') continue;
			  name ++;
			}
			if (buf[0] == ' '){
				tbext[nbext++] = syms.add (name,NULL,SYM_REQUIS
					,module_requis,0);
			}else if (isxdigit(buf[0])){
				tbpub[nbpub++] = syms.add (name,mod,SYM_DEFINI
					,module_requis,buf[9] == 'C');
			}else{
				break;
			}
		}
		pclose (fin);
		setmod (mod,tbpub,nbpub,tbext,nbext,1);
	}
	return ret;
#endif
}

/*
	Affiche la liste des modules qui necessite un symbole (indefini).
*/
PUBLIC void MODULES::showundef (SYMBOL *undef, FILE *fout)
{
	MODULE *ptmod = tbmod;
	int nb = 0;
	for (int i=0; i<nbmod; i++, ptmod++){
		if (ptmod->is_load){
			SYMBOL **ptext = ptmod->ext.tb;
			int nbext = ptmod->ext.nb;
			for (int e=0; e<nbext; e++, ptext++){
				if (*ptext == undef){
					if (nb == 0){
						fprintf (fout,"\t%s\n",undef->name);
						nb = 1;
					}
					if (ptmod->lib == -1){
						fprintf (fout,"\t    %s\n",ptmod->name);
					}else{
						fprintf (fout,"\t    %s(%s)\n",ptmod->name
							,tblibs[ptmod->lib]);
					}
					break;
				}
			}
		}
	}
}
/*
	Affiche la liste des symboles non d‚finies.
	Retourne le nombre trouv‚ ou 0 si ok.
*/
PUBLIC int MODULES::findundef (FILE *fout)
{
	MODULE *ptmod = tbmod;
	int ret = 0;
	for (int i=0; i<nbmod; i++, ptmod++){
		if (ptmod->is_load){
			SYMBOL **ptext = ptmod->ext.tb;
			int nbext = ptmod->ext.nb;
			for (int e=0; e<nbext; e++, ptext++){
				if (!(*ptext)->defini){
					if (ret == 0){
						fprintf (fout,"Undefined symbols:\n");
					}
					showundef (*ptext,fout);
					(*ptext)->defini = 1;	// Evite de rementionner
											// le symbole
					ret++;
				}
			}
		}
	}
	return ret;
}

/*
	Affiche la liste des modules qui seront inclus dans l'ex‚cutable
	Retourne le nombre de modules trouv‚s.
*/
PUBLIC int MODULES::showload (FILE *fout)
{
	MODULE *ptmod = tbmod;
	int ret = 0;
	for (int i=0; i<nbmod; i++, ptmod++){
		if (ptmod->is_load){
			ret++;
			fprintf (fout,"%s ",ptmod->name);
		}
	}
	if (ret) fprintf (fout,"\n");
	return ret;
}

/*
	Affiche la liste de tous les modules en m‚moire. Utilis‚ pour d‚bug
*/
PUBLIC void MODULES::showall (FILE *fout)
{
	MODULE *ptmod = tbmod;
	for (int i=0; i<nbmod; i++, ptmod++){
		fprintf (fout,"%s %d\n",ptmod->name,ptmod->is_load);
		SYMBOL **ptpub = ptmod->pub.tb;
		int nbpub = ptmod->pub.nb;
		int e;
		for (e=0; e<nbpub; e++, ptpub++){
			SYMBOL *ptsym = *ptpub;
			fprintf (fout,"\tT %s %d %d %d %d %d\n",ptsym->name,ptsym->requis
				,ptsym->defini,ptsym->vue_avant
				,ptsym->is_common,ptsym->force);
		}
		SYMBOL **ptext = ptmod->ext.tb;
		int nbext = ptmod->ext.nb;
		for (e=0; e<nbext; e++, ptext++){
			fprintf (fout,"\tU %s %d\n",(*ptext)->name,(*ptext)->defini);
		}
	}
}

static int cmp (const void *p1, const void *p2)
{
	MODULE *pt1 = *(MODULE**)p1;
	MODULE *pt2 = *(MODULE**)p2;
	return strcmp(pt1->name,pt2->name);
}
/*
	Format the dependancy list of a module into a simple makefile
*/
PUBLIC void MODULES::prtdepend (
	FILE *fout,
	const char *dontcare,	// Module we don't want to know about
							// in the dependancy lists
	int verbose,		// Print all module visited
	int showerror)		// Shows undefined symbol
{
	MODULE **tbdep = new MODULE*[nbmod];
	MODULE *ptmod = tbmod;
	for (int i=0; i<nbmod; i++, ptmod++){
		if (strcmp(ptmod->name,dontcare)!=0){
			SYMBOL **ptext = ptmod->ext.tb;
			int nbext = ptmod->ext.nb;
			int nbdepmod = 0;
			int nberr = 0;
			for (int e=0; e<nbext; e++, ptext++){
				MODULE *mod = (*ptext)->module;
				if (mod==NULL){
					if (nberr == 0){
						depmod_error ("*** Unresolved symbols in module %s"
								,ptmod->name);
					}
					if (showerror){
						depmod_error ("\t%s",(*ptext)->name);
					}
					nberr++;
				}else{
					if (strcmp(mod->name,dontcare)!=0){
						int m;
						for (m=0; m<nbdepmod; m++){
							if (tbdep[m] == mod) break;
						}
						if (m == nbdepmod) tbdep[nbdepmod++] = mod;
					}
				}
			}
			if (nberr == 0 && verbose) printf ("%s\n",ptmod->name);
			// Sort so it is nicer to look at :-)
			qsort(tbdep,nbdepmod,sizeof(MODULE*),cmp);
			fprintf (fout,"%s:",ptmod->name);
			for (int m=0; m<nbdepmod; m++){
				if (m != 0 && (m & 3) == 0) fprintf (fout,"\\\n");
				fprintf (fout,"\t%s",tbdep[m]->name);
			}
			fprintf (fout,"\n\n");
		}
	}
	delete [] tbdep;
}

#ifdef TEST

int main (int argc, char *argv[])
{
	if (argc > 1){
		SYMBOLS syms;
		MODULES mods;
		for (int i=1; i<argc; i++){
			char *arg = argv[i];
			mods.loadobj (syms,arg);
		}
		int undef = mods.findundef (stdout);
		printf ("Nombre de undef %d\n",undef);
		char *tb[10000];
		int nbforce = syms.findforce (tb,10000);
		printf ("nbforce = %d\n",nbforce);
		int nbload = mods.showload (stdout);
		printf ("nbload = %d\n",nbload);
		//syms.dump(stdout);
	}
	return 0;
}

#endif
