/*
 # This file is part of the Astrometry.net suite.
 # Licensed under a 3-clause BSD style license - see LICENSE
 */

#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <unistd.h>
#include <math.h>

#include "os-features.h"
#include "image2xy-files.h"
#include "image2xy.h"
#include "fitsio.h"
#include "ioutils.h"
#include "simplexy.h"
#include "errors.h"
#include "log.h"
#include "cfitsutils.h"

int image2xy_files(const char* infn, const char* outfn,
                   anbool do_u8, int downsample, int downsample_as_required,
                   int extension, int plane,
                   simplexy_t* params) {
    fitsfile *fptr = NULL;
    fitsfile *ofptr = NULL;
    int status = 0; // FIXME should have ostatus too
    int naxis;
    long naxisn[3];
    int kk;
    int nhdus, hdutype, nimgs;
    char* str;
    simplexy_t myparams;

    if (params == NULL) {
        memset(&myparams, 0, sizeof(simplexy_t));
        params = &myparams;
    }

    // QFITS to CFITSIO extension convention switch
    extension++;

    fits_open_file(&fptr, infn, READONLY, &status);
    CFITS_CHECK("Failed to open FITS input file %s", infn);

    // Are there multiple HDU's?
    fits_get_num_hdus(fptr, &nhdus, &status);
    CFITS_CHECK("Failed to read number of HDUs for input file %s", infn);
    logverb("nhdus=%d\n", nhdus);

    if (extension > nhdus) {
        logerr("Requested extension %i is greater than number of extensions (%i) in file %s\n",
               extension, nhdus, infn);
        return -1;
    }

    // Create output file
    fits_create_file(&ofptr, outfn, &status);
    CFITS_CHECK("Failed to open FITS output file %s", outfn);

    fits_create_img(ofptr, 8, 0, NULL, &status);
    CFITS_CHECK("Failed to create output image");

    fits_write_key(ofptr, TSTRING, "SRCFN", (char*)infn, "Source image", &status);
    if (extension)
        fits_write_key(ofptr, TINT, "SRCEXT", &extension, "Source image extension (1=primary)", &status);

    /* Parameters for simplexy; save for debugging */
    fits_write_comment(ofptr, "Parameters used for source extraction", &status);

    fits_write_history(ofptr, "Created by Astrometry.net's image2xy program.", &status);
    CFITS_CHECK("Failed to write HISTORY headers");

    asprintf_safe(&str, "GIT URL: %s", AN_GIT_URL);
    fits_write_history(ofptr, str, &status);
    CFITS_CHECK("Failed to write GIT HISTORY headers");
    free(str);
    asprintf_safe(&str, "GIT Rev: %s", AN_GIT_REVISION);
    fits_write_history(ofptr, str, &status);
    CFITS_CHECK("Failed to write GIT HISTORY headers");
    free(str);
    asprintf_safe(&str, "GIT Date: %s", AN_GIT_DATE);
    fits_write_history(ofptr, str, &status);
    CFITS_CHECK("Failed to write GIT HISTORY headers");
    free(str);
    fits_write_history(ofptr, "Visit us on the web at http://astrometry.net/", &status);
    CFITS_CHECK("Failed to write GIT HISTORY headers");

    nimgs = 0;

    // Run simplexy on each HDU
    for (kk=1; kk <= nhdus; kk++) {
        char* ttype[] = {"X","Y","FLUX","BACKGROUND", "LFLUX", "LBG"};
        char* tform[] = {"E","E","E","E","E","E"};
        char* tunit[] = {"pix","pix","unknown","unknown","unknown","unknown"};
        long* fpixel;
        int a;
        int w, h;
        int bitpix;
        int ncols;

        if (extension && kk != extension)
            continue;

        fits_movabs_hdu(fptr, kk, &hdutype, &status);
        fits_get_hdu_type(fptr, &hdutype, &status);

        if (hdutype != IMAGE_HDU) {
            if (extension)
                logerr("Requested extension %i in file %s is not an image.\n", extension, infn);
            continue;
        }

        fits_get_img_dim(fptr, &naxis, &status);
        CFITS_CHECK("Failed to find image dimensions for HDU %i", kk);

        fits_get_img_size(fptr, 2, naxisn, &status);
        CFITS_CHECK("Failed to find image dimensions for HDU %i", kk);

        nimgs++;

        logverb("Got naxis=%d, na1=%lu, na2=%lu\n", naxis, naxisn[0], naxisn[1]);

        fits_get_img_type(fptr, &bitpix, &status);
        CFITS_CHECK("Failed to get FITS image type");

        fpixel = malloc(naxis * sizeof(long));
        for (a=0; a<naxis; a++)
            fpixel[a] = 1;

        if (plane && naxis == 3) {
            if (plane <= naxisn[2]) {
                logmsg("Grabbing image plane %i\n", plane);
                fpixel[2] = plane;
            } else
                logerr("Requested plane %i but only %i are available.\n", plane, (int)naxisn[2]);
        } else if (plane)
            logmsg("Plane %i requested but this image has NAXIS = %i (not 3).\n", plane, naxis);
        else if (naxis > 2)
            logmsg("This looks like a multi-color image: processing the first image plane only.  (NAXIS=%i)\n", naxis);
		
        if (bitpix == 8 && do_u8 && !downsample) {
            simplexy_fill_in_defaults_u8(params);

            // u8 image.
            params->image_u8 = malloc(naxisn[0] * naxisn[1]);
            if (!params->image_u8) {
                SYSERROR("Failed to allocate u8 image array");
                goto bailout;
            }
            fits_read_pix(fptr, TBYTE, fpixel, naxisn[0]*naxisn[1], NULL,
                          params->image_u8, NULL, &status);

        } else {
            simplexy_fill_in_defaults(params);

            params->image = malloc(naxisn[0] * naxisn[1] * sizeof(float));
            if (!params->image) {
                SYSERROR("Failed to allocate image array");
                goto bailout;
            }
            fits_read_pix(fptr, TFLOAT, fpixel, naxisn[0]*naxisn[1], NULL,
                          params->image, NULL, &status);
        }
        free(fpixel);
        CFITS_CHECK("Failed to read image pixels");

        params->nx = naxisn[0];
        params->ny = naxisn[1];

        image2xy_run(params, downsample, downsample_as_required);

        if (params->Lorder)
            ncols = 6;
        else
            ncols = 4;
        fits_create_tbl(ofptr, BINARY_TBL, params->npeaks, ncols, ttype, tform,
                        tunit, "SOURCES", &status);
        CFITS_CHECK("Failed to create output table");

        fits_write_col(ofptr, TFLOAT, 1, 1, 1, params->npeaks, params->x, &status);
        CFITS_CHECK("Failed to write X column");

        fits_write_col(ofptr, TFLOAT, 2, 1, 1, params->npeaks, params->y, &status);
        CFITS_CHECK("Failed to write Y column");

        fits_write_col(ofptr, TFLOAT, 3, 1, 1, params->npeaks, params->flux, &status);
        CFITS_CHECK("Failed to write FLUX column");

        fits_write_col(ofptr, TFLOAT, 4, 1, 1, params->npeaks, params->background, &status);
        CFITS_CHECK("Failed to write BACKGROUND column");

        if (params->Lorder) {
            fits_write_col(ofptr, TFLOAT, 5, 1, 1, params->npeaks, params->fluxL, &status);
            CFITS_CHECK("Failed to write LFLUX column");

            fits_write_col(ofptr, TFLOAT, 6, 1, 1, params->npeaks, params->backgroundL, &status);
            CFITS_CHECK("Failed to write LBG column");
        }

        fits_modify_comment(ofptr, "TTYPE1", "X coordinate", &status);
        CFITS_CHECK("Failed to set X TTYPE");

        fits_modify_comment(ofptr, "TTYPE2", "Y coordinate", &status);
        CFITS_CHECK("Failed to set Y TTYPE");

        fits_modify_comment(ofptr, "TTYPE3", "Flux of source", &status);
        CFITS_CHECK("Failed to set FLUX TTYPE");

        fits_modify_comment(ofptr, "TTYPE4", "Sky background of source", &status);
        CFITS_CHECK("Failed to set BACKGROUND TTYPE");

        fits_write_key(ofptr, TINT, "SRCEXT", &kk,
                       "Extension number in src image", &status);
        CFITS_CHECK("Failed to write SRCEXT");

        w = naxisn[0];
        h = naxisn[1];
        fits_write_key(ofptr, TINT, "IMAGEW", &w, "Input image width", &status);
        CFITS_CHECK("Failed to write IMAGEW");

        fits_write_key(ofptr, TINT, "IMAGEH", &h, "Input image height", &status);
        CFITS_CHECK("Failed to write IMAGEH");

        fits_write_key(ofptr, TFLOAT, "ESTSIGMA", &(params->sigma),
                       "Estimated source image variance", &status);
        CFITS_CHECK("Failed to write ESTSIGMA");

        fits_write_key(ofptr, TFLOAT, "DPSF", &(params->dpsf), "image2xy Assumed gaussian psf width", &status);
        fits_write_key(ofptr, TFLOAT, "PLIM", &(params->plim), "image2xy Significance to keep", &status);
        fits_write_key(ofptr, TFLOAT, "DLIM", &(params->dlim), "image2xy Closest two peaks can be", &status);
        fits_write_key(ofptr, TFLOAT, "SADDLE", &(params->saddle), "image2xy Saddle difference (in sig)", &status);
        fits_write_key(ofptr, TINT, "MAXPER", &(params->maxper), "image2xy Max num of peaks per object", &status);
        fits_write_key(ofptr, TINT, "MAXPEAKS", &(params->maxnpeaks), "image2xy Max num of peaks total", &status);
        fits_write_key(ofptr, TINT, "MAXSIZE", &(params->maxsize), "image2xy Max size for extended objects", &status);
        fits_write_key(ofptr, TINT, "HALFBOX", &(params->halfbox), "image2xy Half-size for sliding sky window", &status);


        fits_write_comment(ofptr,
                           "The X and Y points are specified assuming 1,1 is "
                           "the center of the leftmost bottom pixel of the "
                           "image in accordance with the FITS standard.", &status);
        CFITS_CHECK("Failed to write comments");

        simplexy_free_contents(params);
    }

    // Put in the optional NEXTEND keywoard
    fits_movabs_hdu(ofptr, 1, &hdutype, &status);
    assert(hdutype == IMAGE_HDU);
    fits_write_key(ofptr, TINT, "NEXTEND", &nimgs, "Number of extensions", &status);
    if (status == END_OF_FILE)
        status = 0; /* Reset after normal error */
    CFITS_CHECK("Failed to write NEXTEND");

    fits_close_file(fptr, &status);
    CFITS_CHECK("Failed to close FITS input file");
    fptr = NULL;

    fits_close_file(ofptr, &status);
    CFITS_CHECK("Failed to close FITS output file");

    // for valgrind
    simplexy_clean_cache();

    return 0;

 bailout:
    if (fptr)
        fits_close_file(fptr, &status);
    if (ofptr)
        fits_close_file(ofptr, &status);
    return -1;
}
