#include <9pm/u.h>
#include <9pm/libc.h>
#include <9pm/bio.h>

unsigned char	odata[16];
unsigned char	data[16];
int		ndata;
unsigned long	addr;
int		repeats;
int		swizzle;
int		flush;
int		abase=2;
int		xd(char *, int);
void		xprint(char *, long);
void		initarg(void), swizz(void);
enum{
	Narg=10
};
typedef struct Arg Arg;
typedef void fmtfn(char *);
struct Arg
{
	int	ascii;		/* 0==none, 1==ascii */
	int	loglen;		/* 0==1, 1==2, 2==4, 3==8 */
	int	base;		/* 0==8, 1==10, 2==16 */
	fmtfn	*fn;		/* function to call with data */
	char	*afmt;		/* format to use to print address */
	char	*fmt;		/* format to use to print data */
}arg[Narg];
int	narg;

fmtfn	fmt0, fmt1, fmt2, fmt3, fmtc;
fmtfn *fmt[4] = {
	fmt0,
	fmt1,
	fmt2,
	fmt3
};

char *dfmt[4][3] = {
	" %.3uo",	" %.3ud",	" %.2ux",
	" %.6uo",	" %.5ud",	" %.4ux",
	" %.11luo",	" %.10lud",	" %.8lux",
	" %.22lluo",	" %.20llud",	" %.16llux",
};

char *cfmt[3][3] = {
	"   %c",	"   %c", 	"  %c",
	" %.3s",	" %.3s",	" %.2s",
	" %.3uo",	" %.3ud",	" %.2ux",
};

char *afmt[2][3] = {
	"%.7luo ",	"%.7lud ",	"%.7lux ",
	"%7luo ",	"%7lud ",	"%7lux ",
};

Biobuf	bin;
Biobuf	bout;

void
main(int argc, char *argv[])
{
	int i, err;
	Arg *ap;

	Binit(&bout, 1, OWRITE);
	err = 0;
	ap = 0;
	while(argc>1 && argv[1][0]=='-' && argv[1][1]){
		--argc;
		argv++;
		argv[0]++;
		if(argv[0][0] == 'r'){
			repeats = 1;
			if(argv[0][1])
				goto Usage;
			continue;
		}
		if(argv[0][0] == 's'){
			swizzle = 1;
			if(argv[0][1])
				goto Usage;
			continue;
		}
		if(argv[0][0] == 'u'){
			flush = 1;
			if(argv[0][1])
				goto Usage;
			continue;
		}
		if(argv[0][0] == 'a'){
			argv[0]++;
			switch(argv[0][0]){
			case 'o':
				abase = 0;
				break;
			case 'd':
				abase = 1;
				break;
			case 'x':
				abase = 2;
				break;
			default:
				goto Usage;
			}
			if(argv[0][1])
				goto Usage;
			continue;
		}
		ap = &arg[narg];
		initarg();
		while(argv[0][0]){
			switch(argv[0][0]){
			case 'c':
				ap->ascii = 1;
				ap->loglen = 0;
				if(argv[0][1] || argv[0][-1]!='-')
					goto Usage;
				break;
			case 'o':
				ap->base = 0;
				break;
			case 'd':
				ap->base = 1;
				break;
			case 'x':
				ap->base = 2;
				break;
			case 'b':
			case '1':
				ap->loglen = 0;
				break;
			case 'w':
			case '2':
				ap->loglen = 1;
				break;
			case 'l':
			case '4':
				ap->loglen = 2;
				break;
			case 'v':
			case '8':
				ap->loglen = 3;
				break;
			default:
			Usage:
   fprint(2, "usage: xd [-u] [-r] [-s] [-a{odx}] [-c|{b1w2l4v8}{odx}] ... file ...\n");
				exits("usage");
			}
			argv[0]++;
		}
		if(ap->ascii)
			ap->fn = fmtc;
		else
			ap->fn = fmt[ap->loglen];
		ap->fmt = dfmt[ap->loglen][ap->base];
		ap->afmt = afmt[ap>arg][abase];
	}
	if(narg == 0)
		initarg();
	if(argc == 1)
		err = xd(0, 0);
	else if(argc == 2)
		err = xd(argv[1], 0);
	else for(i=1; i<argc; i++)
		err |= xd(argv[i], 1);
	if(err)
		exits("error");
	exits(0);
}

void
initarg(void)
{
	Arg *ap;

	ap = &arg[narg++];
	if(narg >= Narg){
		fprint(2, "xd: too many formats (max %d)\n", Narg);
		exits("usage");
	}
	ap->ascii = 0;
	ap->loglen = 2;
	ap->base = 2;
	ap->fn = fmt2;
	ap->fmt = dfmt[ap->loglen][ap->base];
	ap->afmt = afmt[narg>1][abase];
}

int
xd(char *name, int title)
{
	int fd;
	int i, star;
	Arg *ap;
	Biobuf *bp;

	fd = 0;
	if(name){
		bp = Bopen(name, OREAD);
		if(bp == 0){
			fprint(2, "xd: can't open %s\n", name);
			return 1;
		}
	}else{
		bp = &bin;
		Binit(bp, fd, OREAD);
	}
	if(title)
		xprint("%s\n", (long)name);
	addr = 0;
	star = 0;
	while((ndata=Bread(bp, data, 16)) >= 0){
		if(ndata < 16)
			for(i=ndata; i<16; i++)
				data[i] = 0;
		if(swizzle)
			swizz();
		if(ndata==16 && repeats){
			if(addr>0 && data[0]==odata[0]){
				for(i=1; i<16; i++)
					if(data[i] != odata[i])
						break;
				if(i == 16){
					addr += 16;
					if(star == 0){
						star++;
						xprint("*\n", 0);
					}
					continue;
				}
			}
			for(i=0; i<16; i++)
				odata[i] = data[i];
			star = 0;
		}
		for(ap=arg; ap<&arg[narg]; ap++){
			xprint(ap->afmt, addr);
			(*ap->fn)(ap->fmt);
			xprint("\n", 0);
			if(flush)
				Bflush(&bout);
		}
		addr += ndata;
		if(ndata<16){
			xprint(afmt[0][abase], addr);
			xprint("\n", 0);
			if(flush)
				Bflush(&bout);
			break;
		}
	}
	Bterm(bp);
	return 0;
}

void
swizz(void)
{
	uchar *p, *q;
	int i;
	uchar swdata[16];

	p = data;
	q = swdata;
	for(i=0; i<16; i++)
		*q++ = *p++;
	p = data;
	q = swdata;
	for(i=0; i<4; i++){
		p[0] = q[3];
		p[1] = q[2];
		p[2] = q[1];
		p[3] = q[0];
		p += 4;
		q += 4;
	}
}

void
fmt0(char *f)
{
	int i;
	for(i=0; i<ndata; i++)
		xprint(f, data[i]);
}

void
fmt1(char *f)
{
	int i;
	for(i=0; i<ndata; i+=sizeof(unsigned short))
		xprint(f, (data[i]<<8)|data[i+1]);
}

void
fmt2(char *f)
{
	int i;
	for(i=0; i<ndata; i+=sizeof(unsigned long))
		xprint(f, (data[i]<<24)|(data[i+1]<<16)|(data[i+2]<<8)|data[i+3]);
}

void
fmt3(char *f)
{
	int i;
	uvlong v;
	for(i=0; i<ndata; i+=sizeof(uvlong)){
		v = (data[i]<<24)|(data[i+1]<<16)|(data[i+2]<<8)|data[i+3];
		v <<= 32;
		v |= (data[i+4]<<24)|(data[i+1+4]<<16)|(data[i+2+4]<<8)|data[i+3+4];
		if(Bprint(&bout, f, v)<0){
			fprint(2, "xd: i/o error\n");
			exits("i/o error");
		}
	}
}

void
fmtc(char *f)
{
	int i;

	USED(f);
	for(i=0; i<ndata; i++)
		switch(data[i]){
		case '\t':
			xprint(cfmt[1][2], (long)"\\t");
			break;
		case '\r':
			xprint(cfmt[1][2], (long)"\\r");
			break;
		case '\n':
			xprint(cfmt[1][2], (long)"\\n");
			break;
		case '\b':
			xprint(cfmt[1][2], (long)"\\b");
			break;
		default:
			if(data[i]>=0x7F || ' '>data[i])
				xprint(cfmt[2][2], data[i]);
			else
				xprint(cfmt[0][2], data[i]);
			break;
		}
}

void
xprint(char *fmt, long d)
{
	if(Bprint(&bout, fmt, d)<0){
		fprint(2, "xd: i/o error\n");
		exits("i/o error");
	}
}
