
    /*********************************************************
     * convert a data file taken by Mark III CCD from Tom's format to FITS.
     * Assumes the following format for the input file:
     *
     *   - chunks (rows) 1646 bytes, divided up like so:
     *
     *           Datex              10 bytes
     *           Timex               8
     *           Lineset          1592         (796*2 bytes)
     *           Singlex            24
     *           Integerx           12
     *         -----------------------------
     *           sum              1646 bytes
     *
     *   - the pixel data is inside "Linex" values, like so:
     *
     *           garbage       bytes   0 -   31
     *           blank pixels         32 -   39
     *           data pixels          40 - 1575
     *           blank pixels       1576 - 1591
     *
     *
     *   - integral number of such rows in data file
     *
     * The data is converted to 16-bit signed integer FITS,
     * with a minimal header.  
     *
     *   Michael Richmond 9/11/1995
     * 
     * Modified to write numbers into the FITS header so that they
     * are right-justified to column 30.
     * 
     *   Michael Richmond 9/18/1995
     *
     * This is really a modified version of an earlier translation file.
     * I've just changed it slightly to handle Mark III data.
     *
     *   Michael Richmond 1/5/1996
     *
     */

#include <stdio.h>
#include <string.h>      /* needed for "strncmp" function */

#undef  LINUX            /* define this if you are using LINUX */

#ifndef LINUX
#define SEEK_SET     0   /* "fseek" to move relative to beginning of file */
#define SEEK_CUR     1   /* "fseek" to move relative to current position */
#define SEEK_END     2   /* "fseek" to move relative to end of file */
#endif


#define CARDLEN     80   /* # of chars in each FITS header record */
#define NCARD       36   /* # of records in a minimal header */
#define FITSMULT  2880   /* FITS file must be multiple of this in bytes */

    /* these pertain to Tom's input data files */
#define CHUNK_LEN 1646         /* this many bytes in one row of data */
#define FIRSTBYTE   40         /* first byte of read data in row */
#define CCDCOLS    768         /* this many columns are real CCD data */
                               /*   (each is 2 bytes long) */
#if 0
#define BIAS      3300         /* add this fixed bias to Tom's data values */
#else
#define BIAS         0         /* add this fixed bias to Tom's data values */
#endif

    /* the following must be a 16-bit signed quantity */
typedef short int int16;    
    /* and this should be a 16-bit unsigned quantity */
typedef unsigned short int uint16;

    /*
     * create a CARDLEN-byte line with the given string at its 
     * beginning, and padded with blanks to the end (which is 
     * NOT null-terminated).
     */

static void
make_line
    (
    char *buf,            /* must have enough space to hold CARDLEN chars */
    char *string          /* copy this (up to NULL) into 'buf' */
    )
{
    int i;

    for (i = 0; i < CARDLEN; i++) {
        buf[i] = ' ';
    }
    for (i = 0; i < strlen(string); i++) {
        buf[i] = string[i];
    }
}

    /*
     * print the given string to the stderr, and terminate execution
     * with status code -1.
     */

static void
die
    (
    char *string           /* print this on stderr */
    )
{
    fprintf(stderr, "%s\n", string);
    exit(-1);
}



    /* 
     * create a header for a FITS file with nrow-by-ncol entries,
     * using 16-bit signed integer data.
     *
     * if an error occurs, print an error message and quit.
     */

static int
create_header
    (
    FILE *fp,              /* write into this stream */
    int nrow,              /* image has this many rows */
    int ncol               /* image has this many columns */
    )
{
    int i;
    char buf[CARDLEN + 1], string[CARDLEN + 1];

    /* 
     * put a minimal header into place, including enough 
     * blank cards after the 'END' to fill the header's NCARD lines
     */
    sprintf(string, "SIMPLE  = %20s", "T");
    make_line(buf, string);
    if (fwrite(buf, 1, CARDLEN, fp) != CARDLEN) {
        die("create_header: fwrite fails on SIMPLE");
    }
    sprintf(string, "BITPIX  = %20d", 16);
    make_line(buf, string);
    if (fwrite(buf, 1, CARDLEN, fp) != CARDLEN) {
        die("create_header: fwrite fails on BITPIX");
    }
    sprintf(string, "NAXIS   = %20d", 2);
    make_line(buf, string);
    if (fwrite(buf, 1, CARDLEN, fp) != CARDLEN) {
        die("create_header: fwrite fails on NAXIS");
    }
    sprintf(string, "NAXIS1  = %20d", ncol);
    make_line(buf, string);
    if (fwrite(buf, 1, CARDLEN, fp) != CARDLEN) {
        die("create_header: fwrite fails on NAXIS1");
    }
    sprintf(string, "NAXIS2  = %20d", nrow);
    make_line(buf, string);
    if (fwrite(buf, 1, CARDLEN, fp) != CARDLEN) {
        die("create_header: fwrite fails on NAXIS2");
    }
    make_line(buf, "END");
    if (fwrite(buf, 1, CARDLEN, fp) != CARDLEN) {
        die("create_header: fwrite fails on END\n");
    }

    /* we've written 6 lines so far; let's write all the rest */
    make_line(buf, " ");
    for (i = 6; i < NCARD; i++) {
        if (fwrite(buf, 1, CARDLEN, fp) != CARDLEN) {
            die("create_header: fwrite fails on blank line");
        }
    }

    return(0);
}

    /*
     * add the given string as a COMMENT line in the header,
     * placing it at the position of the current "END" line,
     * and creating a new "END" line after it.  If we are
     * at the end of the first header block, don't do it,
     * and return -1.  Otherwise, return 0.
     */

static int
add_comment
    (
    FILE *fp,                /* FITS file whose header we edit */
    char *comment            /* the contents of the comment */
    )
{
    char buf[CARDLEN + 1], newline[CARDLEN + 1];
    int i;
    long oldpos, cardlen;
    
    /* save the current position of the FILE pointer */
    oldpos = ftell(fp);

    /* now go back to start of file and look for an END line */
    rewind(fp);
    cardlen = -CARDLEN;
    for (i = 0; i < NCARD - 1; i++) {
        if (fread(buf, 1, CARDLEN, fp) != CARDLEN) {
            die("add_comment: can't read from header");
        }
        if (strncmp(buf, "END ", 4) != 0) {
            continue;
        }

        /* okay, let's add that new COMMENT line */
        sprintf(newline, "COMMENT  %s", comment);
        make_line(buf, newline);
        fseek(fp, cardlen, SEEK_CUR);
        if (fwrite(buf, 1, CARDLEN, fp) != CARDLEN) {
            die("add_comment: fwrite fails on COMMENT");
        }
        /* and now add a new END line */
        make_line(buf, "END");
        if (fwrite(buf, 1, CARDLEN, fp) != CARDLEN) {
            die("add_comment: fwrite fails on END");
        }
        break;
    }

    /* put file pointer back to its position before we entered add_comment */
    fseek(fp, oldpos, SEEK_SET);

    if (i == NCARD - 1) {
        /* we ran out of space in the header; return -1 */
        return(-1);
    }
        
    return(0);
}

        
    
    
    /*
     * copy a sequence of 2-byte data values from the given buffer
     * to the given int16 array.   We convert them from the form
     * 
     *       low-byte   high-byte
     *
     * to signed integer, and add a constant BIAS term to bring all
     * values above 0.
     */

static void
copy_data
    (
    char *chunk,             /* data to copy is in this buffer */
    int start_byte,          /* start at this position in buffer */
    int nvalues,             /* copy this many 2-byte values */
    int16 *dat_array         /* place the values into this array */
    )
{
    unsigned char *ptr;
    int i, byte1, byte2;
    long temp;

    ptr = (unsigned char *) (chunk + start_byte);
    for (i = 0; i < nvalues; i++) {
        byte1 = *ptr++;  
        byte2 = *ptr++;
        temp = BIAS + byte1 + byte2*256;
        dat_array[i] = temp;
    }
}

    /*
     * pad the given FILE with null characters so that its
     * length is an integer multiple of FITSMULT bytes.
     */

static void
pad_file
    (
    FILE *fp                /* image file, all data + header already inside */
    )
{
    long offset, actual_length, desired_length;

    offset = 0;
    fseek(fp, offset, SEEK_END);
    actual_length = ftell(fp);
   
    desired_length = ((actual_length/FITSMULT) + 1)*FITSMULT;
    while (actual_length < desired_length) {
        fputc('\0', fp);
        actual_length++;
    }
}



    /*
     * read a row of data (which should consist of CHUNK_LEN bytes) from
     * Tom's file.  Place the data into the passed array.
     *
     * If reading reveals that our LAST read reached the end
     * of the file, then we're done, so return 1.
     * If reading yields > 0 but < CHUNK_LEN, something went
     * wrong, so return -1.
     * If reading yields exactly CHUNK_LEN, return 0.
     */

static int
read_chunk
    (
    FILE *fp,             /* read from this file stream */
    int16 *output_data    /* place data into this array */
    )
{
    int i, nbytes, start_byte;
    char chunk[CHUNK_LEN+1];

    if ((nbytes = fread(chunk, 1, CHUNK_LEN, fp)) != CHUNK_LEN) {
        if (nbytes == 0) {
            return(1);
        }
        else {
            return(-1);
        }
    }

    /* copy the data into the passed array */
    start_byte = FIRSTBYTE;
    copy_data(chunk, start_byte, CCDCOLS, output_data);

    /* all done */
    return(0);
}
    

    /*
     * write data (assumed to be CCDCOLS 2-byte integers) to the
     * given stream.
     */

static void
write_data
    (
    FILE *fp,               /* write to this stream */
    int16 *data             /* write this data in binary format */
    )
{

    if (fwrite(data, 2, CCDCOLS, fp) != CCDCOLS) {
        die("write_data fails");
    }
}
    

    /*
     * assuming that the given file has an integral number of CHUNK_LEN
     * return the number of such units.  If the file is not
     * an integral number of such units, print error message and quit.
     */

static int
find_rows
    (
    FILE *fp             /* search in this file */
    )
{
    char err_msg[100];
    int nrow;
    long offset, nbytes;

    offset = 0;
    fseek(fp, offset, SEEK_END);
    nbytes = ftell(fp);
    if (nbytes % CHUNK_LEN != 0) {
        sprintf(err_msg, "input file is not an integral number of %d bytes", 
                CHUNK_LEN);
        die(err_msg);
    }
    rewind(fp);

    nrow = nbytes/CHUNK_LEN;
    return(nrow);
}



    
    
    /*
     * the main program.  Usage:
     *
     *      markiii2fits  datfile fitsfile 
     *
     * where the args are
     *
     *    datfile        name of file with data in Tom's format
     *    fitsfile       name of output FITS file
     */
    
int 
main
    (
    int argc,
    char *argv[] 
    )
{
    char comment[CARDLEN+1];      /* holds a comment we add to FITS header */
    char err_msg[100]; 
    int i, nrow;
    int16 fits_data[CCDCOLS];    /* data values from short-exposure */
    FILE *tom_fp;                /* input file stream */
    FILE *fits_fp;               /* output FITS file */

    if (argc != 3) {
        fprintf(stderr, "usage: markiii2fits datfile fitsfile \n");
        exit(-1);
    }

    if ((tom_fp = fopen(argv[1], "r")) == NULL) {
        fprintf(stderr, "can't open file %s\n", argv[1]);
        exit(-1);
    }

    if ((fits_fp = fopen(argv[2], "w+")) == NULL) {
        fprintf(stderr, "can't create file %s\n", argv[2]);
        exit(-1);
    }
    
    nrow = find_rows(tom_fp);
    
    create_header(fits_fp, nrow, CCDCOLS);

    for (i = 0; i < nrow; i++) {
        if (read_chunk(tom_fp, fits_data) != 0) {
            fprintf(stderr, "error reading data at row %d", i);
            die(err_msg);
        }
        write_data(fits_fp, fits_data);

    }

    /* finally, pad files to be integral multiple of FITSMULT bytes */
    pad_file(fits_fp);

    fclose(tom_fp);
    fclose(fits_fp);

    exit(0);
}
