text: #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];
  int32		con[MAXcon];
  ushort		conindex[Ncon+1];
  int32		patchaddr[Npatch+1];
  
  Record	*rec;
  Record	*orec;
  Record	*cur;
  
  char	*dir;
  int	saodb;
  int	ngcdb;
  int	abelldb;
  int	ngctypedb;
  int	mindexdb;
  int	namedb;
  int	bayerdb;
  int	condb;
  int	conindexdb;
  int	patchdb;
  char	parsed[3];
  int32	nrec;
  int32	nreca;
  int32	norec;
  int32	noreca;
  
  Biobuf	bin;
  Biobuf	bout;
  
  void
  main(int argc, char *argv[])
  {
  	char *line;
  
  	dir = unsharp(DIR);
  	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;
  }
  
  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, int32 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 int32 at address l */
  int32
  Long(int32 *l)
  {
  	uchar *p;
  
  	p = (uchar*)l;
  	return (int32)p[0]|((int32)p[1]<<8)|((int32)p[2]<<16)|((int32)p[3]<<24);
  }
  
  /* produce little-endian int32 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;
  	int32 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(int32 index)
  {
  	static int failed;
  	int32 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->u.ngc, sizeof(NGCrec)) != sizeof(NGCrec)){
  		if(!failed){
  			fprint(2, "scat: NGC database not available\n");
  			failed++;
  		}
  		cur->type = NONGC;
  		cur->u.ngc.ngc = 0;
  		cur->u.ngc.ra = 0;
  		cur->u.ngc.dec = 0;
  		cur->u.ngc.diam = 0;
  		cur->u.ngc.mag = 0;
  		return 0;
  	}
  	cur->u.ngc.ngc = Short(&cur->u.ngc.ngc);
  	cur->u.ngc.ra = Long(&cur->u.ngc.ra);
  	cur->u.ngc.dec = Long(&cur->u.ngc.dec);
  	cur->u.ngc.diam = Long(&cur->u.ngc.diam);
  	cur->u.ngc.mag = Short(&cur->u.ngc.mag);
  	return 1;
  }
  
  int
  loadabell(int32 index)
  {
  	int32 j;
  
  	abellopen();
  	j = index-1;
  	grow();
  	cur->type = Abell;
  	cur->index = index;
  	seek(abelldb, j*sizeof(Abellrec), 0);
  	Eread(abelldb, "abell", &cur->u.abell, sizeof(Abellrec));
  	cur->u.abell.abell = Short(&cur->u.abell.abell);
  	if(cur->u.abell.abell != index){
  		fprint(2, "bad format in abell catalog\n");
  		exits("abell");
  	}
  	cur->u.abell.ra = Long(&cur->u.abell.ra);
  	cur->u.abell.dec = Long(&cur->u.abell.dec);
  	cur->u.abell.glat = Long(&cur->u.abell.glat);
  	cur->u.abell.glong = Long(&cur->u.abell.glong);
  	cur->u.abell.rad = Long(&cur->u.abell.rad);
  	cur->u.abell.mag10 = Short(&cur->u.abell.mag10);
  	cur->u.abell.pop = Short(&cur->u.abell.pop);
  	cur->u.abell.dist = Short(&cur->u.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->u.sao, sizeof(SAOrec));
  	cur->u.sao.ra = Long(&cur->u.sao.ra);
  	cur->u.sao.dec = Long(&cur->u.sao.dec);
  	cur->u.sao.dra = Long(&cur->u.sao.dra);
  	cur->u.sao.ddec = Long(&cur->u.sao.ddec);
  	cur->u.sao.mag = Short(&cur->u.sao.mag);
  	cur->u.sao.mpg = Short(&cur->u.sao.mpg);
  	cur->u.sao.hd = Long(&cur->u.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->u.planet, &planet[index], sizeof(Planetrec));
  	else
  		memmove(&cur->u.planet, &r->u.planet, sizeof(Planetrec));
  	return 1;
  }
  
  int
  loadpatch(int32 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->u.patch.nkey = (patchaddr[index]-patchaddr[index-1])/4;
  	Eread(patchdb, "patch", cur->u.patch.key, cur->u.patch.nkey*4);
  	for(i=0; i<cur->u.patch.nkey; i++)
  		cur->u.patch.key[i] = Long(&cur->u.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;
  	int32 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->u.patch.nkey; j++){
  				key = or->u.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->u.ngc.ra, or->u.ngc.dec))
  			keepthis = keep;
  		if(doless && or->u.ngc.mag <= mless)
  			keepthis = keep;
  		if(dogrtr && or->u.ngc.mag >= mgrtr)
  			keepthis = keep;
  		if(dom && (or->type==NGC && ism(or->u.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->u.ngc.type==obj[j])
  				keepthis = keep;
  		if(keepthis){
  			grow();
  			memmove(cur, or, sizeof(Record));
  		}
  	}
  	return 1;
  }
  
  int
  compar(const void *va, const void *vb)
  {
  	Record *a=(Record*)va, *b=(Record*)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;
  	int32 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->u.ngc.dec/MILLIARCSEC;
  		ra = or->u.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;
  	int32 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;
  	int32 c;
  	int na, rah, ram, d1, d2;
  	double r0;
  	int ra, dec;
  	int32 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->u.ngc.ra;
  			dec = r->u.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;
  	Rune c;
  	int32 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].u.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].u.named.name, starts, sizeof(rec[j].u.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 */
  		chartorune(&c, t);
  		if(c == 0xb0)
  			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->u.ngc.ngc==name[i].ngc)
  			ok = 1;
  		if(r->type==Abell && r->u.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->u.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[(uchar)s->name[2]]);
  		break;
  	case NGC:
  		n = &r->u.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->u.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;
  	int32 key;
  
  	if(r) switch(r->type){
  	default:
  		fprint(2, "can't prrec type %d\n", r->type);
  		exits("type");
  
  	case Planet:
  		p = &r->u.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->u.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->u.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->u.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->u.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->u.patch.nkey; i++){
  			key = r->u.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->u.named.name));
  		break;
  
  	case NamedNGC:
  		if(r->index <= NNGC)
  			Bprint(&bout, "NGC%ld \"%s\"\n", r->index, togreek(r->u.named.name));
  		else
  			Bprint(&bout, "IC%ld \"%s\"\n", r->index-NNGC, togreek(r->u.named.name));
  		break;
  
  	case NamedAbell:
  		Bprint(&bout, "Abell%ld \"%s\"\n", r->index, togreek(r->u.named.name));
  		break;
  
  	case PatchC:
  		radec(r->index, &rah, &ram, &dec);
  		Bprint(&bout, "%dh%dm %d\n", rah, ram, dec);
  		break;
  	}
  }
  