#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;
       }
}