#include <u.h>
#include <libc.h>
#include <bio.h>
#include <draw.h>
#include <event.h>
#include "sky.h"
#include "strings.c"

enum
{
	NNGC=7840,	/* number of NGC numbers [1..NNGC] */
	NIC = 5386,	/* number of IC numbers */
	NNGCrec=NNGC+NIC,	/* number of records in the NGC catalog (including IC's, starting at NNGC */
	NMrec=122,	/* number of M records */
	NM=110,		/* number of M numbers */
	NAbell=2712,	/* number of records in the Abell catalog */
	NName=1000,	/* number of prose names; estimated maximum (read from editable text file) */
	NBayer=1517,	/* number of bayer entries */
	NSAO=258998,	/* number of SAO stars */
	MAXcon=1932,	/* maximum number of patches in a constellation */
	Ncon=88,	/* number of constellations */
	Npatch=92053,	/* highest patch number */
};

char		ngctype[NNGCrec];
Mindexrec	mindex[NMrec];
Namerec		name[NName];
Bayerec		bayer[NBayer];
long		con[MAXcon];
ushort		conindex[Ncon+1];
long		patchaddr[Npatch+1];

Record	*rec;
Record	*orec;
Record	*cur;

char	*dir=DIR;
int	saodb;
int	ngcdb;
int	abelldb;
int	ngctypedb;
int	mindexdb;
int	namedb;
int	bayerdb;
int	condb;
int	conindexdb;
int	patchdb;
char	parsed[3];
long	nrec;
long	nreca;
long	norec;
long	noreca;

Biobuf	bin;
Biobuf	bout;

main(int argc, char *argv[])
{
	char *line;

	Binit(&bin, 0, OREAD);
	Binit(&bout, 1, OWRITE);
	if(argc != 1)
		dir = argv[1];
	astro("", 1);
	while(line = Brdline(&bin, '\n')){
		line[Blinelen(&bin)-1] = 0;
		lookup(line, 1);
		Bflush(&bout);
	}
	if(display != nil){
		closedisplay(display);
		/* automatic refresh of rio window is triggered by mouse */
		close(open("/dev/mouse", OREAD));
	}
	return 0;
}

void
reset(void)
{
	nrec = 0;
	cur = rec;
}

void
grow(void)
{
	nrec++;
	if(nreca < nrec){
		nreca = nrec+50;
		rec = realloc(rec, nreca*sizeof(Record));
		if(rec == 0){
			fprint(2, "scat: realloc fails\n");
			exits("realloc");
		}
	}
	cur = rec+nrec-1;
}

void
copy(void)
{
	if(noreca < nreca){
		noreca = nreca;
		orec = realloc(orec, nreca*sizeof(Record));
		if(orec == 0){
			fprint(2, "scat: realloc fails\n");
			exits("realloc");
		}
	}
	memmove(orec, rec, nrec*sizeof(Record));
	norec = nrec;
}

int
eopen(char *s)
{
	char buf[128];
	int f;

	sprint(buf, "%s/%s.scat", dir, s);
	f = open(buf, 0);
	if(f<0){
		fprint(2, "scat: can't open %s\n", buf);
		exits("open");
	}
	return f;
}


void
Eread(int f, char *name, void *addr, long n)
{
	if(read(f, addr, n) != n){	/* BUG! */
		fprint(2, "scat: read error on %s\n", name);
		exits("read");
	}
}

char*
skipbl(char *s)
{
	while(*s!=0 && (*s==' ' || *s=='\t'))
		s++;
	return s;
}

char*
skipstr(char *s, char *t)
{
	while(*s && *s==*t)
		s++, t++;
	return skipbl(s);
}

/* produce little-endian long at address l */
long
Long(long *l)
{
	uchar *p;

	p = (uchar*)l;
	return (long)p[0]|((long)p[1]<<8)|((long)p[2]<<16)|((long)p[3]<<24);
}

/* produce little-endian long at address l */
int
Short(short *s)
{
	uchar *p;

	p = (uchar*)s;
	return p[0]|(p[1]<<8);
}

void
nameopen(void)
{
	Biobuf b;
	int i;
	char *l, *p;

	if(namedb == 0){
		namedb = eopen("name");
		Binit(&b, namedb, OREAD);
		for(i=0; i<NName; i++){
			l = Brdline(&b, '\n');
			if(l == 0)
				break;
			p = strchr(l, '\t');
			if(p == 0){
		Badformat:
				Bprint(&bout, "warning: name.scat bad format; line %d\n", i+1);
				break;
			}
			*p++ = 0;
			strcpy(name[i].name, l);
			if(strncmp(p, "ngc", 3) == 0)
				name[i].ngc = atoi(p+3);
			else if(strncmp(p, "ic", 2) == 0)
				name[i].ngc = atoi(p+2)+NNGC;
			else if(strncmp(p, "sao", 3) == 0)
				name[i].sao = atoi(p+3);
			else if(strncmp(p, "abell", 5) == 0)
				name[i].abell = atoi(p+5);
			else
				goto Badformat;
		}
		if(i == NName)
			Bprint(&bout, "warning: too many names in name.scat (max %d); extra ignored\n", NName);
		close(namedb);

		bayerdb = eopen("bayer");
		Eread(bayerdb, "bayer", bayer, sizeof bayer);
		close(bayerdb);
		for(i=0; i<NBayer; i++)
			bayer[i].sao = Long(&bayer[i].sao);
	}
}

void
saoopen(void)
{
	if(saodb == 0){
		nameopen();
		saodb = eopen("sao");
	}
}

void
ngcopen(void)
{
	if(ngcdb == 0){
		nameopen();
		ngcdb = eopen("ngc2000");
		ngctypedb = eopen("ngc2000type");
		Eread(ngctypedb, "ngctype", ngctype, sizeof ngctype);
		close(ngctypedb);
	}
}

void
abellopen(void)
{
	/* nothing extra to do with abell: it's directly indexed by number */
	if(abelldb == 0)
		abelldb = eopen("abell");
}

void
patchopen(void)
{
	Biobuf *b;
	long l, m;
	char buf[100];

	if(patchdb == 0){
		patchdb = eopen("patch");
		sprint(buf, "%s/patchindex.scat", dir);
		b = Bopen(buf, OREAD);
		if(b == 0){
			fprint(2, "can't open %s\n", buf);
			exits("open");
		}
		for(m=0,l=0; l<=Npatch; l++)
			patchaddr[l] = m += Bgetc(b)*4;
		Bterm(b);
	}
}

void
mopen(void)
{
	int i;

	if(mindexdb == 0){
		mindexdb = eopen("mindex");
		Eread(mindexdb, "mindex", mindex, sizeof mindex);
		close(mindexdb);
		for(i=0; i<NMrec; i++)
			mindex[i].ngc = Short(&mindex[i].ngc);
	}
}

void
constelopen(void)
{
	int i;

	if(condb == 0){
		condb = eopen("con");
		conindexdb = eopen("conindex");
		Eread(conindexdb, "conindex", conindex, sizeof conindex);
		close(conindexdb);
		for(i=0; i<Ncon+1; i++)
			conindex[i] = Short((short*)&conindex[i]);
	}
}

void
lowercase(char *s)
{
	for(; *s; s++)
		if('A'<=*s && *s<='Z')
			*s += 'a'-'A';
}

int
loadngc(long index)
{
	static int failed;
	long j;

	ngcopen();
	j = (index-1)*sizeof(NGCrec);
	grow();
	cur->type = NGC;
	cur->index = index;
	seek(ngcdb, j, 0);
	/* special case: NGC data may not be available */
	if(read(ngcdb, &cur->ngc, sizeof(NGCrec)) != sizeof(NGCrec)){
		if(!failed){
			fprint(2, "scat: NGC database not available\n");
			failed++;
		}
		cur->type = NONGC;
		cur->ngc.ngc = 0;
		cur->ngc.ra = 0;
		cur->ngc.dec = 0;
		cur->ngc.diam = 0;
		cur->ngc.mag = 0;
		return 0;
	}
	cur->ngc.ngc = Short(&cur->ngc.ngc);
	cur->ngc.ra = Long(&cur->ngc.ra);
	cur->ngc.dec = Long(&cur->ngc.dec);
	cur->ngc.diam = Long(&cur->ngc.diam);
	cur->ngc.mag = Short(&cur->ngc.mag);
	return 1;
}

int
loadabell(long index)
{
	long j;

	abellopen();
	j = index-1;
	grow();
	cur->type = Abell;
	cur->index = index;
	seek(abelldb, j*sizeof(Abellrec), 0);
	Eread(abelldb, "abell", &cur->abell, sizeof(Abellrec));
	cur->abell.abell = Short(&cur->abell.abell);
	if(cur->abell.abell != index){
		fprint(2, "bad format in abell catalog\n");
		exits("abell");
	}
	cur->abell.ra = Long(&cur->abell.ra);
	cur->abell.dec = Long(&cur->abell.dec);
	cur->abell.glat = Long(&cur->abell.glat);
	cur->abell.glong = Long(&cur->abell.glong);
	cur->abell.rad = Long(&cur->abell.rad);
	cur->abell.mag10 = Short(&cur->abell.mag10);
	cur->abell.pop = Short(&cur->abell.pop);
	cur->abell.dist = Short(&cur->abell.dist);
	return 1;
}

int
loadsao(int index)
{
	if(index<=0 || index>NSAO)
		return 0;
	saoopen();
	grow();
	cur->type = SAO;
	cur->index = index;
	seek(saodb, (index-1)*sizeof(SAOrec), 0);
	Eread(saodb, "sao", &cur->sao, sizeof(SAOrec));
	cur->sao.ra = Long(&cur->sao.ra);
	cur->sao.dec = Long(&cur->sao.dec);
	cur->sao.dra = Long(&cur->sao.dra);
	cur->sao.ddec = Long(&cur->sao.ddec);
	cur->sao.mag = Short(&cur->sao.mag);
	cur->sao.mpg = Short(&cur->sao.mpg);
	cur->sao.hd = Long(&cur->sao.hd);
	return 1;
}

int
loadplanet(int index, Record *r)
{
	if(index<0 || index>NPlanet || planet[index].name[0]=='\0')
		return 0;
	grow();
	cur->type = Planet;
	cur->index = index;
	/* check whether to take new or existing record */
	if(r == nil)
		memmove(&cur->planet, &planet[index], sizeof(Planetrec));
	else
		memmove(&cur->planet, &r->planet, sizeof(Planetrec));
	return 1;
}

int
loadpatch(long index)
{
	int i;

	patchopen();
	if(index<=0 || index>Npatch)
		return 0;
	grow();
	cur->type = Patch;
	cur->index = index;
	seek(patchdb, patchaddr[index-1], 0);
	cur->patch.nkey = (patchaddr[index]-patchaddr[index-1])/4;
	Eread(patchdb, "patch", cur->patch.key, cur->patch.nkey*4);
	for(i=0; i<cur->patch.nkey; i++)
		cur->patch.key[i] = Long(&cur->patch.key[i]);
	return 1;
}

int
loadtype(int t)
{
	int i;

	ngcopen();
	for(i=0; i<NNGCrec; i++)
		if(t == (ngctype[i])){
			grow();
			cur->type = NGCN;
			cur->index = i+1;
		}
	return 1;
}

void
flatten(void)
{
	int i, j, notflat;
	Record *or;
	long key;

    loop:
	copy();
	reset();
	notflat = 0;
	for(i=0,or=orec; i<norec; i++,or++){
		switch(or->type){
		default:
			fprint(2, "bad type %d in flatten\n", or->type);
			break;

		case NONGC:
			break;

		case Planet:
		case Abell:
		case NGC:
		case SAO:
			grow();
			memmove(cur, or, sizeof(Record));
			break;

		case NGCN:
			if(loadngc(or->index))
				notflat = 1;
			break;

		case NamedSAO:
			loadsao(or->index);
			notflat = 1;
			break;

		case NamedNGC:
			if(loadngc(or->index))
				notflat = 1;
			break;

		case NamedAbell:
			loadabell(or->index);
			notflat = 1;
			break;

		case PatchC:
			loadpatch(or->index);
			notflat = 1;
			break;

		case Patch:
			for(j=1; j<or->patch.nkey; j++){
				key = or->patch.key[j];
				if((key&0x3F) == SAO)
					loadsao((key>>8)&0xFFFFFF);
				else if((key&0x3F) == Abell)
					loadabell((key>>8)&0xFFFFFF);
				else
					loadngc((key>>16)&0xFFFF);
			}
			break;
		}
	}
	if(notflat)
		goto loop;
}

int
ism(int index)
{
	int i;

	for(i=0; i<NMrec; i++)
		if(mindex[i].ngc == index)
			return 1;
	return 0;
}

char*
alpha(char *s, char *t)
{
	int n;

	n = strlen(t);
	if(strncmp(s, t, n)==0 && (s[n]<'a' || 'z'<s[n]))
		return skipbl(s+n);
	return 0;
	
}

char*
text(char *s, char *t)
{
	int n;

	n = strlen(t);
	if(strncmp(s, t, n)==0 && (s[n]==0 || s[n]==' ' || s[n]=='\t'))
		return skipbl(s+n);
	return 0;
	
}

int
cull(char *s, int keep, int dobbox)
{
	int i, j, nobj, keepthis;
	Record *or;
	char *t;
	int dogrtr, doless, dom, dosao, dongc, doabell;
	int mgrtr, mless;
	char obj[100];

	memset(obj, 0, sizeof(obj));
	nobj = 0;
	dogrtr = 0;
	doless = 0;
	dom = 0;
	dongc = 0;
	dosao = 0;
	doabell = 0;
	mgrtr = mless= 0;
	if(dobbox)
		goto Cull;
	for(;;){
		if(s[0] == '>'){
			dogrtr = 1;
			mgrtr = 10 * strtod(s+1, &t);
			if(mgrtr==0  && t==s+1){
				fprint(2, "bad magnitude\n");
				return 0;
			}
			s = skipbl(t);
			continue;
		}
		if(s[0] == '<'){
			doless = 1;
			mless = 10 * strtod(s+1, &t);
			if(mless==0  && t==s+1){
				fprint(2, "bad magnitude\n");
				return 0;
			}
			s = skipbl(t);
			continue;
		}
		if(t = text(s, "m")){
 			dom = 1;
			s = t;
			continue;
		}
		if(t = text(s, "sao")){
			dosao = 1;
			s = t;
			continue;
		}
		if(t = text(s, "ngc")){
			dongc = 1;
			s = t;
			continue;
		}
		if(t = text(s, "abell")){
			doabell = 1;
			s = t;
			continue;
		}
		for(i=0; names[i].name; i++)
			if(t = alpha(s, names[i].name)){
				if(nobj > 100){
					fprint(2, "too many object types\n");
					return 0;
				}
				obj[nobj++] = names[i].type;
				s = t;
				goto Continue;
			}
		break;
	    Continue:;
	}
	if(*s){
		fprint(2, "syntax error in object list\n");
		return 0;
	}

    Cull:
	flatten();
	copy();
	reset();
	if(dom)
		mopen();
	if(dosao)
		saoopen();
	if(dongc || nobj)
		ngcopen();
	if(doabell)
		abellopen();
	for(i=0,or=orec; i<norec; i++,or++){
		keepthis = !keep;
		if(dobbox && inbbox(or->ngc.ra, or->ngc.dec))
			keepthis = keep;
		if(doless && or->ngc.mag <= mless)
			keepthis = keep;
		if(dogrtr && or->ngc.mag >= mgrtr)
			keepthis = keep;
		if(dom && (or->type==NGC && ism(or->ngc.ngc)))
			keepthis = keep;
		if(dongc && or->type==NGC)
			keepthis = keep;
		if(doabell && or->type==Abell)
			keepthis = keep;
		if(dosao && or->type==SAO)
			keepthis = keep;
		for(j=0; j<nobj; j++)
			if(or->type==NGC && or->ngc.type==obj[j])
				keepthis = keep;
		if(keepthis){
			grow();
			memmove(cur, or, sizeof(Record));
		}
	}
	return 1;
}

int
compar(void *va, void *vb)
{
	Record *a=va, *b=vb;

	if(a->type == b->type)
		return a->index - b->index;
	return a->type - b->type;
}

void
sort(void)
{
	int i;
	Record *r, *s;

	if(nrec == 0)
		return;
	qsort(rec, nrec, sizeof(Record), compar);
	r = rec+1;
	s = rec;
	for(i=1; i<nrec; i++,r++){
		/* may have multiple instances of a planet in the scene */
		if(r->type==s->type && r->index==s->index && r->type!=Planet)
			continue;
		memmove(++s, r, sizeof(Record));
	}
	nrec = (s+1)-rec;
}

char	greekbuf[128];

char*
togreek(char *s)
{
	char *t;
	int i, n;
	Rune r;

	t = greekbuf;
	while(*s){
		for(i=1; i<=24; i++){
			n = strlen(greek[i]);
			if(strncmp(s, greek[i], n)==0 && (s[n]==' ' || s[n]=='\t')){
				s += n;
				t += runetochar(t, &greeklet[i]);
				goto Cont;
			}
		}
		n = chartorune(&r, s);
		for(i=0; i<n; i++)
			*t++ = *s++;
    Cont:;
	}
	*t = 0;
	return greekbuf;
}

char*
fromgreek(char *s)
{
	char *t;
	int i, n;
	Rune r;

	t = greekbuf;
	while(*s){
		n = chartorune(&r, s);
		for(i=1; i<=24; i++){
			if(r == greeklet[i]){
				strcpy(t, greek[i]);
				t += strlen(greek[i]);
				s += n;
				goto Cont;
			}
		}
		for(i=0; i<n; i++)
			*t++ = *s++;
    Cont:;
	}
	*t = 0;
	return greekbuf;
}

#ifdef OLD
/*
 * Old version
 */
int
coords(int deg)
{
	int i;
	int x, y;
	Record *or;
	long dec, ra, ndec, nra;
	int rdeg;

	flatten();
	copy();
	reset();
	deg *= 2;
	for(i=0,or=orec; i<norec; i++,or++){
		if(or->type == Planet)	/* must keep it here */
			loadplanet(or->index, or);
		dec = or->ngc.dec/MILLIARCSEC;
		ra = or->ngc.ra/MILLIARCSEC;
		rdeg = deg/cos((dec*PI)/180);
		for(y=-deg; y<=+deg; y++){
			ndec = dec*2+y;
			if(ndec/2>=90 || ndec/2<=-90)
				continue;
			/* fp errors hurt here, so we round 1' to the pole */
			if(ndec >= 0)
				ndec = ndec*500*60*60 + 60000;
			else
				ndec = ndec*500*60*60 - 60000;
			for(x=-rdeg; x<=+rdeg; x++){
				nra = ra*2+x;
				if(nra/2 < 0)
					nra += 360*2;
				if(nra/2 >= 360)
					nra -= 360*2;
				/* fp errors hurt here, so we round up 1' */
				nra = nra/2*MILLIARCSEC + 60000;
				loadpatch(patcha(angle(nra), angle(ndec)));
			}
		}
	}
	sort();
	return 1;
}
#endif

/*
 * New version attempts to match the boundaries of the plot better.
 */
int
coords(int deg)
{
	int i;
	int x, y, xx;
	Record *or;
	long min, circle;
	double factor;

	flatten();
	circle = 360*MILLIARCSEC;
	deg *= MILLIARCSEC;

	/* find center */
	folded = 0;
	bbox(0, 0, 0);
	/* now expand */
	factor = cos(angle((decmax+decmin)/2));
	if(factor < .2)
		factor = .2;
	factor = floor(1/factor);
	folded = 0;
	bbox(factor*deg, deg, 1);
	Bprint(&bout, "%s to ", hms(angle(ramin)));
	Bprint(&bout, "%s\n", hms(angle(ramax)));
	Bprint(&bout, "%s to ", dms(angle(decmin)));
	Bprint(&bout, "%s\n", dms(angle(decmax)));
	copy();
	reset();
	for(i=0,or=orec; i<norec; i++,or++)
		if(or->type == Planet)	/* must keep it here */
			loadplanet(or->index, or);
	min = ramin;
	if(ramin > ramax)
		min -= circle;
	for(x=min; x<=ramax; x+=250*60*60){
		xx = x;
		if(xx < 0)
			xx += circle;
		for(y=decmin; y<=decmax; y+=250*60*60)
			if(-circle/4 < y && y < circle/4)
				loadpatch(patcha(angle(xx), angle(y)));
	}
	sort();
	cull(nil, 1, 1);
	return 1;
}

void
pplate(char *flags)
{
	int i;
	long c;
	int na, rah, ram, d1, d2;
	double r0;
	int ra, dec;
	long ramin, ramax, decmin, decmax;	/* all in degrees */
	Record *r;
	int folded;
	Angle racenter, deccenter, rasize, decsize, a[4];
	Picture *pic;

	rasize = -1.0;
	decsize = -1.0;
	na = 0;
	for(;;){
		while(*flags==' ')
			flags++;
		if(('0'<=*flags && *flags<='9') || *flags=='+' || *flags=='-'){
			if(na >= 3)
				goto err;
			a[na++] = getra(flags);
			while(*flags && *flags!=' ')
				flags++;
			continue;
		}
		if(*flags){
	err:
			Bprint(&bout, "syntax error in plate\n");
			return;
		}
		break;
	}
	switch(na){
	case 0:
		break;
	case 1:
		rasize = a[0];
		decsize = rasize;
		break;
	case 2:
		rasize = a[0];
		decsize = a[1];
		break;
	case 3:
	case 4:
		racenter = a[0];
		deccenter = a[1];
		rasize = a[2];
		if(na == 4)
			decsize = a[3];
		else
			decsize = rasize;
		if(rasize<0.0 || decsize<0.0){
			Bprint(&bout, "negative sizes\n");
			return;
		}
		goto done;
	}
	folded = 0;
	/* convert to milliarcsec */
	c = 1000*60*60;
    Again:
	if(nrec == 0){
		Bprint(&bout, "empty\n");
		return;
	}
	ramin = 0x7FFFFFFF;
	ramax = -0x7FFFFFFF;
	decmin = 0x7FFFFFFF;
	decmax = -0x7FFFFFFF;
	for(r=rec,i=0; i<nrec; i++,r++){
		if(r->type == Patch){
			radec(r->index, &rah, &ram, &dec);
			ra = 15*rah+ram/4;
			r0 = c/cos(RAD(dec));
			ra *= c;
			dec *= c;
			if(dec == 0)
				d1 = c, d2 = c;
			else if(dec < 0)
				d1 = c, d2 = 0;
			else
				d1 = 0, d2 = c;
		}else if(r->type==SAO || r->type==NGC || r->type==Abell){
			ra = r->ngc.ra;
			dec = r->ngc.dec;
			d1 = 0, d2 = 0, r0 = 0;
		}else if(r->type==NGCN){
			loadngc(r->index);
			continue;
		}else if(r->type==NamedSAO){
			loadsao(r->index);
			continue;
		}else if(r->type==NamedNGC){
			loadngc(r->index);
			continue;
		}else if(r->type==NamedAbell){
			loadabell(r->index);
			continue;
		}else
			continue;
		if(dec+d2 > decmax)
			decmax = dec+d2;
		if(dec-d1 < decmin)
			decmin = dec-d1;
		if(folded){
			ra -= 180*c;
			if(ra < 0)
				ra += 360*c;
		}
		if(ra+r0 > ramax)
			ramax = ra+r0;
		if(ra < ramin)
			ramin = ra;
	}
	if(!folded && ramax-ramin>270*c){
		folded = 1;
		goto Again;
	}
	racenter = angle(ramin+(ramax-ramin)/2);
	deccenter = angle(decmin+(decmax-decmin)/2);
	if(rasize<0 || decsize<0){
		rasize = angle(ramax-ramin)*cos(deccenter);
		decsize = angle(decmax-decmin);
	}
    done:
	if(DEG(rasize)>1.1 || DEG(decsize)>1.1){
		Bprint(&bout, "plate too big: %s", ms(rasize));
		Bprint(&bout, " x %s\n", ms(decsize));
		Bprint(&bout, "trimming to 30'x30'\n");
		rasize = RAD(0.5);
		decsize = RAD(0.5);
	}
	Bprint(&bout, "%s %s ", hms(racenter), dms(deccenter));
	Bprint(&bout, "%s", ms(rasize));
	Bprint(&bout, " x %s\n", ms(decsize));
	Bflush(&bout);
	flatten();
	pic = image(racenter, deccenter, rasize, decsize);
	if(pic == 0)
		return;
	Bprint(&bout, "plate %s locn %d %d %d %d\n", pic->name, pic->minx, pic->miny, pic->maxx, pic->maxy);
	Bflush(&bout);
	displaypic(pic);
}

void
lookup(char *s, int doreset)
{
	int i, j, k;
	int rah, ram, deg;
	char *starts, *inputline=s, *t, *u;
	Record *r;
	long n;
	double x;
	Angle ra;

	lowercase(s);
	s = skipbl(s);

	if(*s == 0)
		goto Print;

	if(t = alpha(s, "flat")){
		if(*t){
			fprint(2, "flat takes no arguments\n");
			return;
		}
		if(nrec == 0){
			fprint(2, "no records\n");
			return;
		}
		flatten();
		goto Print;
	}

	if(t = alpha(s, "print")){
		if(*t){
			fprint(2, "print takes no arguments\n");
			return;
		}
		for(i=0,r=rec; i<nrec; i++,r++)
			prrec(r);
		return;
	}

	if(t = alpha(s, "add")){
		lookup(t, 0);
		return;
	}

	if(t = alpha(s, "sao")){
		n = strtoul(t, &u, 10);
		if(n<=0 || n>NSAO)
			goto NotFound;
		t = skipbl(u);
		if(*t){
			fprint(2, "syntax error in sao\n");
			return;
		}
		if(doreset)
			reset();
		if(!loadsao(n))
			goto NotFound;
		goto Print;
	}

	if(t = alpha(s, "ngc")){
		n = strtoul(t, &u, 10);
		if(n<=0 || n>NNGC)
			goto NotFound;
		t = skipbl(u);
		if(*t){
			fprint(2, "syntax error in ngc\n");
			return;
		}
		if(doreset)
			reset();
		if(!loadngc(n))
			goto NotFound;
		goto Print;
	}

	if(t = alpha(s, "ic")){
		n = strtoul(t, &u, 10);
		if(n<=0 || n>NIC)
			goto NotFound;
		t = skipbl(u);
		if(*t){
			fprint(2, "syntax error in ic\n");
			return;
		}
		if(doreset)
			reset();
		if(!loadngc(n+NNGC))
			goto NotFound;
		goto Print;
	}

	if(t = alpha(s, "abell")){
		n = strtoul(t, &u, 10);
		if(n<=0 || n>NAbell)
			goto NotFound;
		if(doreset)
			reset();
		if(!loadabell(n))
			goto NotFound;
		goto Print;
	}

	if(t = alpha(s, "m")){
		n = strtoul(t, &u, 10);
		if(n<=0 || n>NM)
			goto NotFound;
		mopen();
		for(j=n-1; mindex[j].m<n; j++)
			;
		if(doreset)
			reset();
		while(mindex[j].m == n){
			if(mindex[j].ngc){
				grow();
				cur->type = NGCN;
				cur->index = mindex[j].ngc;
			}
			j++;
		}
		goto Print;
	}

	for(i=1; i<=Ncon; i++)
		if(t = alpha(s, constel[i])){
			if(*t){
				fprint(2, "syntax error in constellation\n");
				return;
			}
			constelopen();
			seek(condb, 4L*conindex[i-1], 0);
			j = conindex[i]-conindex[i-1];
			Eread(condb, "con", con, 4*j);
			if(doreset)
				reset();
			for(k=0; k<j; k++){
				grow();
				cur->type = PatchC;
				cur->index = Long(&con[k]);
			}
			goto Print;
		}

	if(t = alpha(s, "expand")){
		n = 0;
		if(*t){
			if(*t<'0' && '9'<*t){
		Expanderr:
				fprint(2, "syntax error in expand\n");
				return;
			}
			n = strtoul(t, &u, 10);
			t = skipbl(u);
			if(*t)
				goto Expanderr;
		}
		coords(n);
		goto Print;
	}

	if(t = alpha(s, "plot")){
		if(nrec == 0){
			Bprint(&bout, "empty\n");
			return;
		}
		plot(t);
		return;
	}

	if(t = alpha(s, "astro")){
		astro(t, 0);
		return;
	}

	if(t = alpha(s, "plate")){
		pplate(t);
		return;
	}

	if(t = alpha(s, "gamma")){
		while(*t==' ')
			t++;
		u = t;
		x = strtod(t, &u);
		if(u > t)
			gam.gamma = x;
		Bprint(&bout, "%.2f\n", gam.gamma);
		return;
	}

	if(t = alpha(s, "keep")){
		if(!cull(t, 1, 0))
			return;
		goto Print;
	}

	if(t = alpha(s, "drop")){
		if(!cull(t, 0, 0))
			return;
		goto Print;
	}

	for(i=0; planet[i].name[0]; i++){
		if(t = alpha(s, planet[i].name)){
			if(doreset)
				reset();
			loadplanet(i, nil);
			goto Print;
		}
	}

	for(i=0; names[i].name; i++){
		if(t = alpha(s, names[i].name)){
			if(*t){
				fprint(2, "syntax error in type\n");
				return;
			}
			if(doreset)
				reset();
			loadtype(names[i].type);
			goto Print;
		}
	}

	switch(s[0]){
	case '"':
		starts = ++s;
		while(*s != '"')
			if(*s++ == 0){
				fprint(2, "bad star name\n");
				return;
			}
		*s = 0;
		if(doreset)
			reset();
		j = nrec;
		saoopen();
		starts = fromgreek(starts);
		for(i=0; i<NName; i++)
			if(equal(starts, name[i].name)){
				grow();
				if(name[i].sao){
					rec[j].type = NamedSAO;
					rec[j].index = name[i].sao;
				}
				if(name[i].ngc){
					rec[j].type = NamedNGC;
					rec[j].index = name[i].ngc;
				}
				if(name[i].abell){
					rec[j].type = NamedAbell;
					rec[j].index = name[i].abell;
				}
				strcpy(rec[j].named.name, name[i].name);
				j++;
			}
		if(parsename(starts))
			for(i=0; i<NBayer; i++)
				if(bayer[i].name[0]==parsed[0] &&
				  (bayer[i].name[1]==parsed[1] || parsed[1]==0) &&
				   bayer[i].name[2]==parsed[2]){
					grow();
					rec[j].type = NamedSAO;
					rec[j].index = bayer[i].sao;
					strncpy(rec[j].named.name, starts, sizeof(rec[j].named.name));
					j++;
				}
		if(j == 0){
			*s = '"';
			goto NotFound;
		}
		break;

	case '0': case '1': case '2': case '3': case '4':
	case '5': case '6': case '7': case '8': case '9':
		strtoul(s, &t, 10);
		if(*t != 'h'){
	BadCoords:
			fprint(2, "bad coordinates %s\n", inputline);
			break;
		}
		ra = DEG(getra(s));
		while(*s && *s!=' ' && *s!='\t')
			s++;
		rah = ra/15;
		ra = ra-rah*15;
		ram = ra*4;
		deg = strtol(s, &t, 10);
		if(t == s)
			goto BadCoords;
		/* degree sign etc. is optional */
		if((uchar)*t == L'°')
			deg = DEG(getra(s));
		if(doreset)
			reset();
		if(abs(deg)>=90 || rah>=24)
			goto BadCoords;
		if(!loadpatch(patch(rah, ram, deg)))
			goto NotFound;
		break;

	default:
		fprint(2, "unknown command %s\n", inputline);
		return;
	}

    Print:
	if(nrec == 0)
		Bprint(&bout, "empty\n");
	else if(nrec <= 2)
		for(i=0; i<nrec; i++)
			prrec(rec+i);
	else
		Bprint(&bout, "%ld items\n", nrec);
	return;

    NotFound:
	fprint(2, "%s not found\n", inputline);
	return;
}

char *ngctypes[] =
{
[Galaxy] 		"Gx",
[PlanetaryN]	"Pl",
[OpenCl]		"OC",
[GlobularCl]	"Gb",
[DiffuseN]		"Nb",
[NebularCl]	"C+N",
[Asterism]		"Ast",
[Knot]		"Kt",
[Triple]		"***",
[Double]		"D*",
[Single]		"*",
[Uncertain]	"?",
[Nonexistent]	"-",
[Unknown]	" ",
[PlateDefect]	"PD",
};

char*
ngcstring(int d)
{
	if(d<Galaxy || d>PlateDefect)
		return "can't happen";
	return ngctypes[d];
}

short	descindex[NINDEX];

void
printnames(Record *r)
{
	int i, ok, done;

	done = 0;
	for(i=0; i<NName; i++){	/* stupid linear search! */
		ok = 0;
		if(r->type==SAO && r->index==name[i].sao)
			ok = 1;
		if(r->type==NGC && r->ngc.ngc==name[i].ngc)
			ok = 1;
		if(r->type==Abell && r->abell.abell==name[i].abell)
			ok = 1;
		if(ok){
			if(done++ == 0)
				Bprint(&bout, "\t");
			Bprint(&bout, " \"%s\"", togreek(name[i].name));
		}
	}
	if(done)
		Bprint(&bout, "\n");
}

int
equal(char *s1, char *s2)
{
	int c;

	while(*s1){
		if(*s1==' '){
			while(*s1==' ')
				s1++;
			continue;
		}
		while(*s2==' ')
			s2++;
		c=*s2;
		if('A'<=*s2 && *s2<='Z')
			c^=' ';
		if(*s1!=c)
			return 0;
		s1++, s2++;
	}
	return 1;
}

int
parsename(char *s)
{
	char *blank;
	int i;

	blank = strchr(s, ' ');
	if(blank==0 || strchr(blank+1, ' ') || strlen(blank+1)!=3)
		return 0;
	blank++;
	parsed[0] = parsed[1] = parsed[2] = 0;
	if('0'<=s[0] && s[0]<='9'){
		i = atoi(s);
		parsed[0] = i;
		if(i > 100)
			return 0;
	}else{
		for(i=1; i<=24; i++)
			if(strncmp(greek[i], s, strlen(greek[i]))==0){
				parsed[0]=100+i;
				goto out;
			}
		return 0;
	    out:
		if('0'<=s[strlen(greek[i])] && s[strlen(greek[i])]<='9')
			parsed[1]=s[strlen(greek[i])]-'0';
	}
	for(i=1; i<=88; i++)
		if(strcmp(constel[i], blank)==0){
			parsed[2] = i;
			return 1;
		}
	return 0;
}

char*
dist_grp(int dg)
{
	switch(dg){
	default:
		return "unknown";
	case 1:
		return "13.3-14.0";
	case 2:
		return "14.1-14.8";
	case 3:
		return "14.9-15.6";
	case 4:
		return "15.7-16.4";
	case 5:
		return "16.5-17.2";
	case 6:
		return "17.3-18.0";
	case 7:
		return ">18.0";
	}
}

char*
rich_grp(int dg)
{
	switch(dg){
	default:
		return "unknown";
	case 0:
		return "30-40";
	case 1:
		return "50-79";
	case 2:
		return "80-129";
	case 3:
		return "130-199";
	case 4:
		return "200-299";
	case 5:
		return ">=300";
	}
}

char*
nameof(Record *r)
{
	NGCrec *n;
	SAOrec *s;
	Abellrec *a;
	static char buf[128];
	int i;

	switch(r->type){
	default:
		return nil;
	case SAO:
		s = &r->sao;
		if(s->name[0] == 0)
			return nil;
		if(s->name[0] >= 100){
			i = snprint(buf, sizeof buf, "%C", greeklet[s->name[0]-100]);
			if(s->name[1])
				i += snprint(buf+i, sizeof buf-i, "%d", s->name[1]);
		}else
			i = snprint(buf, sizeof buf, " %d", s->name[0]);
		snprint(buf+i, sizeof buf-i, " %s", constel[s->name[2]]);
		break;
	case NGC:
		n = &r->ngc;
		if(n->type >= Uncertain)
			return nil;
		if(n->ngc <= NNGC)
			snprint(buf, sizeof buf, "NGC%4d ", n->ngc);
		else
			snprint(buf, sizeof buf, "IC%4d ", n->ngc-NNGC);
		break;
	case Abell:
		a = &r->abell;
		snprint(buf, sizeof buf, "Abell%4d", a->abell);
		break;
	}
	return buf;
}

void
prrec(Record *r)
{
	NGCrec *n;
	SAOrec *s;
	Abellrec *a;
	Planetrec *p;
	int i, rah, ram, dec, nn;
	long key;

	if(r) switch(r->type){
	default:
		fprint(2, "can't prrec type %d\n", r->type);
		exits("type");

	case Planet:
		p = &r->planet;
		Bprint(&bout, "%s", p->name);
		Bprint(&bout, "\t%s %s",
			hms(angle(p->ra)),
			dms(angle(p->dec)));
		Bprint(&bout, " %3.2f° %3.2f°",
			p->az/(double)MILLIARCSEC, p->alt/(double)MILLIARCSEC);
		Bprint(&bout, " %s",
			ms(angle(p->semidiam)));
		if(r->index <= 1)
			Bprint(&bout, " %g", p->phase);
		Bprint(&bout, "\n");
		break;

	case NGC:
		n = &r->ngc;
		if(n->ngc <= NNGC)
			Bprint(&bout, "NGC%4d ", n->ngc);
		else
			Bprint(&bout, "IC%4d ", n->ngc-NNGC);
		Bprint(&bout, "%s ", ngcstring(n->type));
		if(n->mag == UNKNOWNMAG)
			Bprint(&bout, "----");
		else
			Bprint(&bout, "%.1f%c", n->mag/10.0, n->magtype);
		Bprint(&bout, "\t%s %s\t%c%.1f'\n",
			hm(angle(n->ra)),
			dm(angle(n->dec)),
			n->diamlim,
			DEG(angle(n->diam))*60.);
		prdesc(n->desc, desctab, descindex);
		printnames(r);
		break;

	case Abell:
		a = &r->abell;
		Bprint(&bout, "Abell%4d  %.1f %.2f° %dMpc", a->abell, a->mag10/10.0,
			DEG(angle(a->rad)), a->dist);
		Bprint(&bout, "\t%s %s\t%.2f %.2f\n",
			hm(angle(a->ra)),
			dm(angle(a->dec)),
			DEG(angle(a->glat)),
			DEG(angle(a->glong)));
		Bprint(&bout, "\tdist grp: %s  rich grp: %s  %d galaxies/°²\n",
			dist_grp(a->distgrp),
			rich_grp(a->richgrp),
			a->pop);
		printnames(r);
		break;

	case SAO:
		s = &r->sao;
		Bprint(&bout, "SAO%6ld  ", r->index);
		if(s->mag==UNKNOWNMAG)
			Bprint(&bout, "---");
		else
			Bprint(&bout, "%.1f", s->mag/10.0);
		if(s->mpg==UNKNOWNMAG)
			Bprint(&bout, ",---");
		else
			Bprint(&bout, ",%.1f", s->mpg/10.0);
		Bprint(&bout, "  %s %s  %.4fs %.3f\"",
			hms(angle(s->ra)),
			dms(angle(s->dec)),
			DEG(angle(s->dra))*(4*60),
			DEG(angle(s->ddec))*(60*60));
		Bprint(&bout, "  %.3s %c %.2s %ld %d",
			s->spec, s->code, s->compid, s->hd, s->hdcode);
		if(s->name[0])
			Bprint(&bout, " \"%s\"", nameof(r));
		Bprint(&bout, "\n");
		printnames(r);
		break;

	case Patch:
		radec(r->index, &rah, &ram, &dec);
		Bprint(&bout, "%dh%dm %d°", rah, ram, dec);
		key = r->patch.key[0];
		Bprint(&bout, " %s", constel[key&0xFF]);
		if((key>>=8) & 0xFF)
			Bprint(&bout, " %s", constel[key&0xFF]);
		if((key>>=8) & 0xFF)
			Bprint(&bout, " %s", constel[key&0xFF]);
		if((key>>=8) & 0xFF)
			Bprint(&bout, " %s", constel[key&0xFF]);
		for(i=1; i<r->patch.nkey; i++){
			key = r->patch.key[i];
			switch(key&0x3F){
			case SAO:
				Bprint(&bout, " SAO%ld", (key>>8)&0xFFFFFF);
				break;
			case Abell:
				Bprint(&bout, " Abell%ld", (key>>8)&0xFFFFFF);
				break;
			default:	/* NGC */
				nn = (key>>16)&0xFFFF;
				if(nn > NNGC)
					Bprint(&bout, " IC%d", nn-NNGC);
				else
					Bprint(&bout, " NGC%d", nn);
				Bprint(&bout, "(%s)", ngcstring(key&0x3F));
				break;
			}
		}
		Bprint(&bout, "\n");
		break;

	case NGCN:
		if(r->index <= NNGC)
			Bprint(&bout, "NGC%ld\n", r->index);
		else
			Bprint(&bout, "IC%ld\n", r->index-NNGC);
		break;

	case NamedSAO:
		Bprint(&bout, "SAO%ld \"%s\"\n", r->index, togreek(r->named.name));
		break;

	case NamedNGC:
		if(r->index <= NNGC)
			Bprint(&bout, "NGC%ld \"%s\"\n", r->index, togreek(r->named.name));
		else
			Bprint(&bout, "IC%ld \"%s\"\n", r->index-NNGC, togreek(r->named.name));
		break;

	case NamedAbell:
		Bprint(&bout, "Abell%ld \"%s\"\n", r->index, togreek(r->named.name));
		break;

	case PatchC:
		radec(r->index, &rah, &ram, &dec);
		Bprint(&bout, "%dh%dm %d\n", rah, ram, dec);
		break;
	}
}
