#include <u.h>
#include <libc.h>
#include <bio.h>

int debug;

int
b4(uchar *p)
{
	return (p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3];
}

int
b2(uchar *p)
{
	return (p[0]<<8)|p[1];
}

vlong
b8(uchar *p)
{
	return ((vlong)b4(p)<<32) | b4(p+4);
}

int
m4magic(Biobuf *b)
{
	uchar buf[12];

	Bseek(b, 0, 0);
	werrstr("short file");
	if(Bread(b, buf, 12) != 12)
		return -1;
	if(memcmp(buf+4, "ftyp", 4) != 0 && memcmp(buf+4, "FTYP", 4) != 0){
		werrstr("file does not start with ftyp atom");
		return -1;
	}
	if(b4(buf) < 2){
		werrstr("atom too short");
		return -1;
	}
	if((buf[8] != 'm' && buf[8] != 'M') || buf[9] != '4'){
		werrstr("not an m4 file");
		return -1;
	}
	Bseek(b, 0, 0);
	return 0;
}


typedef void Parser(Biobuf*, vlong, int, char*);

Parser m4atoms, m4meta, m4mvhd, m4data;

static struct {
	char *atom;
	Parser *parser;
} atoms[] =
{
	"ilst",	m4atoms,
	"mdia",	m4atoms,
	"minf",	m4atoms,
	"moov",	m4atoms,
	"stbl",	m4atoms,
	"trak",	m4atoms,
	"udta",	m4atoms,

	"meta",	m4meta,
	"mvhd",	m4mvhd,
//	"stsd",	

	"aart",	m4data,
	"akid",	m4data,
	"alb",	m4data,
	"apid",	m4data,
	"atid",	m4data,
	"art",		m4data,
	"cmt",	m4data,
	"cnid",	m4data,
	"cpil",	m4data,
	"cprt",	m4data,
	"day",	m4data,
	"disk",	m4data,
	"geid",	m4data,
	"gen",	m4data,
	"gnre",	m4data,
	"grp",	m4data,
	"nam",	m4data,
	"plid",	m4data,
	"rtng",	m4data,
	"tmpo",	m4data,
	"too",	m4data,
	"trkn",	m4data,
	"wrt",	m4data,
};

/* can't make this up */
char *gnres[] = {
	"N/A",
	"Blues",
	"Classic Rock",
	"Country",
	"Dance",
	"Disco",
	"Funk",
	"Grunge",
	"Hip-Hop",
	"Jazz",
	"Metal",
	"New Age",
	"Oldies",
	"Other",
	"Pop",
	"R&B",
	"Rap",
	"Reggae",
	"Rock",
	"Techno",
	"Industrial",
	"Alternative",
	"Ska",
	"Death Metal",
	"Pranks",
	"Soundtrack",
	"Euro-Techno",
	"Ambient",
	"Trip-Hop",
	"Vocal",
	"Jazz+Funk",
	"Fusion",
	"Trance",
	"Classical",
	"Instrumental",
	"Acid",
	"House",
	"Game",
	"Sound Clip",
	"Gospel",
	"Noise",
	"AlternRock",
	"Bass",
	"Soul",
	"Punk",
	"Space",
	"Meditative",
	"Instrumental Pop",
	"Instrumental Rock",
	"Ethnic",
	"Gothic",
	"Darkwave",
	"Techno-Industrial",
	"Electronic",
	"Pop-Folk",
	"Eurodance",
	"Dream",
	"Southern Rock",
	"Comedy",
	"Cult",
	"Gangsta",
	"Top 40",
	"Christian Rap",
	"Pop/Funk",
	"Jungle",
	"Native American",
	"Cabaret",
	"New Wave",
	"Psychadelic",
	"Rave",
	"Showtunes",
	"Trailer",
	"Lo-Fi",
	"Tribal",
	"Acid Punk",
	"Acid Jazz",
	"Polka",
	"Retro",
	"Musical",
	"Rock & Roll",
	"Hard Rock",
	"Folk",
	"Folk/Rock",
	"National Folk",
	"Swing",
	"Fast-Fusion",
	"Bebob",
	"Latin",
	"Revival",
	"Celtic",
	"Bluegrass",
	"Avantgarde",
	"Gothic Rock",
	"Progressive Rock",
	"Psychedelic Rock",
	"Symphonic Rock",
	"Slow Rock",
	"Big Band",
	"Chorus",
	"Easy Listening",
	"Acoustic",
	"Humour",
	"Speech",
	"Chanson",
	"Opera",
	"Chamber Music",
	"Sonata",
	"Symphony",
	"Booty Bass",
	"Primus",
	"Porn Groove",
	"Satire",
	"Slow Jam",
	"Club",
	"Tango",
	"Samba",
	"Folklore",
	"Ballad",
	"Power Ballad",
	"Rhythmic Soul",
	"Freestyle",
	"Duet",
	"Punk Rock",
	"Drum Solo",
	"A capella",
	"Euro-House",
	"Dance Hall",
	"Goa",
	"Drum & Bass",
	"Club House",
	"Hardcore",
	"Terror",
	"Indie",
	"BritPop",
	"NegerPunk",
	"Polsk Punk",
	"Beat",
	"Christian Gangsta",
	"Heavy Metal",
	"Black Metal",
	"Crossover",
	"Contemporary C",
	"Christian Rock",
	"Merengue",
	"Salsa",
	"Thrash Metal",
	"Anime",
	"JPop",
	"SynthPop"
};

Parser*
parser(char *atom)
{
	int i;

	for(i=0; i<nelem(atoms); i++)
		if(strcmp(atom, atoms[i].atom) == 0)
			return atoms[i].parser;
	return 0;
}

void
m4atoms(Biobuf *b, vlong e, int level, char *atom)
{
	long len;
	uchar buf[16];
	char atombuf[5];
	vlong o;
	Parser *p;
	char *s, *t;

	USED(atom);
	for(;;){
		o = Boffset(b);
		if(o >= e)
			break;
		if(Bread(b, buf, 8) != 8)
			break;
		len = b4(buf);
		if(len == 1)	/* should read 8 more bytes for 64-bit length */
			break;
		memmove(atombuf, buf+4, 4);
		atombuf[4] = 0;
		s = atombuf;
		while(*s == (char)0xA9)
			s++;
		for(t=s; *t; t++)
			*t = tolower(*t);
		if(debug) print("%*s%s %11d %22lld\n", level*2, "", s, len, Boffset(b)-8);
		if((p = parser(s)) != 0)
			(*p)(b, o+len, level+1, s);
		Bseek(b, o+len, 0);
	}
}

void
m4meta(Biobuf *b, vlong e, int level, char *atom)
{
	Bseek(b, 4, 1);	/* skip version */
	return m4atoms(b, e, level, atom);
}

void
m4mvhd(Biobuf *b, vlong e, int level, char *atom)
{
	int scale;
	vlong time;
	uchar buf[32];

	USED(atom);
	if(Bread(b, buf, 32) != 32 || Boffset(b) > e)
		return;

	switch(buf[0]){
	default:
		return;
	case 0:
		scale = b4(buf+12);
		time = b4(buf+16);
		break;
	case 1:
		scale = b4(buf+20);
		time = ((vlong)b4(buf+24)<<32)|b4(buf+28);
		break;
	}

	if(debug) print("%*smvhd %02lld:%02lld.%03lld %.3f\n", level*2, "", 
		time/scale/60, (time/scale)%60, ((time%scale)*1000+scale/2)/scale, 
		(double)time/scale);
}

void
m4data(Biobuf *b, vlong e, int level, char *atom)
{
	uchar *buf, *p;
	int len, type, n;
	char *s;

	len = e-Boffset(b);
	if(len < 16)
		return;

	buf = malloc(len+1);
	if(buf == nil)
		return;

	if(Bread(b, buf, len) != len){
		free(buf);
		return;
	}

	p = buf+16;
	len = b4(buf)-16;
	type = buf[11];

	switch(type){
	case 0:	/* 16-bit int */
		if(strcmp(atom, "gnre") == 0){
			n = b2(p);
			if(n >= nelem(gnres))
				print("genre %d\n", n);
			else
				print("genre %q\n", gnres[n]);
			break;
		}
		if(strcmp(atom, "trkn") == 0){
			print("track %d %d\n", b2(p+2), b2(p+4));
			break;
		}
		if(strcmp(atom, "disk") == 0){
			print("disk %d %d\n", b2(p+2), b2(p+4));
			break;
		}
		/*
		if(len >= 6)
			print("%s %d %d\n", atom, b2(p+2), b2(p+4));
		else
			print("%s %d\n", atom, b2(p+2));
		*/
		break;
	case 1:	/* string; might be big-endian runes, might be utf8.  sigh. */
		s = (char*)p;
		s[len] = 0;
		if(strlen(s) != len)
			break;
		if(strcmp(atom, "nam") == 0)
			print("title %q\n", s);
		else if(strcmp(atom, "art") == 0)
			print("artist %q\n", s);
		else if(strcmp(atom, "alb") == 0)
			print("album %q\n", s);
		else if(strcmp(atom, "wrt") == 0)
			print("writer %q\n", s);
		else if(strcmp(atom, "day") == 0)
			print("year %.4s\n", s);
		else if(strcmp(atom, "cprt") == 0)
			print("copyright %q\n", s);
		else if(strcmp(atom, "aart") == 0)
			print("albumartist %q\n", s);
		else if(strcmp(atom, "gen") == 0)
			print("genre %q\n", s);
		break;
	case 21:	/* raw bytes */
		if(debug) print("%s 0x%.*H\n", atom, len, p);
		break;
	}

	if(debug)
		print("%*sdata %d %.4s 0x%ux\n", level*2, "", b4(buf), buf+4, b4(buf+8));
	free(buf);
}

void
usage(void)
{
	fprint(2, "usage: m4ainfo [-d] file...\n");
	exits("usage");
}

void
main(int argc, char **argv)
{
	Biobuf *b;
	int i;
	vlong e;

	ARGBEGIN{
	case 'd':
		debug++;
		break;
	default:
		usage();
	}ARGEND

	quotefmtinstall();
	fmtinstall('H', encodefmt);
	for(i=0; i<argc; i++){
		print("# %q\n", argv[i]);
		if((b = Bopen(argv[i], OREAD)) == nil){
			fprint(2, "open %s: %r\n", argv[i]);
			continue;
		}
		if(m4magic(b) < 0){
			fprint(2, "%s: not an mpeg4 file\n", argv[i]);
			Bterm(b);
			continue;
		}
		e = Bseek(b, 0, 2);
		Bseek(b, 0, 0);
		m4atoms(b, e, 0, nil);
		Bterm(b);
	}
}
