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

#include "os-features.h"
#include "plotgrid.h"
#include "sip-utils.h"
#include "cairoutils.h"
#include "ioutils.h"
#include "log.h"
#include "errors.h"

DEFINE_PLOTTER(grid);

plotgrid_t* plot_grid_get(plot_args_t* pargs) {
    return plotstuff_get_config(pargs, "grid");
}

void* plot_grid_init(plot_args_t* plotargs) {
    plotgrid_t* args = calloc(1, sizeof(plotgrid_t));
    args->dolabel = TRUE;
    args->raformat = strdup("%.2f");
    args->decformat = strdup("%.2f");
    return args;
}

int plot_grid_set_formats(plotgrid_t* args, const char* raformat, const char* decformat) {
    free(args->raformat);
    free(args->decformat);
    args->raformat = strdup_safe(raformat);
    args->decformat = strdup_safe(decformat);
    return 0;
}

static void pretty_label(const char* fmt, double x, char* buf) {
    int i;
    sprintf(buf, fmt, x);
    logverb("label: \"%s\"\n", buf);
    // Look for decimal point.
    if (!strchr(buf, '.')) {
        logverb("no decimal point\n");
        return;
    }
    // Trim trailing zeroes (after the decimal point)
    i = strlen(buf)-1;
    while (buf[i] == '0') {
        buf[i] = '\0';
        logverb("trimming trailing zero at %i: \"%s\"\n", i, buf);
        i--;
        assert(i > 0);
    }
    // Trim trailing decimal point, if it exists.
    i = strlen(buf)-1;
    if (buf[i] == '.') {
        buf[i] = '\0';
        logverb("trimming trailing decimal point at %i: \"%s\"\n", i, buf);
    }
}

static int setdir(int dir, int* dirs, int* ndir) {
    switch (dir) {
    case DIRECTION_DEFAULT:
    case DIRECTION_POSNEG:
        dirs[0] = 1;
        dirs[1] = -1;
        *ndir = 2;
        break;
    case DIRECTION_POS:
        dirs[0] = 1;
        *ndir = 1;
        break;
    case DIRECTION_NEG:
        dirs[0] = -1;
        *ndir = 1;
        break;
    case DIRECTION_NEGPOS:
        dirs[0] = -1;
        dirs[1] = 1;
        *ndir = 2;
        break;
    default:
        return -1;
    }
    return 0;
}

int plot_grid_find_dec_label_location(plot_args_t* pargs, double dec, double cra, double ramin, double ramax, int dirn, double* pra) {
    double out;
    double in = cra;
    int i, N;
    anbool gotit;
    int dirs[2];
    int j, Ndir=0;
    logverb("Labelling Dec=%g\n", dec);
    gotit = FALSE;
    if (setdir(dirn, dirs, &Ndir))
        return -1;

    // dir is first 1, then -1.
    for (j=0; j<Ndir; j++) {
        int dir = dirs[j];
        for (i=1;; i++) {
            // take 10-deg steps
            out = cra + i*dir*10.0;
            if (out > 370.0 || out <= -10)
                break;
            out = MIN(360, MAX(0, out));
            logverb("ra in=%g, out=%g\n", in, out);
            if (!plotstuff_radec_is_inside_image(pargs, out, dec)) {
                gotit = TRUE;
                break;
            }
            if (!isfinite(in) || !isfinite(out))
                break;
        }
        if (gotit)
            break;
    }
    if (!gotit) {
        ERROR("Couldn't find an RA outside the image for Dec=%g\n", dec);
        return -1;
    }
    i=0;
    N = 10;
    while (!plotstuff_radec_is_inside_image(pargs, in, dec)) {
        if (i == N)
            break;
        in = ramin + (double)i/(double)(N-1) * (ramax-ramin);
        i++;
    }
    if (!plotstuff_radec_is_inside_image(pargs, in, dec))
        return -1;
    while (fabs(out - in) > 1e-6) {
        // hahaha
        double half;
        anbool isin;
        half = (out + in) / 2.0;
        isin = plotstuff_radec_is_inside_image(pargs, half, dec);
        if (isin)
            in = half;
        else
            out = half;
    }
    *pra = in;
    return 0;
}

int plot_grid_find_ra_label_location(plot_args_t* pargs, double ra, double cdec, double decmin, double decmax, int dirn, double* pdec) {
    double out;
    double in = cdec;
    int i, N;
    anbool gotit;
    int dirs[2];
    int j, Ndir=0;
    logverb("Labelling RA=%g\n", ra);
    // where does this line leave the image?
    // cdec is inside; take steps away until we go outside.
    gotit = FALSE;

    if (setdir(dirn, dirs, &Ndir))
        return -1;

    for (j=0; j<Ndir; j++) {
        int dir = dirs[j];
        logverb("direction: %i\n", dir);
        for (i=1;; i++) {
            // take 10-deg steps
            out = cdec + i*dir*10.0;
            logverb("trying Dec = %g\n", out);
            if (out >= 100.0 || out <= -100)
                break;
            out = MIN(90, MAX(-90, out));
            logverb("dec in=%g, out=%g\n", in, out);
            if (!plotstuff_radec_is_inside_image(pargs, ra, out)) {
                logverb("-> good!\n");
                gotit = TRUE;
                break;
            }
        }
        if (gotit)
            break;
    }
    if (!gotit) {
        ERROR("Couldn't find a Dec outside the image for RA=%g\n", ra);
        return -1;
    }
    // Now we've got a Dec inside the image (cdec)
    // and a Dec outside the image (out)
    // Now find the boundary.

    i=0;
    N = 10;
    while (!plotstuff_radec_is_inside_image(pargs, ra, in)) {
        if (i == N)
            break;
        in = decmin + (double)i/(double)(N-1) * (decmax-decmin);
        i++;
    }
    if (!plotstuff_radec_is_inside_image(pargs, ra, in))
        return -1;
    while (fabs(out - in) > 1e-6) {
        // hahaha
        double half;
        anbool isin;
        half = (out + in) / 2.0;
        isin = plotstuff_radec_is_inside_image(pargs, ra, half);
        if (isin)
            in = half;
        else
            out = half;
    }
    *pdec = in;
    return 0;
}

static int do_radec_labels(plot_args_t* pargs, plotgrid_t* args,
                           double ramin, double ramax,
                           double decmin, double decmax) {
    double cra, cdec;
    double ra, dec;

    args->dolabel = (args->ralabelstep > 0) || (args->declabelstep > 0);
    if (!args->dolabel)
        return 0;

    /*
     if (args->ralabelstep == 0 || args->declabelstep == 0) {
     // FIXME -- choose defaults
     ERROR("Need grid_ralabelstep, grid_declabelstep");
     return 0;
     }
     */
    logmsg("Adding grid labels...\n");
    plotstuff_get_radec_center_and_radius(pargs, &cra, &cdec, NULL);
    assert(cra >= ramin && cra <= ramax);
    assert(cdec >= decmin && cdec <= decmax);
    if (args->ralabelstep > 0) {
        double rlo, rhi;
        if (args->ralo != 0 || args->rahi != 0) {
            rlo = args->ralo;
            rhi = args->rahi;
        } else {
            rlo = args->ralabelstep * floor(ramin / args->ralabelstep);
            rhi = args->ralabelstep * ceil(ramax / args->ralabelstep);
        }
        for (ra = rlo; ra <= rhi; ra += args->ralabelstep) {
            double lra;
            if (plot_grid_find_ra_label_location(pargs, ra, cdec, decmin,
                                                 decmax, args->ralabeldir, &dec))
                continue;
            lra = ra;
            if (lra < 0)
                lra += 360;
            if (lra >= 360)
                lra -= 360;
            //logmsg("Label \"%s\" at (%g,%g)\n", label, ra, dec);
            plot_grid_add_label(pargs, ra, dec, lra, args->raformat);
        }
    }
    if (args->declabelstep > 0) {
        double dlo, dhi;
        if (args->declo != 0 || args->dechi != 0) {
            dlo = args->declo;
            dhi = args->dechi;
        } else {
            dlo = args->declabelstep * floor(decmin / args->declabelstep);
            dhi = args->declabelstep * ceil(decmax / args->declabelstep);
        }
        for (dec = dlo; dec <= dhi; dec += args->declabelstep) {
            if (plot_grid_find_dec_label_location(pargs, dec, cra, ramin,
                                                  ramax, args->declabeldir, &ra))
                continue;
            //logmsg("Label Dec=\"%s\" at (%g,%g)\n", label, ra, dec);
            plot_grid_add_label(pargs, ra, dec, dec, args->decformat);
        }
    }
    return 1;
}

void plot_grid_add_label(plot_args_t* pargs, double ra, double dec,
                         double lval, const char* format) {
    char label[32];
    double x,y;
#if 0
    anbool ok;
#endif
    cairo_t* cairo = pargs->cairo;
    pretty_label(format, lval, label);
#if 0
    ok = plotstuff_radec2xy(pargs, ra, dec, &x, &y);
#else
    (void)plotstuff_radec2xy(pargs, ra, dec, &x, &y);
#endif
    plotstuff_stack_text(pargs, cairo, label, x, y);
    plotstuff_plot_stack(pargs, cairo);
}

int plot_grid_plot(const char* command,
                   cairo_t* cairo, plot_args_t* pargs, void* baton) {
    plotgrid_t* args = (plotgrid_t*)baton;
    double ramin,ramax,decmin,decmax;
    double ra,dec;

    if (!pargs->wcs) {
        ERROR("No WCS was set -- can't plot grid lines");
        return -1;
    }
    // Find image bounds in RA,Dec...
    plotstuff_get_radec_bounds(pargs, 50, &ramin, &ramax, &decmin, &decmax);
    /*
     if (args->rastep == 0 || args->decstep == 0) {
     // FIXME -- choose defaults
     ERROR("Need grid_rastep, grid_decstep");
     return -1;
     }
     */

    plotstuff_builtin_apply(cairo, pargs);
    pargs->label_offset_x = 0;
    pargs->label_offset_y = 10;
	
    logverb("Image bounds: RA %g, %g, Dec %g, %g\n",
            ramin, ramax, decmin, decmax);
    if (args->rastep > 0) {
        for (ra = args->rastep * floor(ramin / args->rastep);
             ra <= args->rastep * ceil(ramax / args->rastep);
             ra += args->rastep) {
            plotstuff_line_constant_ra(pargs, ra, decmin, decmax, TRUE);
            cairo_stroke(pargs->cairo);
        }
    }
    if (args->decstep > 0) {
        for (dec = args->decstep * floor(decmin / args->decstep);
             dec <= args->decstep * ceil(decmax / args->decstep);
             dec += args->decstep) {
            plotstuff_line_constant_dec(pargs, dec, ramin, ramax);
            cairo_stroke(pargs->cairo);
        }
    }

    if (do_radec_labels(pargs, args, ramin, ramax, decmin, decmax)) {
        plotstuff_plot_stack(pargs, cairo);
    }
    return 0;
}




int plot_grid_command(const char* cmd, const char* cmdargs,
                      plot_args_t* pargs, void* baton) {
    plotgrid_t* args = (plotgrid_t*)baton;
    if (streq(cmd, "grid_rastep")) {
        args->rastep = atof(cmdargs);
    } else if (streq(cmd, "grid_decstep")) {
        args->decstep = atof(cmdargs);
    } else if (streq(cmd, "grid_ralabelstep")) {
        args->ralabelstep = atof(cmdargs);
    } else if (streq(cmd, "grid_declabelstep")) {
        args->declabelstep = atof(cmdargs);
    } else if (streq(cmd, "grid_step")) {
        args->declabelstep = args->ralabelstep =
            args->rastep = args->decstep = atof(cmdargs);
    } else {
        ERROR("Did not understand command \"%s\"", cmd);
        return -1;
    }
    return 0;
}

void plot_grid_free(plot_args_t* plotargs, void* baton) {
    plotgrid_t* args = (plotgrid_t*)baton;
    free(args->raformat);
    free(args->decformat);
    free(args);
}

