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

#include "coadd.h"
#include "mathutil.h"
#include "errors.h"
#include "log.h"
#include "resample.h"
#include "os-features.h"

coadd_t* coadd_new_from_wcs(anwcs_t* wcs) {
    int W,H;
    coadd_t* co;
    W = anwcs_imagew(wcs);
    H = anwcs_imagew(wcs);
    co = coadd_new(W, H);
    if (!co) {
	return NULL;
    }
    co->wcs = wcs;
    return co;
}

coadd_t* coadd_new(int W, int H) {
    coadd_t* ca = calloc(1, sizeof(coadd_t));
    ca->img = calloc(W * H, sizeof(number));
    ca->weight = calloc(W * H, sizeof(number));
    ca->W = W;
    ca->H = H;
    ca->resample_func = nearest_resample_f;
    return ca;
}

void coadd_set_lanczos(coadd_t* co, int Lorder) {
    lanczos_args_t* L = calloc(1, sizeof(lanczos_args_t));
    L->weighted = 0;
    L->order = Lorder;
    co->resample_token = L;
    co->resample_func = lanczos_resample_f;
}

void coadd_debug(coadd_t* co) {
    int i;
    double mn,mx;
    mn = 1e300;
    mx = -1e300;
    for (i=0; i<(co->W*co->H); i++) {
        mn = MIN(mn, co->img[i]);
        mx = MAX(mx, co->img[i]);
    }
    logmsg("Coadd img min,max %g,%g\n", mn,mx);
    mn = 1e300;
    mx = -1e300;
    for (i=0; i<(co->W*co->H); i++) {
        mn = MIN(mn, co->weight[i]);
        mx = MAX(mx, co->weight[i]);
    }
    logmsg("Weight img min,max %g,%g\n", mn,mx);
    mn = 1e300;
    mx = -1e300;
    for (i=0; i<(co->W*co->H); i++) {
        if (co->weight[i] > 0) {
            mn = MIN(mn, co->img[i] / co->weight[i]);
            mx = MAX(mx, co->img[i] / co->weight[i]);
        }
    }
    logmsg("Img/Weight min,max %g,%g\n", mn,mx);
}

typedef struct {
    double xlo, xhi, ylo, yhi;
    anwcs_t* wcs;
} check_bounds_t;

static void check_bounds(const anwcs_t* wcs, double x, double y, double ra, double dec,
                         void* token) {
    // project the RA,Dec into the co-add
    check_bounds_t* cb = (check_bounds_t*)token;
    double cx, cy;
    if (anwcs_radec2pixelxy(cb->wcs, ra, dec, &cx, &cy)) {
        ERROR("Failed to project RA,Dec (%g,%g) into coadd WCS\n", ra, dec);
        return;
    }
    cx -= 1;
    cy -= 1;
    cb->xlo = MIN(cb->xlo, cx);
    cb->xhi = MAX(cb->xhi, cx);
    cb->ylo = MIN(cb->ylo, cy);
    cb->yhi = MAX(cb->yhi, cy);
}


int coadd_add_image(coadd_t* ca, const number* img,
                    const number* weightimg,
                    number weight, const anwcs_t* wcs) {
    int W, H;
    int i, j;
    int xlo,xhi,ylo,yhi;
    check_bounds_t cb;

    W = anwcs_imagew(wcs);
    H = anwcs_imageh(wcs);

    // if check_bounds:
    cb.xlo = W;
    cb.xhi = 0;
    cb.ylo = H;
    cb.yhi = 0;
    cb.wcs = ca->wcs;
    anwcs_walk_image_boundary(wcs, 50, check_bounds, &cb);
    xlo = MAX(0,     floor(cb.xlo));
    xhi = MIN(ca->W,  ceil(cb.xhi)+1);
    ylo = MAX(0,     floor(cb.ylo));
    yhi = MIN(ca->H,  ceil(cb.yhi)+1);
    logmsg("Image projects to output image region: [%i,%i), [%i,%i)\n", xlo, xhi, ylo, yhi);

    for (i=ylo; i<yhi; i++) {
        for (j=xlo; j<xhi; j++) {
            double ra, dec;
            double px, py;
            double wt;
            double val;

            // +1 for FITS
            if (anwcs_pixelxy2radec(ca->wcs, j+1, i+1, &ra, &dec)) {
                ERROR("Failed to project pixel (%i,%i) through output WCS\n", j, i);
                continue;
            }
            if (anwcs_radec2pixelxy(wcs, ra, dec, &px, &py)) {
                ERROR("Failed to project pixel (%i,%i) through input WCS\n", j, i);
                continue;
            }
            // -1 for FITS
            px -= 1;
            py -= 1;

            if (px < 0 || px >= W)
                continue;
            if (py < 0 || py >= H)
                continue;

            val = ca->resample_func(px, py, img, weightimg, W, H, &wt,
                                    ca->resample_token);
            ca->img[i*ca->W + j] += val * weight;
            ca->weight[i*ca->W + j] += wt * weight;
        }
        logverb("Row %i of %i\n", i+1, ca->H);
    }
    return 0;
}


number* coadd_get_snapshot(coadd_t* co, number* outimg,
                           number badpix) {
    int i;
    if (!outimg)
	outimg = calloc(co->W * co->H, sizeof(number));

    for (i=0; i<(co->W * co->H); i++) {
        if (co->weight[i] == 0)
            outimg[i] = badpix;
        else
            outimg[i] = co->img[i] / co->weight[i];
    }
    return outimg;
}


// divide "img" by "weight"; set img=badpix where weight=0.
void coadd_divide_by_weight(coadd_t* ca, number badpix) {
    int i;
    for (i=0; i<(ca->W * ca->H); i++) {
        if (ca->weight[i] == 0)
            ca->img[i] = badpix;
        else
            ca->img[i] /= ca->weight[i];
    }
}

void coadd_free(coadd_t* ca) {
    free(ca->img);
    free(ca->weight);
    free(ca);
}

number* coadd_create_weight_image_from_range(const number* img, int W, int H,
                                             number lowval, number highval) {
    int i;
    number* weight = malloc(W*H*sizeof(number));
    for (i=0; i<(W*H); i++) {
        if (img[i] <= lowval)
            weight[i] = 0;
        else if (img[i] >= highval)
            weight[i] = 0;
        else
            weight[i] = 1;
    }
    return weight;
}

void coadd_weight_image_mask_value(const number* img, int W, int H,
                                   number* weight, number badval) {
    int i;
    for (i=0; i<(W*H); i++) {
        if (img[i] == badval) {
            weight[i] = 0;
        }
    }
}
