/*++++++++++++++
.IDENTIFICATION ucamake.c
.LANGUAGE       C
.AUTHOR         Francois Ochsenbein [CDS]
.ENVIRONMENT    USNO-A2.0 Catalogue
.KEYWORDS       CDS Catalogue Server
.VERSION  1.0   21-Apr-2001: UCAC1
.COMMENTS       Create the compressed binary files of UCAC1

	The original files are ASCII files contaning 
        ------------------------------------------------
         1-10  I10   mas    RAmas   RA  J2000 IRCS
        11-20  I10   mas    SPDmas  Declination ICRS
        21-25  I5    cmag   mag     UCAC Magnitude
        26-29  I4    mas    sx      Mean error on RA*cos(Dec)
        30-33  I4    mas    sy      Mean error on Declination
        34-36  I3    ---    no      Number of observations
        37-41  I5  yr/1000  Ep      Epoch of RA and Dec (origin=1997.0)
        42-48  I7 0.1mas/yr pmRA    Proper motion 
        49-55  I7 0.1mas/yr pmDE    Proper motion
        56-59  I4 0.1mas/yr e_pmRA  spx Mean error on pmRA
        60-63  I4 0.1mas/yr e_pmDE  Mean error on pmDE
        64-66  I3    ---    nc      Number of catalog positions
        ------------------------------------------------
	named  zxxx
	
	Data for 1 star are compressed in 17 or 18 bytes
	  RA  = 24 bits (3 bytes)
	  SPD = 21 bits
	  mag = 11 bits
	  sx  =  8 bits
	  sy  =  8 bits
	  Ep  = 10 bits (offset specified in each chunk)
	  no  =  5 bits (values 2..17, then tablo)
	  nc  =  5 bits (values 2..33)
	  mux = 13 bits (values -2048..+2047, then tablem)
	  sx  =  9 bits
	  muy = 13 bits (values -2048..+2047, then tablem)
	  sy  =  9 bits
	(see also ucac_def.h)
	
	The header consists in:
	1) an ascii line 
	   #UCAC1-17(80chunks): zxxx [...]
	   made of a multiple of 4 bytes
   	2) 80*4 offset (seek bytes) pointing to each of the 78 chunks
   	3) A chunk header (see ucac_def.h)
	4) The data in one chunk, as 17*nrec bytes
---------------*/

#include <ucac_def.h>	/* Structure definitions */

#include <stdio.h>
#include <stdlib.h>	/* Malloc   */
#include <string.h>
#include <ctype.h>
#include <fcntl.h>	/* O_BINARY */
#include <math.h>

#ifndef O_BINARY	/* Required for MS-DOS	*/
#define O_BINARY 0
#endif
#include <stdio.h>

static char verbop = 0;	/* Verbose Option	*/
static short apm2[2000] ;
static short amag[1000] ;
static int   apm4[1000] ;

static char intro[] = "\
A binary file is made of the following:\n\
   1) an ascii line made of a multiple of 4 bytes, ending by \\n\n\
      #UCAC1-17#12-1234(80chunks)#1998#750: Z...   or\n\
      #UCAC1-18#12-1234(80chunks)#1998#750: Z...   (18-byte records)\n\
   2) 80*4 offset (seek bytes) pointing to each of the 78 chunks\n\
   3) A chunk header (see ucac_def.h)\n\
   4) The data in one chunk, as 17*nrec or 18*nrec bytes\n\
" ;
static int reclen = 17;	/* Constant over one file */

static char usage[] = "\
Usage: ucamake [-v] [-i input_dir] [-o output_dir] input_zone(s)\n\
";

static char help[] = "\
      -v: verbose option\n\
  -i dir: defines the name of the  input data directory [.]\n\
  -o dir: defines the name of the output data directory [.]\n\
   input: a \"zXXX\" original file, in input_dir\n\
";

/*==================================================================
		Internal Utilities
 *==================================================================*/

static char *filename(char *dir, char *name)
/*++++++++++++++++
.PURPOSE  Concatenates for a full file name
.RETURNS  The (statix), file name
-----------------*/
{
  static char *buf; 
  static int lbuf ;
  int len ;
    if (!dir) return(name) ;
    if (*name == '/') return(name) ;
    len = strlen(dir) + strlen(name) + 2 ;
    if (len > lbuf) {
        if (buf) free(buf) ;
	lbuf = (len|63)+1 ;
	buf = malloc(lbuf);
    }
    strcpy(buf, dir);
    strcat(buf, "/") ;
    strcat(buf, name) ;
    return(buf) ;
}

static int code_pm(int pm, short *pm2, int *pm4)
/*++++++++++++++++
.PURPOSE  Reserve a cell for a proper motion outside range 
.RETURNS  [0,4095]=standard ; 4096 .. 6143 : short value
                              6144 .. 8191 : int4 value
-----------------*/
{
  short *s ; int *p; int m ;
    m = pm + 2048 ; 
    if (m&(~0xfff)) {				/* Need extra space */
        if ((m < -32768) || (m > 32767)) {	/* ... as long int! */
	    for (p=pm4; *p && (*p != m); p++) ;
	    if (! *p) {
	        *p = m ;
		if (verbop) fprintf(stderr,
		    "....Allocated pm4cell#%d for pm=%d\n", p-pm4, pm) ;
	    }
	    m = p-pm4; 
	    m |= 0x1800; 
	}
	else {					/* short is enough! */
	    for (s=pm2; *s && (*s != m); s++) ;
	    if (! *s) { 
	        *s = m ;
		if (verbop) fprintf(stderr, 
		    "....Allocated pm2cell#%d for pm=%d\n", s-pm2, pm) ;
	    }
	    m = s-pm2; 
	    m |= 0x1000; 
	}
    }
    return(m) ;
}

static int code_mag(int mag, short *amag)
/*++++++++++++++++
.PURPOSE  Reserve a cell for a magnitude outside range 
.RETURNS  [0,949]=standard ; 950 .. 1023 = outside boundaries
-----------------*/
{
  short *s ; int m ;
    if ((mag>=0) && (mag<950)) return(mag); 	/* Standard Range   */
    for (s=amag; *s && (*s != mag); s++) ;
    if (! *s) { 
	*s = mag ;
	if (verbop) fprintf(stderr, 
	    "....Allocated magcell#%d for mag=%d\n", s-amag, *s+750) ;
    }
    m = s-amag; 
    return(m+950) ;
}

static int code_no(int no, char *pm)
/*++++++++++++++++
.PURPOSE  Reserve a cell for a number of observations
.RETURNS  [0..15] for 2..17, other values
-----------------*/
{
  char *p; int m ;
    m = no-2 ;
    if (m&(~0xf)) {				/* Need extra space */
	for (p=pm; *p && (*p != m); p++) ;
	if (! *p) {
	    *p = m ;
	    if (verbop) fprintf(stderr,
	        "....Allocated no_cell#%d for no=%d\n", p-pm, no) ;
	}
	m = p-pm; 
	m |= 0x10; 
    }
    return(m) ;
}

/*==================================================================
		Convert the Input Record(s)
 *==================================================================*/

static int trec(CHUNK *h, int *val /*[12]*/, unsigned char *out)
/*++++++++++++++++
.PURPOSE  Convert the input (RA+DE+MAG) into 7- or 8-byte record
.RETURNS  Number of errors
-----------------*/
{
  int m, n, c, ra, sd ;
  int errors = 0 ;

    /* Positions */
    ra = val[0] - h->ra0 ;
    if (reclen == 18) {
        *out = ra>>24;
	out++ ;
	ra &= MASK(24) ;
    }
    if (ra&(~MASK(24))) errors++, fprintf(stderr, 
        "****ra '%d' (%X) too large\n", val[0], ra) ;
    out[0] = ra>>16; out[1] = ra>>8; out[2] = ra ;

    sd = val[1] - h->sd0 ;
    if (sd&(~MASK(21))) errors++, fprintf(stderr,
        "****sd '%d' (%X) too large\n", val[1], sd) ;
    out[3] = sd>>13; out[4] = sd>>5; 

    /* Epoch -- we assume ep0 is loaded */
    m = val[6] - h->ep0 ;	/* Normalize to yr 1998 */
    if (m&(~MASK(11))) errors++, fprintf(stderr,
        "****Ep '%d' (%X) outside bounds, ep0=%d\n", val[6], m, h->ep0) ;
    out[5] = (sd<<3) | (m>>8) ;
    out[6] = m ;

    /* Sigmas */
    out[7] = val[3]; 
    out[8] = val[4]; 

    /* Magnitude */
    m = code_mag(val[2] - h->omag, h->amag) ;
    if (m&(~MASK(10))) errors++, fprintf(stderr,
        "****Mag#%d (%X) outside bounds, mag=%d\n", m, m, val[2]) ;
    out[9] = m>>2;

    /* Number of observations, 5 bits only */
    n = code_no(val[5], h->overno) ;

    /* Number of catalogues */
    c = val[11]-2 ;
    if (c&(~MASK(5))) errors++, fprintf(stderr,
        "****nc '%d' (%X) outside bounds\n", val[11], c) ;
    out[10] = (m<<6) | (n<<1) | (c>>4);

    /* Proper Motions and Errors */
    m = code_pm(val[7], h->amu2, h->amu4) ;
    if (m&(~MASK(13))) errors++, fprintf(stderr,
        "****pmx '%d' (%X) outside bounds\n", val[7], m) ;
    out[11] = (c<<4) | (m>>9) ;
    out[12] = m>>1 ;
    out[13] = (m<<7) | (val[9]>>2) ;

    m = code_pm(val[8], apm2, apm4) ;
    if (m&(~MASK(13))) errors++, fprintf(stderr,
        "****pmy '%d' (%X) outside bounds\n", val[9], m) ;
    out[14] = (val[9]<<6) | (m>>7) ;
    out[15] = (m<<1) | (val[10]>>8);
    out[16] = val[10] ;

    return(errors) ;
}

/*==================================================================
		Operate on a file
 *==================================================================*/

static int write_chunk(FILE *file, CHUNK *header, 
                      unsigned char *chunk, int size)
/*++++++++++++++++
.PURPOSE  Write to output file a Chunk -- after verifications, of course !
.RETURNS  Number of bytes written (chunk header + chunk + extra bytes)
.REMARKS  Files opened.
-----------------*/
{
  static struct {
     int i4[2]; short headlen; unsigned char i1[22]; 
  } head ;
  int bytes, extra_bytes ;
  int *a4; short *a2 ;

    /* Compute number of extra proper motions */
    for (a2=header->amag; *a2; a2++) ; header->nmag = a2 - header->amag ;
    for (a2=header->amu2; *a2; a2++) ; header->npm2 = a2 - header->amu2 ;
    for (a4=header->amu4; *a4; a4++) ; header->npm4 = a4 - header->amu4 ;

    /* Complete Header */
    header->headlen = sizeof(head) + header->npm4*4 
                    + (header->npm2 + header->nmag)*2 ;
    header->len = size ;
    bytes = header->headlen + header->len ;	/* Bytes to write out	*/
    header->pos = 0 ;
    header->chunk = chunk ;

    if (verbop) {
        fprintf(stderr, "....z%03d,chunk#%02d: %5d stars;", 
            header->zone, header->chunkno, size/reclen) ;
	fprintf(stderr, " Add.pm4,pm2,mag=%d,%d,%d\n", 
	    header->npm4, header->npm2, header->nmag) ;
    }
    
    /* Copy into head, and write -- must round bytes to multiple of 4 	*/
    if (extra_bytes=bytes&3) extra_bytes = 4-extra_bytes;
    head.i4[0] = bytes + extra_bytes ;
    head.i4[1] = header->id0 ;
    head.headlen = header->headlen ;
    head.i1[0] = header->chunkno ;
    head.i1[1] = header->npm4 ;
    head.i1[2] = header->npm2 ;
    head.i1[3] = header->nmag ;
    memcpy(&head.i1[4], header->overno, sizeof(header->overno)) ;
    fwrite(&head, sizeof(head),1, file) ;

    /* Write the extra Proper Motions and Magnitudes */
    if (header->npm4) fwrite(header->amu4, 4, header->npm4, file) ;
    if (header->npm2) fwrite(header->amu2, 2, header->npm2, file) ;
    if (header->nmag) fwrite(header->amag, 2, header->nmag, file) ;

    /* Write the Records */
    fwrite(header->chunk, 1, size, file) ;
    if (extra_bytes) fwrite(&extra_bytes, 1, extra_bytes, file) ;
    
    /* Verify ... */
    if (ferror(file)) {
        fprintf(stderr, "****Problem writing zone/chunk %03d/%02d",
	    header->zone, header->chunkno) ;
	perror("") ;
	exit(1) ;
    }

    return( bytes + extra_bytes ) ;
}

static int exec17(FILE *infile, FILE *ofile, int zone)
/*++++++++++++++++
.PURPOSE  Convert one file from ASCII to BINARY
.RETURNS  0 / -1
.REMARKS  Files opened.
-----------------*/
{
  static char line1[80] ;	/* The header line		*/
  static long ochunks[80] ;	/* The 80 offsets at file top 	*/
  static CHUNK header ;		/* Header for each chunk	*/
  /* A complete chunk is in memory before output - reallocated	*/
  static unsigned char *chunk, *pc, *ec, *bc ;
  int ira, i, len, dep, val[12], rashift ;
  long bytes ;
  short s;
  char inbuf[BUFSIZ] ;		/* One line from input file	*/
  char *p, *a ;

    /* Initialize */
    if (!chunk) {
        chunk = (unsigned char *)malloc(len = 17*18000) ;
	ec = chunk + len ;
    }
    header.zone = zone ;
    header.reclen = reclen ;
    header.id0  = header.id1 = UCAC1[zone-1]-1 ;
    header.amag = amag; 
    header.amu2 = apm2; 
    header.amu4 = apm4; 
    header.ra0  = header.ra1 = 0 ;
    header.sd0  = (zone-1)*Dstep ;
    header.sd1  = zone*Dstep - 1;
    rashift = reclen == 18 ? 1 : 0;

    /* Write an Empty Header First 	*/
    memset(line1, '.', sizeof(line1)) ;
    line1[sizeof(line1)-1] = '\n' ;
    line1[sizeof(line1)-2] = '\r' ;
    memset(ochunks, 0, sizeof(ochunks)) ;
    fwrite(line1, sizeof(line1),1, ofile) ;
    fwrite(ochunks, sizeof(ochunks),1, ofile) ;
    bytes = sizeof(line1) + sizeof(ochunks) ;

    pc = chunk ;
    ira = -1 ;
    while(fgets(inbuf, sizeof(inbuf), infile)) {
        /* Read the 12 integer numbers */
        for (i=0, p=inbuf; i<12; i++) {
            while (isspace(*p)) p++ ;
	    val[i] = atoi(p) ;
	    while (isgraph(*p)) p++ ;
        }
	val[6] += 1997000 ;

	/* A new chunk starts when leftmost byte of RA changes */
	if (rashift) i=0;
	else i = val[0] >> 24 ;
	if (i != ira) {
	    if (pc != chunk) 
	        bytes += write_chunk(ofile, &header, chunk, pc-chunk) ;
	    while (++ira < i) ochunks[ira] = bytes ;
	    ochunks[ira] = bytes ;
	    header.pos = 0 ;
	    header.chunkno = i ;
	    header.id0 = header.id1+1 ;
	    header.ra0 = val[0]&(~MASK(24)) ;	
	    header.ep0 = 1998000 ;	/* Fixed here	*/
	    header.omag = 750 ;		/* Origin mags	*/
	    header.npm2 = header.npm4 = 0 ;
	    memset(header.overno, 0, sizeof(header.overno)) ;
	    memset(apm2, 0, sizeof(apm2)) ;
	    memset(amag, 0, sizeof(amag)) ;
	    memset(apm4, 0, sizeof(apm4)) ;
	    pc = chunk ;
	}

	header.id1++ ;			/* A new star	*/
	header.ra1 = val[0] ;		/* Actual limit	*/

	if (pc >= ec) {			/* RALLOC !!! */
	    i = pc-chunk ;
	    if (verbop) fprintf(stderr, 
	        "++++Realloc Nrecs from %d to ", i/reclen) ;
	    chunk = realloc(chunk, i + 17*3600) ;
	    pc = chunk + i ;
	    i += 17*3600 ;
	    ec = chunk + i ;
	    if (verbop) fprintf(stderr, "%d\n", i/reclen) ;
	}

	/* Add this new record */
	trec(&header, val, pc) ;
	pc += reclen ;
    }

    /* Write the last chunk	*/
    if (pc != chunk) 
        bytes += write_chunk(ofile, &header, chunk, pc-chunk) ;
    while (++ira < ITEMS(ochunks)) 
	ochunks[ira] = bytes ;

    /* Update the File Header: write the byte pattern	*/
    strcpy(line1, header_text) ;
    if (reclen == 18) *strchr(line1, '7') = '8' ;
    p = strchr(line1+1, '#') ;
    s = 0x0102; a = (char *)&s;		/* Short Pattern */
    *++p = '0'+a[0]; *++p = '0'+a[1]; ++p;
    i=0x01020304; a = (char *)&i; 	/* Long  Pattern */
    *++p = '0'+a[0]; *++p = '0'+a[1]; *++p = '0'+a[2]; *++p = '0'+a[3]; 
    p = line1 + strlen(line1);
    sprintf(p, "%03d", zone); p += strlen(p) ;
    sprintf(p, " %08d-%08d", UCAC1[zone-1], header.id1) ; p += strlen(p) ;
    memset(p, ' ', (line1+sizeof(line1))-p) ;
    line1[sizeof(line1)-1] = '\n' ;
    line1[sizeof(line1)-2] = '\r' ;

    fseek(ofile, 0, 0) ;
    fwrite(line1, sizeof(line1),1, ofile) ;
    fwrite(ochunks, sizeof(ochunks),1, ofile) ;

    /* Write out some statistics */
    fprintf(stderr, "====") ;
    fwrite(line1, sizeof(line1),1, stderr) ;

    return(0) ;
}

/*==================================================================
		Main Program
 *==================================================================*/
main (argc, argv) int argc; char **argv;
{
  static char *idir, *odir ;
  static char oname[20];
  FILE *filin, *filout ;
  int nfiles, zone ;
  char *p, *s ;

    /* Check the Arguments */
    nfiles = 0 ;
    while (-- argc > 0) {
	p = *++argv;
	if (*p == '-') switch (*++p) {
	  case 'h':
	    printf("%s%s", usage, help) ;
	    exit(0);
	  case 'i':
	    idir = *++argv; -- argc;
	    continue;
	  case 'o':
	    p = *++argv; -- argc;
	    odir = p ;
	    continue ;
	  case 'v':		/* Verbose		*/
	    verbop = 1; 
	    continue;
	  default:
	  bad_option:
	    fprintf(stderr, "\n****Bad option: -%s\n", p);
	    exit(1);
	}

	/* Execute on one file */
	filin =  fopen(s=filename(idir, p), "r") ;
	if (!filin) {
	    fprintf(stderr, "****Input ") ; perror(s); 
	    exit(1) ;
	}
	if (verbop) fprintf(stderr, "----Convert %s ", s) ;
	if (s = strrchr(p, '/')) s++; 
	else s = p ;
	zone = atoi(s+1) ;
	if ((zone <= 0) || (zone > ITEMS(UCAC1))) {
	    fprintf(stderr, 
	    "**** Invalid zone '%d' from file %s:\n    (range 1 .. %d)\n",
	    zone, p, ITEMS(UCAC1)) ;
	    exit(1) ;
	}
	if (zone < 8) reclen = 18; 
	else reclen = 17 ;
	sprintf(oname, "z%03d.bin", zone) ;

	filout =  fopen(s=filename(odir, oname), "w+b") ;
	if (verbop) fprintf(stderr, "=> %s\n", s) ;
	if (!filout) {
	    fprintf(stderr, "****Output ") ; perror(s); 
	    exit(1) ;
	}
	if (exec17(filin, filout, zone) < 0) exit(1) ;
	fclose(filin) ;
	fclose(filout);
	nfiles++ ;
    }

    if (nfiles == 0) {
    	fprintf(stderr, "%s", usage) ;
    	exit(1) ;
    }
    
    exit(0) ;
}
