/*
  This file is part of the Astrometry.net suite.
  Copyright 2006, 2007 Dustin Lang, Keir Mierle and Sam Roweis.

  The Astrometry.net suite 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, version 2.

  The Astrometry.net suite is distributed in the hope that it will be
  useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with the Astrometry.net suite ; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
*/

#include <stdio.h>
#include <math.h>
#include <string.h>
#include <assert.h>

#include "quadfile.h"
#include "qfits_header.h"
#include "fitsioutils.h"
#include "starutil.h"
#include "ioutils.h"
#include "errors.h"
#include "an-endian.h"

#define CHUNK_QUADS 0

static fitsbin_chunk_t* quads_chunk(quadfile* qf) {
    return fitsbin_get_chunk(qf->fb, CHUNK_QUADS);
}

static int callback_read_header(fitsbin_t* fb, fitsbin_chunk_t* chunk) {
    qfits_header* primheader = fitsbin_get_primary_header(fb);
	quadfile* qf = chunk->userdata;

    qf->dimquads = qfits_header_getint(primheader, "DIMQUADS", 4);
    qf->numquads = qfits_header_getint(primheader, "NQUADS", -1);
    qf->numstars = qfits_header_getint(primheader, "NSTARS", -1);
    qf->index_scale_upper = qfits_header_getdouble(primheader, "SCALE_U", -1.0);
    qf->index_scale_lower = qfits_header_getdouble(primheader, "SCALE_L", -1.0);
	qf->indexid = qfits_header_getint(primheader, "INDEXID", 0);
	qf->healpix = qfits_header_getint(primheader, "HEALPIX", -1);
	qf->hpnside = qfits_header_getint(primheader, "HPNSIDE", 1);

	if ((qf->numquads == -1) || (qf->numstars == -1) ||
		(qf->index_scale_upper == -1.0) || (qf->index_scale_lower == -1.0)) {
        ERROR("Couldn't find NQUADS or NSTARS or SCALE_U or SCALE_L entries in FITS header");
		return -1;
	}
    if (fits_check_endian(primheader)) {
        ERROR("Quad file was written with the wrong endianness");
		return -1;
    }

    chunk->itemsize = qf->dimquads * sizeof(uint32_t);
    chunk->nrows = qf->numquads;
	return 0;
}

static quadfile* new_quadfile(const char* fn, anqfits_t* fits, anbool writing) {
	quadfile* qf;
	fitsbin_chunk_t chunk;
	qf = calloc(1, sizeof(quadfile));
	if (!qf) {
		SYSERROR("Couldn't malloc a quadfile struct");
		return NULL;
	}
	qf->healpix = -1;
    qf->hpnside = 1;

    if (writing)
        if (fn) {
			qf->fb = fitsbin_open_for_writing(fn);
		} else {
			qf->fb = fitsbin_open_in_memory();
		}
    else {
		if (fits)
			qf->fb = fitsbin_open_fits(fits);
		else
			qf->fb = fitsbin_open(fn);
	}
    if (!qf->fb) {
        ERROR("Failed to create fitsbin");
        return NULL;
    }

    fitsbin_chunk_init(&chunk);
    chunk.tablename = "quads";
    chunk.required = 1;
    chunk.callback_read_header = callback_read_header;
    chunk.userdata = qf;
    fitsbin_add_chunk(qf->fb, &chunk);
    fitsbin_chunk_clean(&chunk);
    
	return qf;
}

int quadfile_check(const quadfile* qf) {
	int q, i;
	if (qf->dimquads < 3 || qf->dimquads > DQMAX) {
		ERROR("Dimquads has illegal value %i", qf->dimquads);
		return -1;
	}
	for (q=0; q<qf->numquads; q++) {
		unsigned int stars[DQMAX];
		if (quadfile_get_stars(qf, q, stars)) {
			ERROR("Failed to get quad %i of %i", q, qf->numquads);
			return -1;
		}
		for (i=0; i<qf->dimquads; i++) {
			if (stars[i] >= qf->numstars) {
				ERROR("Star ID %i is out of bounds: num stars %i", stars[i], qf->numstars);
				return -1;
			}
		}
	}
	return 0;
}

int quadfile_dimquads(const quadfile* qf) {
    return qf->dimquads;
}

int quadfile_nquads(const quadfile* qf) {
    return qf->numquads;
}

qfits_header* quadfile_get_header(const quadfile* qf) {
	return fitsbin_get_primary_header(qf->fb);
}

static quadfile* my_open(const char* fn, anqfits_t* fits) {
    quadfile* qf = NULL;
    fitsbin_chunk_t* chunk;

    qf = new_quadfile(fn, fits, FALSE);
    if (!qf)
        goto bailout;
    if (fitsbin_read(qf->fb)) {
        ERROR("Failed to open quads file");
        goto bailout;
    }
    chunk = quads_chunk(qf);
	qf->quadarray = chunk->data;
    return qf;

 bailout:
    if (qf)
        quadfile_close(qf);
    return NULL;
}

char* quadfile_get_filename(const quadfile* qf) {
	return fitsbin_get_filename(qf->fb);
}

quadfile* quadfile_open_fits(anqfits_t* fits) {
	return my_open(NULL, fits);
}

quadfile* quadfile_open(const char* fn) {
	return my_open(fn, NULL);
}

int quadfile_close(quadfile* qf) {
    int rtn;
	if (!qf) return 0;
	rtn = fitsbin_close(qf->fb);
	free(qf);
    return rtn;
}

static quadfile* open_for_writing(const char* fn) {
	quadfile* qf;
	qfits_header* hdr;
	qf = new_quadfile(fn, NULL, TRUE);
	if (!qf)
		goto bailout;
    qf->dimquads = 4;
	// add default values to header
	hdr = fitsbin_get_primary_header(qf->fb);
    fits_add_endian(hdr);
	qfits_header_add(hdr, "AN_FILE", "QUAD", "This file lists, for each quad, its stars.", NULL);
	qfits_header_add(hdr, "DIMQUADS", "0", "", NULL);
	qfits_header_add(hdr, "NQUADS", "0", "", NULL);
	qfits_header_add(hdr, "NSTARS", "0", "", NULL);
	qfits_header_add(hdr, "SCALE_U", "0.0", "", NULL);
	qfits_header_add(hdr, "SCALE_L", "0.0", "", NULL);
	qfits_header_add(hdr, "INDEXID", "0", "", NULL);
	qfits_header_add(hdr, "HEALPIX", "-1", "", NULL);
 	qfits_header_add(hdr, "HPNSIDE", "1", "", NULL);
    fits_add_long_comment(hdr, "The first extension contains the quads "
                          "stored as %i 32-bit native-endian unsigned ints.", qf->dimquads);
	return qf;

 bailout:
	if (qf)
		quadfile_close(qf);
	return NULL;
}

quadfile* quadfile_open_for_writing(const char* fn) {
	if (!fn) {
		ERROR("Non-NULL filename required");
		return NULL;
	}
	return open_for_writing(fn);
}

quadfile* quadfile_open_in_memory() {
	return open_for_writing(NULL);
}

int quadfile_switch_to_reading(quadfile* qf) {
	if (quadfile_fix_header(qf)) {
        ERROR("Failed to fix quads header");
		return -1;
	}
	if (fitsbin_switch_to_reading(qf->fb)) {
        ERROR("Failed to switch to read mode");
		return -1;
	}
    if (fitsbin_read(qf->fb)) {
        ERROR("Failed to open quads file");
		return -1;
    }
	qf->quadarray = quads_chunk(qf)->data;
	return 0;
}

static void add_to_header(qfits_header* hdr, quadfile* qf) {
	fits_header_mod_int(hdr, "DIMQUADS", qf->dimquads, "Number of stars in a quad.");
	fits_header_mod_int(hdr, "NQUADS", qf->numquads, "Number of quads.");
	fits_header_mod_int(hdr, "NSTARS", qf->numstars, "Number of stars.");
	fits_header_mod_double(hdr, "SCALE_U", qf->index_scale_upper, "Upper-bound index scale (radians).");
	fits_header_mod_double(hdr, "SCALE_L", qf->index_scale_lower, "Lower-bound index scale (radians).");
	fits_header_mod_int(hdr, "INDEXID", qf->indexid, "Index unique ID.");
	fits_header_mod_int(hdr, "HEALPIX", qf->healpix, "Healpix of this index.");
	fits_header_mod_int(hdr, "HPNSIDE", qf->hpnside, "Nside of the healpixelization");
}

int quadfile_write_header(quadfile* qf) {
	fitsbin_t* fb = qf->fb;
	fitsbin_chunk_t* chunk = quads_chunk(qf);
	qfits_header* hdr;
    chunk->itemsize = qf->dimquads * sizeof(uint32_t);
	chunk->nrows = qf->numquads;

	hdr = fitsbin_get_primary_header(fb);
	add_to_header(hdr, qf);

	if (fitsbin_write_primary_header(fb) ||
		fitsbin_write_chunk_header(fb, chunk)) {
		ERROR("Failed to write quadfile header");
		return -1;
	}
	return 0;
}

int quadfile_write_header_to(quadfile* qf, FILE* fid) {
	fitsbin_t* fb = qf->fb;
	fitsbin_chunk_t* chunk = quads_chunk(qf);
	qfits_header* hdr;
    chunk->itemsize = qf->dimquads * sizeof(uint32_t);
	chunk->nrows = qf->numquads;
	hdr = fitsbin_get_primary_header(fb);
	add_to_header(hdr, qf);

	if (fitsbin_write_primary_header_to(fb, fid) ||
		fitsbin_write_chunk_header_to(fb, chunk, fid)) {
		ERROR("Failed to write quadfile header");
		return -1;
	}
	return 0;
}

int quadfile_write_quad(quadfile* qf, unsigned int* stars) {
	uint32_t* data;
	uint32_t ustars[qf->dimquads];
	int i;
	fitsbin_chunk_t* chunk = quads_chunk(qf);

	if (sizeof(uint32_t) == sizeof(uint)) {
		data = stars;
	} else {
		data = ustars;
		for (i=0; i<qf->dimquads; i++)
			ustars[i] = stars[i];
	}
    if (fitsbin_write_item(qf->fb, chunk, data)) {
		ERROR("Failed to write a quad");
		return -1;
	}
	qf->numquads++;
	return 0;
}

int quadfile_write_all_quads_to(quadfile* qf, FILE* fid) {
	fitsbin_chunk_t* chunk = quads_chunk(qf);
	if (fitsbin_write_items_to(chunk, qf->quadarray, quadfile_nquads(qf), fid)) {
		ERROR("Failed to write %i quads", quadfile_nquads(qf));
		return -1;
	}
	return 0;
}

int quadfile_fix_header(quadfile* qf) {
	qfits_header* hdr;
	fitsbin_t* fb = qf->fb;
	fitsbin_chunk_t* chunk = quads_chunk(qf);

	chunk->itemsize = qf->dimquads * sizeof(uint32_t);
	chunk->nrows = qf->numquads;

	hdr = fitsbin_get_primary_header(fb);
	add_to_header(hdr, qf);

	if (fitsbin_fix_primary_header(fb) ||
		fitsbin_fix_chunk_header(fb, chunk)) {
        ERROR("Failed to fix quad header");
		return -1;
	}
	return 0;
}

double quadfile_get_index_scale_upper_arcsec(const quadfile* qf) {
    return rad2arcsec(qf->index_scale_upper);
}

double quadfile_get_index_scale_lower_arcsec(const quadfile* qf) {
	return rad2arcsec(qf->index_scale_lower);
}

int quadfile_get_stars(const quadfile* qf, unsigned int quadid, unsigned int* stars) {
    int i;
	if (quadid >= qf->numquads) {
		ERROR("Requested quadid %i, but number of quads is %i",	quadid, qf->numquads);
        assert(quadid < qf->numquads);
		return -1;
	}

    for (i=0; i<qf->dimquads; i++) {
        stars[i] = qf->quadarray[quadid * qf->dimquads + i];
    }
    return 0;
}



