/*
  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 <stdarg.h>
#include <string.h>
#include <assert.h>
#include <errors.h>

#include "qfits_header.h"
#include "xylist.h"
#include "fitstable.h"
#include "fitsioutils.h"
#include "an-bool.h"
#include "keywords.h"

static anbool is_writing(xylist_t* ls) {
    return (ls->table && ls->table->fid) ? TRUE : FALSE;
}
static anbool is_reading(xylist_t* ls) {
	return !is_writing(ls);
}

int xylist_get_imagew(xylist_t* ls) {
	int W;
	qfits_header* hdr = xylist_get_header(ls);
	W = qfits_header_getint(hdr, "IMAGEW", 0);
	if (!W) {
		// some axy's have IMAGEW/H only in the primary hdr...
		hdr = xylist_get_primary_header(ls);
		W = qfits_header_getint(hdr, "IMAGEW", 0);
	}
	return W;
}

int xylist_get_imageh(xylist_t* ls) {
	qfits_header* hdr = xylist_get_header(ls);
	int H;
	H = qfits_header_getint(hdr, "IMAGEH", 0);
	if (!H) {
		// some axy's have IMAGEW/H only in the primary hdr...
		hdr = xylist_get_primary_header(ls);
		H = qfits_header_getint(hdr, "IMAGEH", 0);
	}
	return H;
}

anbool xylist_is_file_xylist(const char* fn, const char* xcolumn, const char* ycolumn,
                           char** reason) {
    int rtn;
    xylist_t* xyls;
    err_t* err;

    errors_push_state();
    err = errors_get_state();
    err->print = NULL;
    err->save = TRUE;

    xyls = xylist_open(fn);
    if (!xyls) {
        goto bail;
    }

    if (fitstable_n_extensions(xyls->table) < 2) {
        ERROR("FITS file does not have any extensions");
        goto bail;
    }

    if (xcolumn)
        xylist_set_xname(xyls, xcolumn);
    if (ycolumn)
        xylist_set_yname(xyls, ycolumn);

    fitstable_add_read_column_struct(xyls->table, fitscolumn_double_type(),
                                     1, 0, fitscolumn_any_type(), xyls->xname, TRUE);
    fitstable_add_read_column_struct(xyls->table, fitscolumn_double_type(),
                                     1, 0, fitscolumn_any_type(), xyls->yname, TRUE);

    rtn = fitstable_read_extension(xyls->table, 1);
    if (rtn)
        fitstable_error_report_missing(xyls->table);
    xylist_close(xyls);
    if (rtn)
        goto bail;

    errors_pop_state();
    return TRUE;

 bail:
    if (reason) *reason = error_get_errs(err, ": ");
    errors_pop_state();
    return FALSE;
}

static xylist_t* xylist_new() {
    xylist_t* xy = calloc(1, sizeof(xylist_t));
    xy->xname = "X";
    xy->yname = "Y";
    xy->xtype = TFITS_BIN_TYPE_D;
    xy->ytype = TFITS_BIN_TYPE_D;
    return xy;
}

xylist_t* xylist_open(const char* fn) {
    qfits_header* hdr;
	xylist_t* ls = NULL;
	ls = xylist_new();
	if (!ls) {
        ERROR("Failed to allocate xylist");
		return NULL;
    }
    ls->table = fitstable_open_mixed(fn);
    if (!ls->table) {
		ERROR("Failed to open FITS table %s", fn);
        free(ls);
        return NULL;
    }
    ls->table->extension = 1;

    hdr = fitstable_get_primary_header(ls->table);
	ls->antype = fits_get_dupstring(hdr, "AN_FILE");
    // not including primary extension...
	ls->nfields = fitstable_n_extensions(ls->table) - 1;
    ls->include_flux = TRUE;
    ls->include_background = TRUE;
	assert(is_reading(ls));
	return ls;
}

xylist_t* xylist_open_for_writing(const char* fn) {
	xylist_t* ls;
    qfits_header* hdr;
	ls = xylist_new();
	if (!ls) {
        ERROR("Failed to allocate xylist");
        return NULL;
    }
    ls->table = fitstable_open_for_writing(fn);
    if (!ls->table) {
        ERROR("Failed to open FITS table for writing");
        free(ls);
        return NULL;
    }
    // since we have to call xylist_next_field() before writing the first one...
    ls->table->extension = 0;

    xylist_set_antype(ls, AN_FILETYPE_XYLS);
    hdr = fitstable_get_primary_header(ls->table);
    qfits_header_add(hdr, "AN_FILE", ls->antype, "Astrometry.net file type", NULL);
	assert(is_writing(ls));
	return ls;
}

int xylist_add_tagalong_column(xylist_t* ls, tfits_type c_type,
                               int arraysize, tfits_type fits_type,
                               const char* name, const char* units) {
	assert(is_writing(ls));
    fitstable_add_write_column_struct(ls->table, c_type, arraysize,
                                      0, fits_type, name, units);
    return fitstable_ncols(ls->table) - 1;
}

int xylist_write_tagalong_column(xylist_t* ls, int colnum,
                                 int offset, int N,
                                 void* data, int datastride) {
	assert(is_writing(ls));
    return fitstable_write_one_column(ls->table, colnum, offset, N,
                                      data, datastride);
}

void* xylist_read_tagalong_column(xylist_t* ls, const char* colname,
                                  tfits_type c_type) {
	assert(is_reading(ls));
    return fitstable_read_column_array(ls->table, colname, c_type);
}

sl* xylist_get_tagalong_column_names(xylist_t* ls, sl* lst) {
    char* x;
    char* y;
	assert(is_reading(ls));
	lst = fitstable_get_fits_column_names(ls->table, lst);
	x = sl_remove_string_bycaseval(lst, ls->xname);
	y = sl_remove_string_bycaseval(lst, ls->yname);
    free(x);
    free(y);
    return lst;
}

void xylist_set_antype(xylist_t* ls, const char* type) {
    free(ls->antype);
    ls->antype = strdup(type);
}

int xylist_close(xylist_t* ls) {
    int rtn = 0;
    if (ls->table) {
        if (fitstable_close(ls->table)) {
			ERROR("Failed to close xylist table");
            rtn = -1;
        }
    }
    free(ls->antype);
	free(ls);
	return rtn;
}

void xylist_set_xname(xylist_t* ls, const char* name) {
    ls->xname = name;
}
void xylist_set_yname(xylist_t* ls, const char* name) {
    ls->yname = name;
}
void xylist_set_xtype(xylist_t* ls, tfits_type type) {
    ls->xtype = type;
}
void xylist_set_ytype(xylist_t* ls, tfits_type type) {
    ls->ytype = type;
}
void xylist_set_xunits(xylist_t* ls, const char* units) {
    ls->xunits = units;
}
void xylist_set_yunits(xylist_t* ls, const char* units) {
    ls->yunits = units;
}

void xylist_set_include_flux(xylist_t* ls, anbool inc) {
    ls->include_flux = inc;
}

void xylist_set_include_background(xylist_t* ls, anbool inc) {
    ls->include_background = inc;
}

int xylist_n_fields(xylist_t* ls) {
    return ls->nfields;
}

int xylist_write_one_row(xylist_t* ls, starxy_t* fld, int row) {
	// FIXME -- does this work if you're using background but not flux?
	assert(is_writing(ls));
    return fitstable_write_row(ls->table, fld->x + row, fld->y + row,
                               ls->include_flux ? fld->flux + row : NULL,
                               ls->include_background ? fld->background + row : NULL);
}

int xylist_write_one_row_data(xylist_t* ls, double x, double y,
							  double flux, double bg) {
	assert(is_writing(ls));
    return fitstable_write_row(ls->table, &x, &y,
                               ls->include_flux ? &flux : NULL,
                               ls->include_background ? &bg : NULL);
}

int xylist_write_field(xylist_t* ls, starxy_t* fld) {
    int i;
	assert(is_writing(ls));
    assert(fld);
    for (i=0; i<fld->N; i++) {
        if (fitstable_write_row(ls->table, fld->x + i, fld->y + i,
                                ls->include_flux ? fld->flux + i : NULL,
                                ls->include_background ? fld->background + i : NULL))
            return -1;
    }
    return 0;
}

starxy_t* xylist_read_field(xylist_t* ls, starxy_t* fld) {
    anbool freeit = FALSE;
    tfits_type dubl = fitscolumn_double_type();
	assert(is_reading(ls));

    if (!ls->table->table)
		xylist_open_field(ls, ls->table->extension);

    if (!ls->table->table) {
        // FITS table not found.
        return NULL;
    }

    if (!fld) {
        fld = calloc(1, sizeof(starxy_t));
        freeit = TRUE;
    }

    fld->N = fitstable_nrows(ls->table);
    fld->x = fitstable_read_column(ls->table, ls->xname, dubl);
    fld->y = fitstable_read_column(ls->table, ls->yname, dubl);
    if (ls->include_flux)
        fld->flux = fitstable_read_column(ls->table, "FLUX", dubl);
    else
        fld->flux = NULL;
    if (ls->include_background)
        fld->background = fitstable_read_column(ls->table, "BACKGROUND", dubl);
    else
        fld->background = NULL;

    if (!(fld->x && fld->y)) {
        free(fld->x);
        free(fld->y);
        free(fld->flux);
        free(fld->background);
        if (freeit)
            free(fld);
        return NULL;
    }
    return fld;
}

starxy_t* xylist_read_field_num(xylist_t* ls, int ext, starxy_t* fld) {
	starxy_t* rtn;
	assert(is_reading(ls));
	if (xylist_open_field(ls, ext)) {
		ERROR("Failed to open field %i from xylist", ext);
        return NULL;
    }
	rtn = xylist_read_field(ls, fld);
	if (!rtn)
		ERROR("Failed to read field %i from xylist", ext);
    return rtn;
}

int xylist_open_field(xylist_t* ls, int i) {
    return fitstable_open_extension(ls->table, i);
}

/*
 Used for both reading and writing.

 --when writing: start a new field.  Set up the table and header
 structures so that they can be added to before writing the field
 header.

 --when reading: move on to the next extension.
 */
int xylist_next_field(xylist_t* ls) {
    if (is_writing(ls)) {
		fitstable_next_extension(ls->table);
        fitstable_clear_table(ls->table);
        ls->nfields++;
    } else {
        int rtn = fitstable_open_next_extension(ls->table);
        //int rtn = fitstable_open_extension(ls->table, ls->table->extension);
        if (rtn)
            return rtn;
    }
    return 0;
}

qfits_header* xylist_get_primary_header(xylist_t* ls) {
    qfits_header* hdr;
    hdr = fitstable_get_primary_header(ls->table);
	// ??
    qfits_header_mod(hdr, "AN_FILE", ls->antype, "Astrometry.net file type");
    return hdr;
}

qfits_header* xylist_get_header(xylist_t* ls) {
    if (is_writing(ls) && !ls->table->header) {
        fitstable_add_write_column_convert(ls->table, ls->xtype,
                                           fitscolumn_double_type(),
                                           ls->xname, ls->xunits);
        fitstable_add_write_column_convert(ls->table, ls->ytype,
                                           fitscolumn_double_type(),
                                           ls->yname, ls->yunits);

        if (ls->include_flux)
            fitstable_add_write_column_convert(ls->table,
                                               fitscolumn_double_type(),
                                               fitscolumn_double_type(),
                                               "FLUX", "fluxunits");
        if (ls->include_background)
            fitstable_add_write_column_convert(ls->table,
                                               fitscolumn_double_type(),
                                               fitscolumn_double_type(),
                                               "BACKGROUND", "fluxunits");

        fitstable_new_table(ls->table);
    }
	if (is_reading(ls) && !ls->table->header)
		xylist_open_field(ls, ls->table->extension);
    return fitstable_get_header(ls->table);
}

int xylist_write_primary_header(xylist_t* ls) {
	assert(is_writing(ls));
    // ensure we've added the AN_FILE header...
    xylist_get_primary_header(ls);
    return fitstable_write_primary_header(ls->table);
}

int xylist_fix_primary_header(xylist_t* ls) {
	assert(is_writing(ls));
    return fitstable_fix_primary_header(ls->table);
}

int xylist_write_header(xylist_t* ls) {
	assert(is_writing(ls));
    // ensure we've added our columns to the table...
    xylist_get_header(ls);
    return fitstable_write_header(ls->table);
}

int xylist_fix_header(xylist_t* ls) {
	assert(is_writing(ls));
    return fitstable_fix_header(ls->table);
}

