/*!
 * \file vinput2d.c
 *
 * \author
 * Written by H. Mitasova, I. Kosinovsky, D. Gerdes Fall 1993
 * University of Illinois
 * US Army Construction Engineering Research Lab
 *
 * \author
 * Mitasova (University of Illinois),
 * I. Kosinovsky, (USA-CERL), and D.Gerdes (USA-CERL)
 *
 * \author modified by McCauley in August 1995
 * \author modified by Mitasova in August 1995
 * \author modofied by Mitasova in Nov 1999 (dmax fix)
 *
 * \copyright
 * (C) 1993-1999 by Helena Mitasova and the GRASS Development Team
 *
 * \copyright
 * This program is free software under the
 * GNU General Public License (>=v2).
 * Read the file COPYING that comes with GRASS
 * for details.
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <grass/bitmap.h>
#include <grass/linkm.h>
#include <grass/gis.h>
#include <grass/dbmi.h>
#include <grass/vector.h>
#include <grass/glocale.h>

#include <grass/interpf.h>

/*!
 * Insert into a quad tree
 *
 * Inserts input data inside the region into a quad tree. Also translates
 * data. Returns number of segments in the quad tree.
 *
 * As z values may be used (in *Map*):
 * - z coordinates in 3D file -> field = 0
 * - categories -> field > 0, zcol = NULL
 * - attributes -> field > 0, zcol != NULL
 */
int IL_vector_input_data_2d(
    struct interp_params *params, /*!< interpolation parameters */
    struct Map_info *Map,         /*!< input vector map */
    int field,                    /*!< category field number */
    char *zcol,                   /*!< name of the column containing z values */
    char *scol,             /*!< name of the column containing smooth values */
    struct tree_info *info, /*!< quadtree info */
    double *xmin, double *xmax, double *ymin, double *ymax, double *zmin,
    double *zmax, int *n_points, /*!< number of points used for interpolation */
    double *dmax                 /*!< max distance between points */
)
{
    double dmax2; /* max distance between points squared */
    double c1, c2, c3, c4;
    int i, k = 0;
    double ns_res, ew_res;
    int npoint, OUTRANGE;
    int totsegm;
    struct quaddata *data = (struct quaddata *)info->root->data;
    double xprev, yprev, zprev, x1, y1, z1, d1, xt, yt, z, sm;
    struct line_pnts *Points;
    struct line_cats *Cats;
    int times, j1, ltype, cat, zctype = 0, sctype = 0;
    struct field_info *Fi;
    dbDriver *driver;
    dbHandle handle;
    dbString stmt;
    dbCatValArray zarray, sarray;

    OUTRANGE = 0;
    npoint = 0;

    G_debug(2, "IL_vector_input_data_2d(): field = %d, zcol = %s, scol = %s",
            field, zcol, scol);
    ns_res = (data->ymax - data->y_orig) / data->n_rows;
    ew_res = (data->xmax - data->x_orig) / data->n_cols;
    dmax2 = *dmax * *dmax;

    Points = Vect_new_line_struct(); /* init line_pnts struct */
    Cats = Vect_new_cats_struct();

    if (field == 0 && !Vect_is_3d(Map))
        G_fatal_error(_("Vector map <%s> is not 3D"), Vect_get_full_name(Map));

    if (field > 0 && zcol != NULL) { /* open db driver */
        G_verbose_message(_("Loading data from attribute table ..."));
        Fi = Vect_get_field(Map, field);
        if (Fi == NULL)
            G_fatal_error(_("Database connection not defined for layer %d"),
                          field);
        G_debug(3, "  driver = %s database = %s table = %s", Fi->driver,
                Fi->database, Fi->table);
        db_init_handle(&handle);
        db_init_string(&stmt);
        driver = db_start_driver(Fi->driver);
        db_set_handle(&handle, Fi->database, NULL);
        if (db_open_database(driver, &handle) != DB_OK)
            G_fatal_error(_("Unable to open database <%s> by driver <%s>"),
                          Fi->database, Fi->driver);

        zctype = db_column_Ctype(driver, Fi->table, zcol);
        G_debug(3, " zcol C type = %d", zctype);
        if (zctype == -1)
            G_fatal_error(_("Column <%s> not found"), zcol);
        if (zctype != DB_C_TYPE_INT && zctype != DB_C_TYPE_DOUBLE)
            G_fatal_error(_("Data type of column <%s> must be numeric"), zcol);

        db_CatValArray_init(&zarray);
        G_debug(3, "RST SQL WHERE: %s", params->wheresql);
        db_select_CatValArray(driver, Fi->table, Fi->key, zcol,
                              params->wheresql, &zarray);

        if (scol != NULL) {
            sctype = db_column_Ctype(driver, Fi->table, scol);
            G_debug(3, " scol C type = %d", sctype);
            if (sctype == -1)
                G_fatal_error(_("Column <%s> not found"), scol);
            if (sctype != DB_C_TYPE_INT && sctype != DB_C_TYPE_DOUBLE)
                G_fatal_error(_("Data type of column <%s> must be numeric"),
                              scol);

            db_CatValArray_init(&sarray);
            db_select_CatValArray(driver, Fi->table, Fi->key, scol,
                                  params->wheresql, &sarray);
        }

        db_close_database_shutdown_driver(driver);
    }

    /* Lines without nodes */
    G_message(_("Reading features from vector map ..."));
    sm = 0;
    while ((ltype = Vect_read_next_line(Map, Points, Cats)) != -2) {

        if (!(ltype & (GV_POINT | GV_LINE | GV_BOUNDARY)))
            continue;

        if (field > 0) { /* use cat or attribute */
            Vect_cat_get(Cats, field, &cat);

            if (zcol == NULL) { /* use categories */
                z = (double)cat;
            }
            else { /* read att from db */
                int ret, intval;

                if (zctype == DB_C_TYPE_INT) {
                    ret = db_CatValArray_get_value_int(&zarray, cat, &intval);
                    z = intval;
                }
                else { /* DB_C_TYPE_DOUBLE */
                    ret = db_CatValArray_get_value_double(&zarray, cat, &z);
                }

                if (ret != DB_OK) {
                    if (params->wheresql != NULL)
                        /* G_message(_("Database record for cat %d not used due
                         * to SQL statement")); */
                        /* do nothing in this case to not confuse user. Or
                         * implement second cat list */
                        ;
                    else
                        G_warning(_("Database record for cat %d not found"),
                                  cat);
                    continue;
                }

                if (scol != NULL) {
                    if (sctype == DB_C_TYPE_INT) {
                        ret =
                            db_CatValArray_get_value_int(&sarray, cat, &intval);
                        sm = intval;
                    }
                    else { /* DB_C_TYPE_DOUBLE */
                        ret =
                            db_CatValArray_get_value_double(&sarray, cat, &sm);
                    }
                    if (sm < 0.0)
                        G_fatal_error(_("Negative value of smoothing detected: "
                                        "sm must be >= 0"));
                }
                G_debug(5, "  z = %f sm = %f", z, sm);
            }
        }

        /* Insert all points including nodes (end points) */
        for (i = 0; i < Points->n_points; i++) {
            if (field == 0)
                z = Points->z[i];
            process_point(Points->x[i], Points->y[i], z, sm, info,
                          params->zmult, xmin, xmax, ymin, ymax, zmin, zmax,
                          &npoint, &OUTRANGE, &k);
        }

        /* Check all segments */
        xprev = Points->x[0];
        yprev = Points->y[0];
        zprev = Points->z[0];
        for (i = 1; i < Points->n_points; i++) {
            /* compare the distance between current and previous */
            x1 = Points->x[i];
            y1 = Points->y[i];
            z1 = Points->z[i];

            xt = x1 - xprev;
            yt = y1 - yprev;
            d1 = (xt * xt + yt * yt);
            if ((d1 > dmax2) && (dmax2 != 0.)) {
                times = (int)(d1 / dmax2 + 0.5);
                for (j1 = 0; j1 < times; j1++) {
                    xt = x1 - j1 * ((x1 - xprev) / times);
                    yt = y1 - j1 * ((y1 - yprev) / times);
                    if (field == 0)
                        z = z1 - j1 * ((z1 - zprev) / times);

                    process_point(xt, yt, z, sm, info, params->zmult, xmin,
                                  xmax, ymin, ymax, zmin, zmax, &npoint,
                                  &OUTRANGE, &k);
                }
            }
            xprev = x1;
            yprev = y1;
            zprev = z1;
        }
    }

    if (field > 0 && zcol != NULL)
        db_CatValArray_free(&zarray);
    if (scol != NULL) {
        db_CatValArray_free(&sarray);
    }

    c1 = *xmin - data->x_orig;
    c2 = data->xmax - *xmax;
    c3 = *ymin - data->y_orig;
    c4 = data->ymax - *ymax;
    if ((c1 > 5 * ew_res) || (c2 > 5 * ew_res) || (c3 > 5 * ns_res) ||
        (c4 > 5 * ns_res)) {
        static int once = 0;

        if (!once) {
            once = 1;
            G_warning(_("Strip exists with insufficient data"));
        }
    }

    totsegm = translate_quad(info->root, data->x_orig, data->y_orig, *zmin, 4);
    if (!totsegm)
        return 0;
    data->x_orig = 0;
    data->y_orig = 0;

    /* G_read_vector_timestamp(name,mapset,ts); */

    if (OUTRANGE > 0)
        G_warning(_("There are points outside specified 2D/3D region - %d "
                    "points ignored"),
                  OUTRANGE);
    if (npoint > 0)
        G_important_message(_("Ignoring %d points (too dense)"), npoint);
    npoint = k - npoint - OUTRANGE;
    if (npoint < params->kmin) {
        if (npoint != 0) {
            G_warning(_("%d points given for interpolation (after thinning) is "
                        "less than given NPMIN=%d"),
                      npoint, params->kmin);
            params->kmin = npoint;
        }
        else {
            G_warning(_("Zero points in the given region"));
            return -1;
        }
    }
    if (npoint > params->KMAX2 && params->kmin <= params->kmax) {
        G_warning(
            _("Segmentation parameters set to invalid values: npmin= %d, "
              "segmax= %d "
              "for smooth connection of segments, npmin > segmax (see manual)"),
            params->kmin, params->kmax);
        return -1;
    }
    if (npoint < params->KMAX2 && params->kmax != params->KMAX2)
        G_warning(_("There are less than %d points for interpolation. No "
                    "segmentation is necessary, to run the program faster set "
                    "segmax=%d (see manual)"),
                  params->KMAX2, params->KMAX2);

    G_verbose_message(_("Number of points from vector map %d"), k);
    G_verbose_message(_("Number of points outside of 2D/3D region %d"),
                      OUTRANGE);
    G_verbose_message(_("Number of points being used %d"), npoint);

    *n_points = npoint;
    return (totsegm);
}

int process_point(double x, double y, double z, double sm,
                  struct tree_info *info, /* quadtree info */
                  double zmult,           /* multiplier for z-values */
                  double *xmin, double *xmax, double *ymin, double *ymax,
                  double *zmin, double *zmax, int *npoint, int *OUTRANGE,
                  int *total)
{
    struct triple *point;
    double c1, c2, c3, c4;
    int a;
    static int first_time = 1;
    struct quaddata *data = (struct quaddata *)info->root->data;

    (*total)++;

    z = z * zmult;
    c1 = x - data->x_orig;
    c2 = data->xmax - x;
    c3 = y - data->y_orig;
    c4 = data->ymax - y;

    if (!((c1 >= 0) && (c2 >= 0) && (c3 >= 0) && (c4 >= 0))) {
        if (!(*OUTRANGE)) {
            G_warning(_("Some points outside of region (ignored)"));
        }
        (*OUTRANGE)++;
    }
    else {
        if (!(point = quad_point_new(x, y, z, sm))) {
            G_warning(_("Unable to allocate memory"));
            return -1;
        }
        a = MT_insert(point, info, info->root, 4);
        if (a == 0) {
            (*npoint)++;
        }
        if (a < 0) {
            G_warning(_("Unable to insert %f,%f,%f a = %d"), x, y, z, a);
            return -1;
        }
        free(point);
        if (first_time) {
            first_time = 0;
            *xmin = x;
            *ymin = y;
            *zmin = z;
            *xmax = x;
            *ymax = y;
            *zmax = z;
        }
        *xmin = amin1(*xmin, x);
        *ymin = amin1(*ymin, y);
        *zmin = amin1(*zmin, z);
        *xmax = amax1(*xmax, x);
        *ymax = amax1(*ymax, y);
        *zmax = amax1(*zmax, z);
    }
    return 1;
}
