#include <inttypes.h>
#include <grass/raster.h>
#include <grass/glocale.h>
#include "local_proto.h"

/* need elevation map, do A* search on elevation like for r.watershed */

int ele_round(double x)
{
    if (x >= 0.0)
        return x + .5;
    else
        return x - .5;
}

/*
 * loads elevation and optional flow accumulation map to memory and
 * gets start points for A* Search
 * start points are edges
 */
int load_maps(int ele_fd, int acc_fd)
{
    int r, c;
    void *ele_buf, *ptr, *acc_buf = NULL, *acc_ptr = NULL;
    CELL ele_value, *stream_id;
    DCELL dvalue, acc_value;
    size_t ele_size, acc_size = 0;
    int ele_map_type, acc_map_type = 0;
    WAT_ALT *wabuf;

    ASP_FLAG *afbuf;

    if (acc_fd < 0)
        G_message(_("Loading elevation raster map..."));
    else
        G_message(_("Loading input raster maps..."));

    n_search_points = n_points = 0;

    ele_map_type = Rast_get_map_type(ele_fd);
    ele_size = Rast_cell_size(ele_map_type);
    ele_buf = Rast_allocate_buf(ele_map_type);

    if (ele_buf == NULL) {
        G_warning(_("Unable to allocate memory"));
        return -1;
    }

    if (acc_fd >= 0) {
        acc_map_type = Rast_get_map_type(acc_fd);
        acc_size = Rast_cell_size(acc_map_type);
        acc_buf = Rast_allocate_buf(acc_map_type);
        if (acc_buf == NULL) {
            G_warning(_("Unable to allocate memory"));
            return -1;
        }
    }

    ele_scale = 1;
    if (ele_map_type == FCELL_TYPE || ele_map_type == DCELL_TYPE)
        ele_scale = 1000; /* should be enough to do the trick */

    wabuf = G_malloc(ncols * sizeof(WAT_ALT));
    afbuf = G_malloc(ncols * sizeof(ASP_FLAG));
    stream_id = G_malloc(ncols * sizeof(CELL));

    G_debug(1, "start loading %d rows, %d cols", nrows, ncols);
    for (r = 0; r < nrows; r++) {

        G_percent(r, nrows, 2);

        Rast_get_row(ele_fd, ele_buf, r, ele_map_type);
        ptr = ele_buf;

        if (acc_fd >= 0) {
            Rast_get_row(acc_fd, acc_buf, r, acc_map_type);
            acc_ptr = acc_buf;
        }

        for (c = 0; c < ncols; c++) {

            afbuf[c].flag = 0;
            afbuf[c].asp = 0;
            stream_id[c] = 0;

            /* check for masked and NULL cells */
            if (Rast_is_null_value(ptr, ele_map_type)) {
                FLAG_SET(afbuf[c].flag, NULLFLAG);
                FLAG_SET(afbuf[c].flag, INLISTFLAG);
                FLAG_SET(afbuf[c].flag, WORKEDFLAG);
                FLAG_SET(afbuf[c].flag, WORKED2FLAG);
                Rast_set_c_null_value(&ele_value, 1);
                /* flow accumulation */
                if (acc_fd >= 0) {
                    if (!Rast_is_null_value(acc_ptr, acc_map_type))
                        G_fatal_error(_("Elevation raster map is NULL but "
                                        "accumulation map is not NULL"));
                }
                Rast_set_d_null_value(&acc_value, 1);
            }
            else if (acc_fd >= 0 && Rast_is_null_value(acc_ptr, acc_map_type)) {
                /* elevation is not NULL, but provided accumulation is NULL
                 * this is ok after weighing or
                 * when analysing a selected upstream catchment area */
                FLAG_SET(afbuf[c].flag, NULLFLAG);
                FLAG_SET(afbuf[c].flag, INLISTFLAG);
                FLAG_SET(afbuf[c].flag, WORKEDFLAG);
                FLAG_SET(afbuf[c].flag, WORKED2FLAG);
                Rast_set_c_null_value(&ele_value, 1);
                Rast_set_d_null_value(&acc_value, 1);
            }
            else {
                /* elevation is not NULL, and
                 * if accumulation is provided it is also not NULL */
                switch (ele_map_type) {
                case CELL_TYPE:
                    ele_value = *((CELL *)ptr);
                    break;
                case FCELL_TYPE:
                    dvalue = *((FCELL *)ptr);
                    dvalue *= ele_scale;
                    ele_value = ele_round(dvalue);
                    break;
                case DCELL_TYPE:
                    dvalue = *((DCELL *)ptr);
                    dvalue *= ele_scale;
                    ele_value = ele_round(dvalue);
                    break;
                }
                if (acc_fd < 0)
                    acc_value = 1;
                else {
                    switch (acc_map_type) {
                    case CELL_TYPE:
                        acc_value = *((CELL *)acc_ptr);
                        break;
                    case FCELL_TYPE:
                        acc_value = *((FCELL *)acc_ptr);
                        break;
                    case DCELL_TYPE:
                        acc_value = *((DCELL *)acc_ptr);
                        break;
                    }
                }

                n_points++;
            }

            wabuf[c].wat = acc_value;
            wabuf[c].ele = ele_value;
            ptr = G_incr_void_ptr(ptr, ele_size);
            if (acc_fd >= 0)
                acc_ptr = G_incr_void_ptr(acc_ptr, acc_size);
        }
        seg_put_row(&watalt, (char *)wabuf, r);
        seg_put_row(&aspflag, (char *)afbuf, r);
        cseg_put_row(&stream, stream_id, r);
    }
    G_percent(nrows, nrows, 1); /* finish it */

    Rast_close(ele_fd);
    G_free(ele_buf);
    G_free(wabuf);
    G_free(afbuf);
    G_free(stream_id);

    if (acc_fd >= 0) {
        Rast_close(acc_fd);
        G_free(acc_buf);
    }

    G_debug(1, "%" PRId64 " non-NULL cells", n_points);

    return (n_points > 0);
}
