/* UNPACK.CPP           Copyright 1997,  Project Pluto                  */
/* All rights reserved.  This source code may be used freely for        */
/* non-commercial uses.                                                 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>
#include <time.h>
#include "crunch.h"

/* This code provides an _example_ as to how to decompress Guide's GSC  */
/* data.  It produces files in the original ASCII format used by STScI  */
/* on their 2-CD version.  It works with all Guide versions,  1.0       */
/* and later.  Do note that the index files moved from version to       */
/* version,  and that Guide checks for all of them as needed!  Also     */
/* note that,  due to an unfortunate oversight,  there is no HEADER.GSC */
/* on the 1.0 and 2.0 CDs;  you can copy one to your hard drive from a  */
/* 3.0 or higher,  and UNPACK will use it.                              */

#ifdef __WATCOMC__
#define BUFF_SIZE 200000U
#else
#define BUFF_SIZE 10000U
#endif

int gsc_star_uncrunch( GSC_STAR *star, GSC_HEADER *hdr, unsigned char *buff);

static char *zone_names[24] = { "N0000", "N0730", "N1500", "N2230",
                                "N3000", "N3730", "N4500", "N5230",
                                "N6000", "N6730", "N7500", "N8230",
                                "S0000", "S0730", "S1500", "S2230",
                                "S3000", "S3730", "S4500", "S5230",
                                "S6000", "S6730", "S7500", "S8230" };
static int zone_start[25] = {    1,  594, 1178, 1729, 2259, 2781,
                              3246, 3652, 4014, 4294, 4492, 4615,
                              4663, 5260, 5838, 6412, 6989, 7523,
                              8022, 8464, 8840, 9134, 9346, 9490, 9538 };

int gsc_unpack( const int cd_drive_letter, const int tile_no,
                           const int fix_numbers)
{
   int line_no = 0, rval, zone;
   unsigned pad_lines, i, prev_n = 0, big_oloc = 0, big_osize = 1024;
   char *buff, *tptr, filename[100], obuff[46], prev_obuff[46];
   char *big_obuff = NULL;
   long bytes_left, loc[2];
   FILE *ifile, *ofile, *sample;
   GSC_HEADER hdr;
   GSC_STAR star;

   if( cd_drive_letter != '!')
      {
               /* first,  get the offset in the file */
               /* On the 4.0 CD,  the index is gscdata2.idx... */
      sprintf( filename, "%c:\\text\\gscdata2.idx", cd_drive_letter);
      ifile = fopen( filename + 8, "rb");      /* check on local drive... */
      if( !ifile)
         ifile = fopen( filename, "rb");       /* ...then on CD */

              /* If we're using the Guide 3.0 CD,  the file to check is... */
      sprintf( filename, "%c:\\text\\gscdata.idx", cd_drive_letter);
      if( !ifile)
         ifile = fopen( filename + 8, "rb");   /* check on local drive... */
      if( !ifile)
         ifile = fopen( filename, "rb");       /* ...then on CD */

              /* On the 1.0 and 2.0 CDs,  GSCDATA.IDX is in the root dir */
      sprintf( filename, "%c:\\gscdata.idx", cd_drive_letter);
      if( !ifile)
         ifile = fopen( filename, "rb");   /* check on local drive... */

      if( ifile)
         {
         fseek( ifile, (long)(tile_no + 731) << 3, SEEK_SET);
         fread( loc, 2, sizeof( long), ifile);
         fclose( ifile);
         }
      else
         loc[0] = -1L;

                  /* determine its 7.5 degree zone */
      for( zone = 24; zone_start[zone] > tile_no; zone--);
      sprintf( filename, "%c:\\gsc\\%s.lmp", cd_drive_letter, zone_names[zone]);
      ifile = fopen( filename, "rb");
      if( !ifile)
         return( -2);
                             /* Worst-case: if no GSCDATA.IDX is found, we   */
      if( loc[0] == -1L)     /* can still get the offset and size data from  */
         {                   /* the .LMP itself: */
         fseek( ifile, 16L + (long)( tile_no-zone_start[zone]) * 22L,SEEK_SET);
         fread( loc, 2, sizeof( long), ifile);
         }
      fseek( ifile, loc[0], SEEK_SET);
      }
   else        /* just decompressing a 'plain' .GSE tile */
      {
      sprintf( filename, "%04d.gse", tile_no);
      ifile = fopen( filename, "rb");
      if( !ifile)
         return( -2);
      }

   sprintf( filename, "%04d.gsc", tile_no);
   ofile = fopen( filename, "wb");
   if( !ofile)
      return( -3);

   fread( (char *)&hdr, sizeof( GSC_HEADER), 1, ifile);
   pad_lines = hdr.total_lines - hdr.n_entries - 192;
   hdr.prev_n = 0;
   buff = (char *)malloc( BUFF_SIZE);
   if( !buff)
      return( -4);

   while( !big_obuff)
      {
      big_osize >>= 1;
      big_obuff = (char *)calloc( big_osize, 45);
      }

   sprintf( filename, "%c:\\compress\\header.gsc", cd_drive_letter);

            /* Guide 1.0 and 2.0 lack a header.gsc;  for these,  one must */
            /* check the local drive.  */
   sample = fopen( filename, "rb");
   if( !sample)
      sample = fopen( "header.gsc", "rb");

   if( !sample)
      {
      free( buff);
      free( big_obuff);
      return( -5);
      }

   rval = fread( buff, 192, 45, sample);
   fclose( sample);

   sprintf( buff + 3225, "%5d", hdr.n_entries);
   buff[3230] = ' ';
   sprintf( buff + 3622, "%05d", hdr.area_no);
   buff[3627] = '\'';
   sprintf( buff + 3648, "%05d", hdr.area_no);
   buff[3653] = ' ';
   fwrite( buff, 192, 45, ofile);

   tptr = buff;
   bytes_left = hdr.filesize - (long)sizeof( GSC_HEADER);
   if( bytes_left > (long)BUFF_SIZE)
      fread( buff, BUFF_SIZE, 1, ifile);
   else
      fread( buff, (unsigned)bytes_left, 1, ifile);
   memset( &star, 0, sizeof( GSC_STAR));
   i = 0;
#ifdef CODE_NOT_USED_ANYMORE
   if( argc > 3)        /* show header info */
      {
      printf( "Area %d:  %ld bytes compressed\n", hdr.area_no, hdr.filesize);
      printf( "%d plates in this area\n", hdr.n_plates);
      printf( "RA: %ld to %ld;  dec: %ld to %ld\n", hdr.ra_min, hdr.ra_max,
                     hdr.dec_min, hdr.dec_max);
      printf( "%u stars;  %u entries;  %u lines\n", hdr.n_stars,
                  hdr.n_entries, hdr.total_lines);
      printf( "%lu bytes uncompressed:  compression factor %4.2lf:1\n",
         (long)hdr.total_lines * 45L, (double)hdr.total_lines * 45. /
         (double)hdr.filesize);
      }
#endif
   while( bytes_left)
      {
      if( tptr > buff + BUFF_SIZE - 50)      /* getting close to buffer end */
         {
         unsigned n_valid, readsize;

         n_valid = BUFF_SIZE - (tptr - buff);
         readsize = (( (long)(tptr - buff) > bytes_left) ?
                              (unsigned)bytes_left : tptr - buff);
         printf( "%u bytes left in buffer; %lu to go\n", n_valid, bytes_left);
         memmove( buff, tptr, n_valid);
         tptr = buff;
         fread( buff + n_valid, readsize, 1, ifile);
         }

      rval = gsc_star_uncrunch( &star, &hdr, (unsigned char *)tptr);
         /*             n    ra.ra    dec.dec   perr   mag.     merr  ban c pl add*/
      sprintf( obuff, "%05u%3ld.%05ld%3ld.%05ld%3u.%1u%2u.%02u%1u.%02u%2u%u%s%c",
         star.n, star.x / 100000L, star.x % 100000L, star.y / 100000L,
         labs( star.y) % 100000L, star.posn_err / 10, star.posn_err % 10,
         star.mag / 100, star.mag % 100, star.mag_err / 100,
         star.mag_err % 100, star.mag_band, star.obj_type,
         hdr.plates + star.plate_id_no * 5, star.is_a_dup? 'T' : 'F');

      if( star.y < 0L && star.y > -100000L)
         obuff[15] = '-';
      if( hdr.area_no == 593 && star.x == 0L)
         {        /* special fix for area 593 peculiarity */
         obuff[5] = '3';
         obuff[6] = '6';
         }                    /* Due to a bug,  the 10000s place for the    */
      if( !fix_numbers)       /* GSC numbers on the original GSC 1.x CDs is */
         obuff[0] = '0';      /* always 0,  even on the (rare) occasions    */
                              /* when it should actually be 1.              */
      if( (long)rval > bytes_left)
         return( -6);
      if( !star.mag)    /* mag zeroed to indicate 'phantom' star */
         obuff[0] = '\0';
      if( star.n == prev_n)
         prev_obuff[44] = 'T';
      prev_n = star.n;
      if( i && *prev_obuff)
         {
         memcpy( big_obuff + big_oloc * 45, prev_obuff, 45);
         big_oloc++;
         if( big_oloc == big_osize)
            {
            fwrite( big_obuff, 45, big_oloc, ofile);
            big_oloc = 0;
            }
         }
      memcpy( prev_obuff, obuff, 45);
      line_no++;
      tptr += rval;
      bytes_left -= (long)rval;
      i++;
      }
   fwrite( big_obuff, 45, big_oloc, ofile);
   fwrite( obuff, 45, 1, ofile);
   free( buff);
   free( big_obuff);
   memset( obuff, ' ', 45);
   while( pad_lines--)
      fwrite( obuff, 45, 1, ofile);
   fclose( ifile);
   fclose( ofile);
   return( 0);
}

#ifdef TEST_PROGRAM

#include <stdlib.h>
#include <conio.h>

#define BUFFSIZE 8192

int make_comparison( int tile_no, int compare_drive)
{
   int zone, n_read1 = 1, n_read2 = 1, compare = 0;
   char filename[50];
   char *buff1 = (char *)malloc( 2 * BUFFSIZE);
   char *buff2 = buff1 + BUFFSIZE;
   FILE *ifile1, *ifile2;

   if( !buff1)
      return( -4);
   for( zone = 24; zone_start[zone] > tile_no; zone--);
   sprintf( filename, "%c:\\gsc\\%s\\%04d.gsc",
                        (char)compare_drive, zone_names[zone], tile_no);
   ifile1 = fopen( filename, "rb");
   if( !ifile1)
      {
      printf( "Didn't open %s\n", filename);
      return( -2);
      }
   ifile2 = fopen( filename + 13, "rb");
   if( !ifile2)
      {
      printf( "Didn't open %s\n", filename + 13);
      return( -3);
      }

   while( n_read1 && n_read2 == n_read1 && !compare)
      {
      n_read1 = fread( buff1, 1, BUFFSIZE, ifile1);
      n_read2 = fread( buff2, 1, BUFFSIZE, ifile2);
      if( n_read1 && n_read1 == n_read2)
         compare = memcmp( buff1, buff2, n_read1);
      }

   fclose( ifile1);
   fclose( ifile2);
   unlink( filename + 13);
   free( buff1);
   if( n_read1 != n_read2)
      compare = -9;
   return( compare);
}

void main( int argc, char **argv)
{
   time_t t;
   int rval, fix_numbers = 0, start, end, n_args, i, compare_drive = 0;

   if( argc >= 3)
      n_args = sscanf( argv[2], "%d,%d", &start, &end);
   if( argc < 3 || n_args < 1)
      {
      printf( "UNPACK is a utility for extracting Hubble Guide Star Catalog\n");
      printf( "tiles in their original form from the compressed GUIDE data.\n");
      printf( "UNPACK needs to know the CD drive letter and the number of the\n");
      printf( "zone you want.  For example,  to get tile 3588 with the GUIDE\n");
      printf( "CD in drive e:, type:\n\nUNPACK E 3588\n\n");
      printf( "The resulting 3588.GSC file will be created.\n");
      exit( 0);
      }

   if( argc == 4 && !strcmp( argv[3], "#"))
      fix_numbers = 1;
   if( argc == 4 && argv[3][0] == '#')
      compare_drive = argv[3][1];
   if( n_args == 1)           /* only one tile to decompress */
      end = start;
   for( i = start; i <= end; i++)
      {
      printf( "Decompressing GSC tile %d\n", i);
      rval = gsc_unpack( argv[1][0], i, fix_numbers);
      switch( rval)
         {
         case -1:
            printf( "Couldn't open index file\n");
            break;
         case -2:
            printf( "Couldn't open data file\n");
            break;
         case -3:
            printf( "Couldn't open output file\n");
            break;
         case -4:
            printf( "Not enough memory\n");
            break;
         case -5:
            printf( "Couldn't open HEADER.GSC\n");
            break;
         case -6:
            printf( "Error in reading compressed data\n");
            break;
         case 0:
            t = time( NULL);
            printf( "finished %d at %s\n", i, ctime( &t));
            break;
         default:
            printf( "Unrecognized error code %d\n", rval);
            break;
         }
      if( compare_drive)
         {
         if( rval)
            getch( );
         rval = make_comparison( i, compare_drive);
         if( rval)
            {
            printf( "Comparison: rval %d\n", rval);
            getch( );
            }
         }
      }
}
#endif
