
/***************************************************************************
       binfiletester.c - Check star catalog (binary) files for errors
                             -------------------
    begin                : Wed May 28 2008
    copyright            : (C) 2008 by Akarsh Simha
    email                : akarshsimha@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <string.h>
#include "byteorder.h"

#define HTM_LEVEL 3
#define INDEX_ENTRY_SIZE 12

#include "binfile.h"

/*
 * struct to store star data, to be written in this format, into the binary file.
 */

typedef struct starData {
    int32_t RA;
    int32_t Dec;
    int32_t dRA;
    int32_t dDec;
    int32_t parallax;
    int32_t HD;
    int16_t mag;
    int16_t bv_index;
    char spec_type[2];
    char flags;
    char unused;
} starData;


dataElement de[100];
u_int16_t nfields;
long index_offset, data_offset;
char byteswap;
u_int32_t ntrixels;

/*
 * Does byteswapping for starData structures
 */
void bswap_stardata( starData *stardata ) {
    stardata->RA = bswap_32( stardata->RA );
    stardata->Dec = bswap_32( stardata->Dec );
    stardata->dRA = bswap_32( stardata->dRA );
    stardata->dDec = bswap_32( stardata->dDec );
    stardata->parallax = bswap_32( stardata->parallax );
    stardata->HD = bswap_32( stardata->HD );
    stardata->mag = bswap_16( stardata->mag );
    stardata->bv_index = bswap_16( stardata->bv_index );
}

int verifyIndexValidity(FILE *f) {

    int i;
    u_int32_t trixel;
    u_int32_t offset;
    u_int32_t prev_offset;
    u_int32_t nrecs;
    u_int32_t prev_nrecs;
    unsigned int nerr;

    fprintf(stdout, "Performing Index Table Validity Check...\n");
    fprintf(stdout, "Assuming that index starts at %X\n", ftell(f));
    index_offset = ftell(f);

    prev_offset = 0;
    prev_nrecs = 0;
    nerr = 0;

    for(i = 0; i < ntrixels; ++i) {
        if(!fread(&trixel, 4, 1, f)) {
            fprintf(stderr, "Table truncated before expected! Read i = %d records so far\n", i);
            +nerr;
            break;
        }
        if( byteswap ) trixel = bswap_32( trixel );
        if(trixel >= ntrixels) {
            fprintf(stderr, "Trixel number %u is greater than the expected number of trixels %u\n", trixel, ntrixels);
            ++nerr;
        }
        if(trixel != i) {

	  fprintf(stderr, "Found trixel = %d, while I expected number = %d\n", 
		  trixel, i);
	  ++nerr;
        }
        fread(&offset, 4, 1, f);
        if( byteswap ) offset = bswap_32( offset );
        fread(&nrecs, 4, 1, f);
        if( byteswap ) nrecs = bswap_32( nrecs );
        if( prev_offset != 0 && prev_nrecs != (-prev_offset + offset)/sizeof(starData) ) { 
            fprintf( stderr, "Expected %u  = (%X - %x) / %d records, but found %u, in trixel %d\n", 
                     (offset - prev_offset) / sizeof( starData ), offset, prev_offset, sizeof( starData ), nrecs, trixel);
            ++nerr;
        }
        prev_offset = offset;
        prev_nrecs = nrecs;
    }

    data_offset = ftell(f);

    if(nerr) { fprintf(stderr, "ERROR ;-): The index seems to have %u errors\n", nerr); }
    else { fprintf(stdout, "Index verified. PASSED.\n"); }

}

/**
 *This method ensures that the data part of the file is sane. It ensures that there are no jumps in magnitude etc.
 */
int verifyStarData( FILE *f ) {
    int16_t faintMag;
    int8_t HTM_Level;
    u_int16_t MSpT;
    int16_t realFaintMag = -500;
    u_int16_t realMSpT;
    u_int32_t nstars;
    u_int32_t offset;

    int trixel, i;
    int nerr_trixel;
    int nerr;

    starData data;
    int16_t mag;

    fprintf( stdout, "Assuming that the data starts at %ld\n", ftell( f ) );
    fread( &faintMag, 2, 1, f );
    fprintf( stdout, "Faint Magnitude Limit: %f\n", faintMag / 100.0 );
    fread( &HTM_Level, 1, 1, f );
    fprintf( stdout, "HTMesh Level: %d\n", HTM_Level );
    if( HTM_Level != HTM_LEVEL ) {
        fprintf( stderr, "ERROR: HTMesh Level in file (%d) and HTM_LEVEL in program (%d) differ. Please set the define directive for HTM_LEVEL correctly and rebuild\n.", HTM_Level, HTM_LEVEL );
        return 0;
    }
    fread( &MSpT, 2, 1, f );

    mag = -500;
    nerr = 0;

    // Scan the file for magnitude jumps, etc.
    realMSpT = 0;
    for( trixel = 0; trixel < ntrixels; ++trixel ) {
        mag = -500;
        nerr_trixel = 0;
        fprintf( stdout, "Checking trixel #%d: ", trixel );
        fseek( f, index_offset + trixel * INDEX_ENTRY_SIZE + 4 , SEEK_SET );
        fread( &offset, 4, 1, f );
        fread( &nstars, 4, 1, f );
        if( nstars > realMSpT )
            realMSpT = nstars;
        fseek( f, offset, SEEK_SET );
        for( i = 0; i < nstars; ++i ) {
            fread( &data, sizeof( starData ), 1, f );
	    if( byteswap ) bswap_stardata( &data );
            if( mag != -500 && ( ( data.mag - mag ) > 20 && mag < 1250 ) || data.mag < mag ) { // TODO: Make sensible magnitude limit (1250) user specifiable
                // TODO: Enable byteswapping
                fprintf( stderr, "\n\tEncountered jump of %f at star #%d in trixel %d from %f to %f.", ( data.mag - mag ) / 100.0, i, trixel, mag / 100.0, data.mag / 100.0 );
                ++nerr_trixel;
            }
            mag = data.mag;
            if( mag > realFaintMag ) {
                realFaintMag = mag;
            }
	    if( mag > 1500 )
	      fprintf( stderr, "Magnitude > 15.00 ( = %f ) in trixel %d\n", mag / 100.0, trixel );
        }
        if( nerr_trixel > 0 )
            fprintf( stderr, "\n * Encountered %d magnitude jumps in trixel %d\n", nerr_trixel, trixel );
        else
            fprintf( stdout, "Successful\n" );
        nerr += nerr_trixel;
    }
    if( MSpT != realMSpT ) {
        fprintf( stderr, "ERROR: MSpT according to file = %d, but turned out to be %d\n", MSpT, realMSpT );
        nerr++;
    }
    if( realFaintMag != faintMag ) {
        fprintf( stderr, "ERROR: Faint Magnitude according to file = %f, but turned out to be %f\n", faintMag / 100.0, realFaintMag / 100.0 );
        nerr++;
    }
    if( nerr > 0 ) {
        fprintf( stderr, "ERROR: Exiting with %d errors\n", nerr );
        return 0;
    }
    fprintf( stdout, "Data validation success!\n" );
    return 1;
}

void readStarList(FILE *f, u_int32_t trixel, FILE *names) {
    long offset;
    long n;
    int i;
    u_int32_t nrecs;
    u_int32_t trix;
    char bayerName[8];
    char longName[32];
    starData data;

    printf("Reading star list for trixel %d\n", trixel);
    rewind( f );
    offset = index_offset + trixel * INDEX_ENTRY_SIZE; // CAUTION: Change if the size of each entry in the index table changes
    fseek(f, offset, SEEK_SET);
    fread(&trix, 4, 1, f);
    if( byteswap ) trix = bswap_32( trix );
    if(trix != trixel) {
        fprintf(stderr, "ERROR: Something fishy in the index. I guessed that %d would be here, but instead I find %d. Aborting.\n", trixel, trix);
        return;
    }
    fread(&offset, 4, 1, f);
    if( byteswap ) offset = bswap_32( offset );
    fread(&nrecs, 4, 1, f);
    if( byteswap ) nrecs = bswap_32( nrecs );

    if(fseek(f, offset, SEEK_SET)) {
        fprintf(stderr, "ERROR: Could not seek to position %X in the file. The file is either truncated or the indexes are bogus.\n", offset);
        return;
    }
    printf("Data for trixel %d starts at offset 0x%X and has %d records\n", trixel, offset, nrecs);

    for(i = 0; i < nrecs; ++i) {
        offset = ftell(f);
        n = (offset - data_offset)/sizeof( starData );
        fread(&data, sizeof(starData), 1, f);
        if( byteswap ) bswap_stardata( &data );
        printf("Entry #%d\n", i);
        printf("\tRA = %f\n", data.RA / 1000000.0);
        printf("\tDec = %f\n", data.Dec / 100000.0);
        printf("\tdRA/dt = %f\n", data.dRA / 10.0);
        printf("\tdDec/dt = %f\n", data.dDec / 10.0);
        printf("\tParallax = %f\n", data.parallax / 10.0);
        printf("\tHD Catalog # = %lu\n", data.HD);
        printf("\tMagnitude = %f\n", data.mag / 100.0);
        printf("\tB-V Index = %f\n", data.bv_index / 100.0);
        printf("\tSpectral Type = %c%c\n", data.spec_type[0], data.spec_type[1]);
        printf("\tHas a name? %s\n", ((data.flags & 0x01)?"Yes":"No"));
        /*
          if(data.flags & 0x01 && names) {
          fseek(names, n * (32 + 8) + 0xA0, SEEK_SET);
          fread(bayerName, 8, 1, names);
          fread(longName, 32, 1, names);
          printf("\t\tBayer Designation = %s\n", bayerName);
          printf("\t\tLong Name = %s\n", longName);
          }
        */
        printf("\tMultiple Star? %s\n", ((data.flags & 0x02)?"Yes":"No"));
        printf("\tVariable Star? %s\n", ((data.flags & 0x04)?"Yes":"No"));
    }
}

/**
 *@short  Read the KStars binary file header and display its contents
 *@param f  Binary file to read from
 *@returns  non-zero if successful, zero if not
 */

int readFileHeader(FILE *f) {
    int i;
    int16_t endian_id;
    char ASCII_text[125];
    u_int8_t version_no;

    if(f == NULL)
        return 0;

    fread(ASCII_text, 124, 1, f);
    ASCII_text[124] = '\0';
    printf("%s", ASCII_text);

    fread(&endian_id, 2, 1, f);
    if(endian_id != 0x4B53) {
        fprintf( stdout, "Byteswapping required\n" );
        byteswap = 1;
    }
    else {
        fprintf( stdout, "Byteswapping not required\n" );
        byteswap = 0;
    }

    fread(&version_no, 1, 1, f);
    fprintf( stdout, "File version number: %d\n", version_no );
    fread(&nfields, 2, 1, f);
    if( byteswap ) nfields = bswap_16( nfields );
    fprintf( stdout, "%d fields reported\n", nfields );

    for(i = 0; i < nfields; ++i) {
        fread(&(de[i]), sizeof(struct dataElement), 1, f);
        if( byteswap ) de->scale = bswap_32( de->scale );
        displayDataElementDescription(&(de[i]));
    }

    fread(&ntrixels, 4, 1, f);
    if( byteswap ) ntrixels = bswap_32( ntrixels );
    fprintf( stdout, "Number of trixels reported = %d\n", ntrixels );

    return 1;
}


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

    FILE *f, *names;
    int16_t maglim = -500;
    names = NULL;
    if(argc <= 1) {
        fprintf(stderr, "USAGE: %s filename [trixel]\n", argv[0]);
        fprintf(stderr, "Designed for use only with KStars star data files\n");
        return 1;
    }

    f = fopen(argv[1], "rb");

    if(f == NULL) {
        fprintf(stderr, "ERROR: Could not open file %s for binary read.\n", argv[1]);
        return 1;
    }

    readFileHeader(f);

    verifyIndexValidity(f);
    verifyStarData(f);

    //    fread(&maglim, 2, 1, f);
    //    fprintf(stdout, "Limiting Magnitude of Catalog File: %f\n", maglim / 100.0);

    if(argc > 2) {
        /*
          if(argc > 3)
          names = fopen(argv[3], "rb");
          else
          names = NULL;
        
          fprintf(stderr, "Names = %s\n", ((names)?"Yes":"No"));
        */
      rewind( f );
      readStarList(f, atoi( argv[2] ), names);
    }

    fclose(f);
    if(names) fclose(names);

    return 0;
}
