#include "bzlib.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <string.h>


// compile with gcc patches/fs2xml.c -lbz2

struct inode
{ 
	char name[128];
	char src[128];
	char link[128];
	int mode;
	int type;
	int size;
	int uid;
	int gid;
	int parentid;
	char *data;
	int compressed;
	int load;
	unsigned int mtime;
};

struct inode *inodes;
int ninodes = 0;

char currentpackage[256];

struct __attribute__ ((__packed__)) posix_header
{                              /* byte offset */
  char name[100];               /*   0 */
  char mode[8];                 /* 100 */
  char uid[8];                  /* 108 */
  char gid[8];                  /* 116 */
  char size[12];                /* 124 */
  char mtime[12];               /* 136 */
  char chksum[8];               /* 148 */
  char typeflag;                /* 156 */
  char linkname[100];           /* 157 */
  char magic[6];                /* 257 */
  char version[2];              /* 263 */
  char uname[32];               /* 265 */
  char gname[32];               /* 297 */
  char devmajor[8];             /* 329 */
  char devminor[8];             /* 337 */
  char prefix[155];             /* 345 */
                                /* 500 */
};

char* GetFullPath(const char* root, int id)
{
	static char path[1024];
	char temp[1024];
	path[0] = 0;
	temp[0] = 0;
	while(id != -1)
	{
		strcpy(temp, path);
		sprintf(path, "/%s%s", inodes[id].name, temp);
		id = inodes[id].parentid;
	}
	strcpy(temp, path);
	sprintf(path, "%s%s", root, temp);
	return path;
}

char* GetFullPathNotHidden(const char* root, int id, int *hidden)
{
	static char path[1024];
	char temp[1024];
	path[0] = 0;
	temp[0] = 0;
	while(id != -1)
	{
		strcpy(temp, path);
                if (inodes[id].name[0] == '.') {
		    sprintf(path, "/%s%s", &(inodes[id].name[1]), temp);
		    *hidden = 1;
                } else
                if (inodes[id].name[0] == '_') {
		    sprintf(path, "/%s%s", &(inodes[id].name[1]), temp);
		    *hidden = 1;
                } else
		    sprintf(path, "/%s%s", inodes[id].name, temp);
		id = inodes[id].parentid;
	}
	strcpy(temp, path);
	sprintf(path, "%s%s", root, temp);
	return path;
}

int decompress(const char *filename, unsigned char *buf)
{
	BZFILE *BZ2fp_r = NULL;
	char name[256];
	sprintf(name, "bin/%s", filename);
	BZ2fp_r = BZ2_bzopen(name, "rb");
	if (BZ2fp_r == NULL)
	{
		fprintf(stderr, "Error: Cannot open bz2 file\n");
		exit(1);
		return 0;
	}

	int len = BZ2_bzread(BZ2fp_r, buf, 60*1024*1024);
	BZ2_bzclose(BZ2fp_r);

	//printf("decompressed size: %i\n", len);
	return len;
}

int Split(char *name, char walk[12][128])
{
	int n = strlen(name);
	int p = 0;
	int pn = 0;
	int i = 0;
	if (name[0] == '/') i=1;
	if (name[0] == '.') i=2;


	for(; i<n; i++)
	{
		if (name[i] == '/') { walk[pn][p]=0; p=0; pn++; continue;}
		walk[pn][p] = name[i];
		p++;
	}
	walk[pn][p]=0;
	if (name[n-1] == '/') pn--;
	return pn+1;
}

int IsEmpty(int parentid)
{
	int i=0;
	for(i=0; i<ninodes; i++)
	{
		if (inodes[i].parentid == parentid) return 0;		
	}

	return 1;
}

int SearchInode(char *name, int parentid)
{
	int i = 0;
//	printf("%s\n", name);
	for(i=0; i<ninodes; i++)
	{
		if (inodes[i].parentid != parentid) continue;		
		if ( strcmp(name, inodes[i].name) != 0) continue;
		//printf("found equal %s %s at index %i of %i and parentid %i\n", name, inodes[i].name, i, ninodes, parentid);
		return i;
	}
	return -1;
}


void MergeFile(struct posix_header* ph)
{
	char walk[12][128];
	int n = Split(ph->name, walk);
	//printf("%i\n", n);
	int parentid = -1;
	int i = 0;

	for(i=0; i<n-1; i++)
	{
		int ret = SearchInode(walk[i], parentid);
		if (ret == -1) 
		{
			fprintf(stderr, "Error: Could not find inode. But it should be there. Walk position: %i\n", i);
			exit(1);
		}
		parentid = ret;
	}
	int ret = SearchInode(walk[n-1], parentid);

	if ((ret != -1) && (ph->typeflag != '5'))
	{
		fprintf(stderr, "Warning: File '%s' already exists with id = %i and name '%s'\n", 
		walk[n-1], ret, inodes[ret].name);
		//exit(1);
		return; // do not overwrite
	}

	if ((ret != -1) && (ph->typeflag == '5')) return; // file exists


	strncpy(inodes[ninodes].name, walk[n-1], 127);
	//strncpy(inodes[ninodes].src, currentpackage, 127);
	inodes[ninodes].src[0] = 0;
	strncpy(inodes[ninodes].link, ph->linkname, 100);
	inodes[ninodes].parentid = parentid;
	inodes[ninodes].size = strtol(ph->size, NULL, 8);
	inodes[ninodes].uid = strtol(ph->uid, NULL, 8);
	inodes[ninodes].gid = strtol(ph->gid, NULL, 8);
        if ((inodes[ninodes].uid != 0) && (inodes[ninodes].uid != 1000)) {
            inodes[ninodes].uid = 0;
            inodes[ninodes].gid = 0;
        }
	inodes[ninodes].mtime = strtol(ph->mtime, NULL, 8);
		
	int mode = strtol(ph->mode, NULL, 8); 

	switch(ph->typeflag)
	{
		case '5':
			mode |= S_IFDIR;
		break;
		case '0':
			mode |= S_IFREG;
			char *p = (char*)ph;	
			if (inodes[ninodes].size != 0) 
			{
				inodes[ninodes].data = malloc(inodes[ninodes].size);
				memcpy(inodes[ninodes].data, p+512, inodes[ninodes].size);
			}
		break;
		case '1':
		case '2':
			mode |= S_IFLNK;
			if (ph->linkname[0] == 0)
			{
				fprintf(stderr, "Error: no link given\n");
				exit(1);
			}
			if (ph->typeflag == '1') {
		            strncpy(inodes[ninodes].link+1, ph->linkname, 100);
                            inodes[ninodes].link[0] = '/';
			}

			printf("%s\n", ph->linkname);
		break;
		default:
			printf("Error:type %c unknown\n", ph->typeflag);
			exit(1);
		break;
	}
	inodes[ninodes].mode = mode;

	ninodes++;
}


void Untar(unsigned char *buf, int len)
{
	struct posix_header* ph;
	int p = 0;
	while(p < len)
	{
		ph = (struct posix_header*)&buf[p];
		//printf("%s\n", ph->magic);
		p += 0x200;
		if (strncmp(ph->magic, "ustar", 5) != 0)
		{
			//ph->magic[5] = 0;
			//printf("magic wrong: %s\n", ph->magic);
		}
		if (strncmp(ph->magic, "ustar", 5) != 0) continue;
		//int size = atoi(ph->size);
		int size = strtol(ph->size, NULL, 8);
		printf("%c %s %8i %s\n", ph->typeflag, ph->mode, size, ph->name);
		MergeFile(ph);

		if (size != 0) {
			p = p + size;
			// round up
			if (p & 511) {p = p & (~0x1FF); /*p += 0x200;*/}
		}
	}
}

void PrintIdent(FILE *fp, int sub)
{
	int i = 0;
	for(i=0; i<sub; i++)
	{
		fprintf(fp, "\t");
	}
}



void WalkXML(FILE *fp, int parentid, int sub)
{
	int i=0;
	for(i=0; i<ninodes; i++)
	{
		if (inodes[i].parentid != parentid) continue;
		if ((inodes[i].mode & S_IFMT) == S_IFDIR){

			PrintIdent(fp, sub);

			fprintf(fp, "<Dir name='%s' mode='%o'", inodes[i].name, inodes[i].mode);
			if (inodes[i].uid)
				fprintf(fp, " uid='%i'", inodes[i].uid);
			if (inodes[i].gid)
				fprintf(fp, " gid='%i'", inodes[i].gid);

			fprintf(fp, ">");
			if (!(IsEmpty(i)))
				fprintf(fp, "\n");
/*
			fprintf(fp, "<Dir>");

			//PrintIdent(fp, sub+1);
			fprintf(fp, " <Name>");
			fprintf(fp, "%s", inodes[i].name);
			fprintf(fp, "</Name>");

			//PrintIdent(fp, sub+1);
			fprintf(fp, " <Mode>");
			fprintf(fp, "%o", inodes[i].mode);
			fprintf(fp, "</Mode>\n");
*/
			WalkXML(fp, i, sub+1);
			
			if (!(IsEmpty(i))) PrintIdent(fp, sub);			
			fprintf(fp, "</Dir>\n");
		} else
		if ((inodes[i].mode & S_IFMT) == S_IFREG){

			PrintIdent(fp, sub);
//			fprintf(fp, "<File name='%s' mode='%o' src='%s' />\n", inodes[i].name, inodes[i].mode, inodes[i].src);

			fprintf(fp, "<File name='%s' mode='%o' size='%i'", inodes[i].name, inodes[i].mode, inodes[i].size);

			if (inodes[i].uid)
				fprintf(fp, " uid='%i'", inodes[i].uid);
			if (inodes[i].gid)
				fprintf(fp, " gid='%i'", inodes[i].gid);
			if (inodes[i].compressed)
				fprintf(fp, " compressed='1'");
			if (inodes[i].load)
				fprintf(fp, " load='1'");
			if (inodes[i].src[0] != 0)
				fprintf(fp, " src='%s'", inodes[i].src);
			fprintf(fp, "/>\n");

/*
			fprintf(fp, "<File>");

			//PrintIdent(fp, sub+1);
			fprintf(fp, " <Name>");
			fprintf(fp, "%s", inodes[i].name);
			fprintf(fp, "</Name>");

			//PrintIdent(fp, sub+1);
			fprintf(fp, " <Package>");
			fprintf(fp, "%s", inodes[i].src);
			fprintf(fp, "</Package>");

			//PrintIdent(fp, sub+1);
			fprintf(fp, " <Size>");
			fprintf(fp, "%i", inodes[i].size);
			fprintf(fp, "</Size>");

			//PrintIdent(fp, sub+1);
			fprintf(fp, " <Mode>");
			fprintf(fp, "%o", inodes[i].mode);
			fprintf(fp, "</Mode>");

			//PrintIdent(fp, sub);
			fprintf(fp, " </File>\n");
*/
		} else
		if ((inodes[i].mode & S_IFMT) == S_IFLNK){

			PrintIdent(fp, sub);
			fprintf(fp, "<Link name='%s' mode='%o' path='%s' />\n", inodes[i].name, inodes[i].mode, inodes[i].link);

/*
			fprintf(fp, "<Link>");

			//PrintIdent(fp, sub+1);
			fprintf(fp, " <Name>");
			fprintf(fp, "%s", inodes[i].name);
			fprintf(fp, "</Name>");

			//PrintIdent(fp, sub+1);
			fprintf(fp, " <Path>");
			fprintf(fp, "%s", inodes[i].link);
			fprintf(fp, "</Path>");

			//PrintIdent(fp, sub+1);
			fprintf(fp, " <Mode>");
			fprintf(fp, "%o", inodes[i].mode);
			fprintf(fp, "</Mode>");


			//PrintIdent(fp, sub);
			fprintf(fp, " </Link>\n");
*/
		}
	}

}

void CreateXML()
{
	FILE *fp = fopen("fs.xml", "w");
//	fprintf(fp, "<FS n=\"%i\">\n", ninodes);
	fprintf(fp, "<FS src='bin/sysroot/'>\n", ninodes);
	WalkXML(fp, -1, 1);
	fprintf(fp, "</FS>\n");
	fclose(fp);
}

int ShouldBeLoaded(char *name)
{
	if (strcmp(name, "libc.so") == 0) return 1;
	if (strcmp(name, "libncurses.so.5.9") == 0) return 1;
	if (strcmp(name, "libmenu.so.5.9") == 0) return 1;
	if (strcmp(name, "libgcc_s.so.1") == 0) return 1;
	return 0;
}


int ShouldBeCompressed(char *name, char *data, int size)
{
	if (size < 4) return 0;

	int len=strlen(name);
	if (len > 4) { // already compressed?
	if (name[len-3] == '.')
	if (name[len-2] == 'g')
	if (name[len-1] == 'z') return 0;

	if (name[len-4] == '.')
	if (name[len-3] == 'b')
	if (name[len-2] == 'z')
	if (name[len-1] == '2') return 0;

	if (name[len-4] == '.')
	if (name[len-3] == 'z')
	if (name[len-2] == 'i')
	if (name[len-1] == 'p') return 0;
	}

	if (size > 10*1024) return 1;

	if (data[0] == 0x7F)
	if (data[1] == 'E')
	if (data[2] == 'L')
	if (data[3] == 'F')
	{
		//printf("found binary\n");
		return 1;
	}
	return 0;
}

void RemoveFirstSignofString(char *s)
{
	int n = strlen(s);
	int i;
	for(i=0; i<n; i++)
	{
		s[i] = s[i+1];
	}
}

void AddFirstSigntoString(char *s, char x)
{
	int n = strlen(s);
	int i;
	for(i=n; i>=0; i--)
	{
		s[i+1] = s[i];
	}
	s[0] = x;
}

void WalkDir(int parentid)
{
	char *path;
	int hidden = 0;
	path = GetFullPathNotHidden("bin/fs", parentid, &hidden);
	//printf("mkdir %s\n", path);
	mkdir(path, 0777);
	int i = 0;
	for(i=0; i<ninodes; i++)
	{
		if (inodes[i].parentid != parentid) continue;
		if ((inodes[i].mode & S_IFMT) == S_IFDIR) 
		{
			WalkDir(i);
		}
		if ((inodes[i].mode & S_IFMT) == S_IFREG) 
		{
                        hidden = 0;
			path = GetFullPathNotHidden("bin/fs", i, &hidden);
                        if (hidden) {
				strcpy(inodes[i].src, &path[7]);
				printf("hidden file: %s src: %s path: %s\n", inodes[i].name, inodes[i].src, path);

                        }
/*
			if (inodes[i].name[0] == '.') {
				RemoveFirstSignofString(inodes[i].name);
				path = GetFullPath("bin/fs", i);
				strcpy(inodes[i].src, &path[7]);
				AddFirstSigntoString(inodes[i].name, '.');
				printf("hidden file: %s src: %s path: %s\n", inodes[i].name, inodes[i].src, path);
			}
*/
			FILE *fp = fopen(path, "wb");
			if (fp == NULL) {
				printf("Couldn't write file %s\n", path);
				exit(1);
				continue;
			}
			//printf("%s\n", inodes[i].name);
			if (inodes[i].size != 0)
				fwrite(inodes[i].data, inodes[i].size, 1, fp);
			fclose(fp);
			if (ShouldBeCompressed(inodes[i].name, inodes[i].data, inodes[i].size))
			{
				inodes[i].compressed = 1;
				char command[1024];
				sprintf(command, "bzip2 -f \"%s\"\n", path);
				//sprintf(command, "xz -e -f %s\n", path);
				system(command);
			}
			if (ShouldBeLoaded(inodes[i].name)) {
                             inodes[i].load = 1;
                        }

		}
	}
}

void CreateFilesystem()
{
	WalkDir(-1);
}

const char* packages[] =
{
"DirectFB-examples.tar.bz2",
"gcc.tar.bz2",
"nbench.tar.bz2",
"Frodo.tar.bz2",
"joe.tar.bz2",
"ncurses-dev.tar.bz2",
"SDL-dev.tar.bz2",
"ncurses.tar.bz2",
"alsa.tar.bz2",
"SDL.tar.bz2",
"libjpeg-turbo.tar.bz2",
"nmap.tar.bz2",
"openssh.tar.bz2",
"libpng.tar.bz2",
"bc.tar.bz2",
"links.tar.bz2",
"openssl.tar.bz2",
"certs.tar.bz2",
"binutils.tar.bz2",
"prboom.tar.bz2",
"lua.tar.bz2",
"scummvm.tar.bz2",
"lynx.tar.bz2",
"strace.tar.bz2",
"directfb.tar.bz2",
"make.tar.bz2",
"dosbox.tar.bz2",
"monkey.tar.bz2",
"tslib.tar.bz2",
"freetype.tar.bz2",
"fontconfig.tar.bz2",
"pixman.tar.bz2",
"musl-dev.tar.bz2",
"zlib.tar.bz2",
"zlib-dev.tar.bz2",
"frotz.tar.bz2",
"musl.tar.bz2",
"gcc-libs.tar.bz2",
"nano.tar.bz2",
"libX11.tar.bz2",
"xkbcomp.tar.bz2",
"font.tar.bz2",
"mesa.tar.bz2",
"xorg-server.tar.bz2",
"glxgears.tar.bz2",
"xinit.tar.bz2",
"xclock.tar.bz2",
"xterm.tar.bz2",
"twm.tar.bz2",
"xeyes.tar.bz2",
"xcalc.tar.bz2",
"expat.tar.bz2",
"libffi.tar.bz2",
"cairo.tar.bz2",
"wayland.tar.bz2",
"libdrm.tar.bz2",
"mtdev.tar.bz2",
"eudev.tar.bz2",
"libxkbcommon.tar.bz2",
"weston.tar.bz2",
"xsetroot.tar.bz2",
"curl.tar.bz2",
"fltk.tar.bz2",
"dillo.tar.bz2",
"toppler.tar.bz2",
"mplayer.tar.bz2",
"frontier.tar.bz2",
"alsa-utils.tar.bz2",
"ltrace.tar.bz2",
"htop.tar.bz2",
"libevdev.tar.bz2",
"libinput.tar.bz2",
"compilepackages.tar.bz2",
"autoconf.tar.bz2",
"vim.tar.bz2",
"git.tar.bz2",
"file.tar.bz2",
"screen.tar.bz2",
"aalib.tar.bz2",
"bb.tar.bz2",
"libevent.tar.bz2",
"libevent-dev.tar.bz2",
"automake.tar.bz2",
"m4.tar.bz2",
"perl.tar.bz2",
"ruby.tar.bz2",
"php.tar.bz2",
"flisp.tar.bz2",
"python.tar.bz2",
"tcl.tar.bz2",
"tmux.tar.bz2",
NULL
};

/*
"expect.tar.bz2",
"libcaca.tar.bz2",
"pango.tar.bz2",
"glib.tar.bz2",
"gdk-pixbuf.tar.bz2",
"gtk.tar.bz2",
"atk.tar.bz2",
*/
//"mikmod.tar.bz2",
//"libsidplay.tar.bz2",
//"d1x.tar.bz2",
//"dune.tar.bz2",
//"quake.tar.bz2",
//"fluxbox.tar.bz2",
//"freera.tar.bz2",
//"fire.tar.bz2",


int main()
{
	unsigned char *buf = (unsigned char*)malloc(60 * 1024 * 1024);

	inodes = (struct inode*) malloc(10000 * sizeof(struct inode));
	memset(inodes, 0, 10000 * sizeof(struct inode));

//	strncpy(inodes[ninodes].name, "/", 127);
//	ninodes = 1;

	int i=0;
	char filename[256];
	for(i=0; packages[i] != NULL; i++)
	{
		//sprintf(filename, "bin/%s", packages[i]);
		printf("decompress: %s\n", packages[i]);
		int len = decompress(packages[i], buf);
		Untar(buf, len);
	}

/*
	struct dirent *dp;
	DIR *dirp = opendir("bin");
	if (dirp == NULL) return 1;
	while (1)
	{
		errno = 0;
		dp = readdir(dirp);
		if (dp == NULL) break;

		int l = strlen(dp->d_name);
		if (dp->d_name[l-1] != '2') continue;
		printf("decompress: %s\n", dp->d_name);
		int len = decompress(dp->d_name, buf);
		if (len == 0) continue;
		strncpy(currentpackage, dp->d_name, 256);
		Untar(buf, len);
	}
	closedir(dirp);
*/

	fprintf(stderr, "number of inodes: %i\n", ninodes);
	int n=0;
	int size = 0;
	for(i=0; i<ninodes; i++)
	{
		if ((inodes[i].mode&S_IFMT) != S_IFREG) continue;
		n++;
		size += inodes[i].size;
	}
	fprintf(stderr, "number of files: %i with %f MB\n", n, (double)size/1024./1024.);

	CreateFilesystem();

	fprintf(stderr, "Generate XML\n");
	CreateXML();

	return 0;
}
