/*
 *  v.lrs.stationing - Generate stationing as new vector map and labels from
 *                     input map for existing linear reference system
 *
 */

/******************************************************************************
 * Copyright (c) 2004, Radim Blazek (blazek@itc.it)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *****************************************************************************/
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <grass/gis.h>
#include <grass/vector.h>
#include <grass/dbmi.h>
#include <grass/glocale.h>
#include "../lib/lrs.h"

#define PI M_PI

typedef struct {
    int lid;
    double start_map, end_map;
    double start_mp,
        start_off; /* milepost, offset for the beginning of ref. segment */
    double end_mp, end_off; /* milepost, offset for the end of ref. segment */
} RSEGMENT;

int cmp_along(const void *pa, const void *pb);

int main(int argc, char **argv)
{
    int i, ret, nlines, more;
    int cat, lfield;
    int line, type;
    int lcat, mp, sta;
    double start, end, station;
    double mp_multip, sta_multip, map_offset;
    double x, y, z, angle, xs, ys, rotate;
    double sta_l_off, sta_r_off, mp_l_off,
        mp_r_off; /* Left and right offset of stationing */
    double l_off, r_off;
    double lab_x_off, lab_y_off, lab_x, lab_y;
    int arseg, nrseg, seg;
    RSEGMENT *rseg;
    struct Option *in_opt, *out_opt, *labels_opt;
    struct Option *lfield_opt;
    struct Option *driver_opt, *database_opt, *table_opt, *offset_opt;
    struct Option *Xoffset;
    struct Option *Yoffset;
    struct Option *Reference;
    struct Option *Font;
    struct Option *Color;
    struct Option *Size;
    struct Option *Width;
    struct Option *Hcolor;
    struct Option *Hwidth;
    struct Option *Bcolor;
    struct Option *Border;
    struct Option *Opaque;

    struct GModule *module;
    const char *mapset;
    char buf[2000];
    struct Map_info In, Out;
    struct line_cats *LCats, *SCats;
    struct line_pnts *LPoints, *SPoints;
    dbDriver *rsdriver;
    dbHandle rshandle;
    dbString stmt;
    dbCursor cursor;
    dbTable *table;
    dbColumn *column;
    dbValue *value;
    FILE *labels;

    double t1, t2;
    double dt;
    int nstat;

    G_gisinit(argv[0]);

    module = G_define_module();
    G_add_keyword(_("vector"));
    G_add_keyword(_("linear reference system"));
    G_add_keyword(_("network"));
    module->description = _("Creates stationing from input lines, "
                            "and linear reference system.");

    in_opt = G_define_standard_option(G_OPT_V_INPUT);
    in_opt->description = _("Input vector map containing lines");

    out_opt = G_define_standard_option(G_OPT_V_OUTPUT);
    out_opt->description =
        _("Output vector map where stationing will be written");

    lfield_opt = G_define_standard_option(G_OPT_V_FIELD);
    lfield_opt->key = "llayer";
    lfield_opt->answer = "1";
    lfield_opt->description = _("Line layer");

    driver_opt = G_define_option();
    driver_opt->key = "rsdriver";
    driver_opt->type = TYPE_STRING;
    driver_opt->required = NO;
    driver_opt->description = _("Driver name for reference system table");
    driver_opt->options = db_list_drivers();
    driver_opt->answer = (char *)db_get_default_driver_name();

    database_opt = G_define_option();
    database_opt->key = "rsdatabase";
    database_opt->type = TYPE_STRING;
    database_opt->required = NO;
    database_opt->description = _("Database name for reference system table");
    database_opt->answer = (char *)db_get_default_database_name();

    table_opt = G_define_option();
    table_opt->key = "rstable";
    table_opt->type = TYPE_STRING;
    table_opt->required = YES;
    table_opt->description = _("Name of the reference system table");

    labels_opt = G_define_option();
    labels_opt->key = "labels";
    labels_opt->type = TYPE_STRING;
    labels_opt->required = NO;
    labels_opt->multiple = NO;
    labels_opt->gisprompt = "new,paint/labels,Labels";
    labels_opt->description = _("Label file");

    offset_opt = G_define_option();
    offset_opt->key = "offset";
    offset_opt->type = TYPE_STRING;
    offset_opt->required = NO;
    offset_opt->multiple = YES;
    offset_opt->answer = "50,100,25,25";
    offset_opt->description =
        _("PM left, MP right, stationing left, stationing right offset");

    Xoffset = G_define_option();
    Xoffset->key = "xoffset";
    Xoffset->description = _("Offset label in label x-direction in map units");
    Xoffset->type = TYPE_DOUBLE;
    Xoffset->answer = "25";

    Yoffset = G_define_option();
    Yoffset->key = "yoffset";
    Yoffset->description = _("Offset label in label y-direction in map units");
    Yoffset->type = TYPE_DOUBLE;
    Yoffset->answer = "5";

    Reference = G_define_option();
    Reference->key = "reference";
    Reference->description = _("Reference position");
    Reference->type = TYPE_STRING;
    Reference->answer = "center";
    Reference->options = "center,left,right,upper,lower";

    Font = G_define_option();
    Font->key = "font";
    Font->description = _("Font");
    Font->type = TYPE_STRING;
    Font->answer = "standard";

    Size = G_define_option();
    Size->key = "size";
    Size->description = _("Label size (in map-units)");
    Size->type = TYPE_INTEGER;
    Size->answer = "100";
    Size->options = "1-1000";

    Color = G_define_standard_option(G_OPT_C);
    Color->label = _("Text color");

    Width = G_define_option();
    Width->key = "width";
    Width->label = _("Line width of text");
    Width->description = _("Only for d.label output");
    Width->type = TYPE_INTEGER;
    Width->answer = "1";
    Width->options = "1-100";

    Hcolor = G_define_standard_option(G_OPT_CN);
    Hcolor->key = "highlight_color";
    Hcolor->label = _("Highlight color for text");
    Hcolor->answer = "none";

    Hwidth = G_define_option();
    Hwidth->key = "highlight_width";
    Hwidth->label = _("Line width of highlight color");
    Hwidth->description = _("Only for d.label output");
    Hwidth->type = TYPE_INTEGER;
    Hwidth->answer = "0";
    Hwidth->options = "0-100";

    Bcolor = G_define_standard_option(G_OPT_CN);
    Bcolor->key = "bgcolor";
    Bcolor->label = _("Background color");
    Bcolor->answer = "none";

    Border = G_define_standard_option(G_OPT_CN);
    Border->key = "border";
    Border->label = _("Border color");
    Border->answer = "none";

    Opaque = G_define_option();
    Opaque->key = "opaque";
    Opaque->label = _("Opaque to vector");
    Opaque->description = _("Only relevant if background color is selected");
    Opaque->type = TYPE_STRING;
    Opaque->answer = "yes";
    Opaque->options = "yes,no";

    if (G_parser(argc, argv))
        exit(EXIT_FAILURE);

    LCats = Vect_new_cats_struct();
    SCats = Vect_new_cats_struct();
    LPoints = Vect_new_line_struct();
    SPoints = Vect_new_line_struct();

    lfield = atoi(lfield_opt->answer);
    lab_x_off = atof(Xoffset->answer);
    lab_y_off = atof(Yoffset->answer);
    mp_multip = 1000; /* Number of map units per MP unit */
    sta_multip = 100; /* Number of map units per stationing unit */

    i = 0;
    mp_l_off = 50;
    mp_r_off = 100;
    sta_l_off = 25;
    sta_r_off = 25;
    while (offset_opt->answers[i]) {
        if (i == 0)
            mp_l_off = atoi(offset_opt->answers[i]);
        else if (i == 1)
            mp_r_off = atoi(offset_opt->answers[i]);
        else if (i == 2)
            sta_l_off = atoi(offset_opt->answers[i]);
        else if (i == 3)
            sta_r_off = atoi(offset_opt->answers[i]);
        i++;
    }

    /* Open input lines */
    mapset = G_find_vector2(in_opt->answer, NULL);
    if (mapset == NULL)
        G_fatal_error(_("Vector map <%s> not found"), in_opt->answer);

    Vect_set_open_level(2);
    if (Vect_open_old(&In, in_opt->answer, mapset) < 0)
        G_fatal_error(_("Unable to open vector map <%s>"), in_opt->answer);

    /* Open output segments */
    if (Vect_open_new(&Out, out_opt->answer, Vect_is_3d(&In)) < 0)
        G_fatal_error(_("Unable to create vector map <%s>"), out_opt->answer);

    /* open labels */
    labels = NULL;
    if (labels_opt->answer) {
        labels = G_fopen_new("paint/labels", labels_opt->answer);
        if (labels == NULL)
            G_fatal_error(_("Unable to open label file <%s>"),
                          labels_opt->answer);
    }

    db_init_handle(&rshandle);
    db_init_string(&stmt);
    rsdriver = db_start_driver(driver_opt->answer);
    db_set_handle(&rshandle, database_opt->answer, NULL);
    if (db_open_database(rsdriver, &rshandle) != DB_OK)
        G_fatal_error(_("Unable to open database for reference table"));

    /* For each line select all existeng reference segments, sort them along the
     * line and fcreate stationing. */

    G_debug(2, "find_line(): lfield = %d", lfield);

    arseg = 1000;
    rseg = (RSEGMENT *)G_malloc(arseg * sizeof(RSEGMENT));

    nlines = Vect_get_num_lines(&In);
    /* for ( line = 19; line <= 19; line++ ) { */
    for (line = 1; line <= nlines; line++) {
        G_debug(3, "  line = %d / %d", line, nlines);
        type = Vect_read_line(&In, LPoints, LCats, line);
        if (!(type & GV_LINE))
            continue;
        Vect_cat_get(LCats, lfield, &cat);
        if (cat < 0)
            continue;

        sprintf(buf,
                "select start_map, end_map, start_mp, start_off, end_mp, "
                "end_off, lid "
                "from %s where lcat = %d;",
                table_opt->answer, cat);
        G_debug(2, "  SQL: %s", buf);
        db_append_string(&stmt, buf);

        G_debug(1, "    select");
        if (db_open_select_cursor(rsdriver, &stmt, &cursor, DB_SEQUENTIAL) !=
            DB_OK)
            G_fatal_error(_("Unable to select records from LRS table: %s"),
                          buf);

        table = db_get_cursor_table(&cursor);

        G_debug(1, "    fetch");
        nrseg = 0;
        while (1) {
            if (db_fetch(&cursor, DB_NEXT, &more) != DB_OK)
                G_fatal_error(_("Unable to fetch data from table"));

            if (!more)
                break;

            if (nrseg == arseg) {
                arseg += 1000;
                rseg = (RSEGMENT *)G_realloc(rseg, arseg * sizeof(RSEGMENT));
            }

            column = db_get_table_column(table, 0);
            value = db_get_column_value(column);
            rseg[nrseg].start_map = db_get_value_double(value);

            column = db_get_table_column(table, 1);
            value = db_get_column_value(column);
            rseg[nrseg].end_map = db_get_value_double(value);

            column = db_get_table_column(table, 2);
            value = db_get_column_value(column);
            rseg[nrseg].start_mp = db_get_value_double(value);

            column = db_get_table_column(table, 3);
            value = db_get_column_value(column);
            rseg[nrseg].start_off = db_get_value_double(value);

            column = db_get_table_column(table, 4);
            value = db_get_column_value(column);
            rseg[nrseg].end_mp = db_get_value_double(value);

            column = db_get_table_column(table, 5);
            value = db_get_column_value(column);
            rseg[nrseg].end_off = db_get_value_double(value);

            column = db_get_table_column(table, 6);
            value = db_get_column_value(column);
            rseg[nrseg].lid = db_get_value_int(value);

            G_debug(2, "RS: %f - %f => %f+%f - %f+%f", rseg[nrseg].start_map,
                    rseg[nrseg].end_map, rseg[nrseg].start_mp,
                    rseg[nrseg].start_off, rseg[nrseg].end_mp,
                    rseg[nrseg].end_off);

            nrseg++;
        }

        G_debug(3, "    %d reference segments selected", nrseg);
        if (nrseg == 0)
            continue;

        /* Sort along the line */
        qsort((void *)rseg, nrseg, sizeof(RSEGMENT), cmp_along);

        /* Go through all segments of current line */
        G_debug(1, "    write");
        /* t1 = clock(); */
        t1 = (double)time(NULL);
        nstat = 0;
        for (seg = 0; seg < nrseg; seg++) {
            nstat++;
            start = mp_multip * rseg[seg].start_mp + rseg[seg].start_off;
            end = mp_multip * rseg[seg].end_mp + rseg[seg].end_off;
            mp = start / mp_multip;
            sta = (start - mp * mp_multip) / sta_multip;
            station = mp * mp_multip + sta * sta_multip;
            G_debug(1, "      seg = %d length = %f", seg, end - start);

            while (station < end) {
                G_debug(2, "mp = %d sta = %d station = %f", mp, sta, station);

                G_debug(1, "      get offset");
                ret = LR_get_offset(rsdriver, table_opt->answer, "lcat", "lid",
                                    "start_map", "end_map", "start_mp",
                                    "start_off", "end_mp", "end_off",
                                    rseg[seg].lid, mp, sta * sta_multip,
                                    mp_multip, &lcat, &map_offset);
                /* G_debug(1, "      get offset time = %d", t2 - t1); */
                G_debug(1, "      get offset");

                if (ret == 0) {
                    G_warning(_("No record in LR table"));
                    break;
                }
                if (ret == -1) {
                    G_warning(_("More than one record in LR table"));
                    break;
                }
                G_debug(2, "map_offset = %f", map_offset);

                Vect_reset_cats(SCats);
                Vect_cat_set(SCats, 2, cat);

                /* Decide if MP or common stationing */
                if (sta == 0) { /* MP */
                    l_off = mp_l_off;
                    r_off = mp_r_off;
                    Vect_cat_set(SCats, 1, 1);
                }
                else {
                    l_off = sta_l_off;
                    r_off = sta_r_off;
                    Vect_cat_set(SCats, 1, 2);
                }

                Vect_point_on_line(LPoints, map_offset, &x, &y, &z, &angle,
                                   NULL);

                Vect_reset_line(SPoints);
                Vect_append_point(SPoints, x, y, 0);
                Vect_write_line(&Out, GV_POINT, SPoints, SCats);

                Vect_reset_line(SPoints);
                xs = x + l_off * cos(angle + PI / 2);
                ys = y + l_off * sin(angle + PI / 2);
                Vect_append_point(SPoints, xs, ys, 0);
                xs = x + r_off * cos(angle - PI / 2);
                ys = y + r_off * sin(angle - PI / 2);
                Vect_append_point(SPoints, xs, ys, 0);

                Vect_write_line(&Out, GV_LINE, SPoints, SCats);

                /* Create label */
                lab_x = x + lab_x_off * cos(angle - PI / 2);
                lab_y = y + lab_x_off * sin(angle - PI / 2);
                lab_x = lab_x + lab_y_off * cos(angle);
                lab_y = lab_y + lab_y_off * sin(angle);
                if (sta == 0 && labels != NULL) {
                    rotate = 360 * angle / 2 / PI - 90;

                    fprintf(labels, "east: %f\n", lab_x);
                    fprintf(labels, "north: %f\n", lab_y);
                    fprintf(labels, "xoffset: 0\n");
                    fprintf(labels, "yoffset: 0\n");
                    fprintf(labels, "ref: %s\n", Reference->answer);
                    fprintf(labels, "font: %s\n", Font->answer);
                    fprintf(labels, "color: %s\n", Color->answer);
                    fprintf(labels, "size: %s\n", Size->answer);
                    fprintf(labels, "width: %s\n", Width->answer);
                    fprintf(labels, "hcolor: %s\n", Hcolor->answer);
                    fprintf(labels, "hwidth: %s\n", Hwidth->answer);
                    fprintf(labels, "background: %s\n", Bcolor->answer);
                    fprintf(labels, "border: %s\n", Border->answer);
                    fprintf(labels, "opaque: %s\n", Opaque->answer);
                    fprintf(labels, "rotate: %f\n\n", rotate);
                    fprintf(labels, "text: %d+%.0f\n\n", mp, sta * mp_multip);
                }

                sta++;
                if (sta == (mp_multip / sta_multip)) {
                    mp++;
                    sta = 0;
                }
                station = mp * mp_multip + sta * sta_multip;
            }
        }
        /* t2 = clock(); */
        t2 = (double)time(NULL);
        dt = (t2 - t1) / nstat;
        G_debug(1, "    time / seg = %f, time = %f, nstat = %d", dt, t2 - t1,
                nstat);
        /* break; */
        /* debug */
    }

    db_close_database(rsdriver);

    Vect_copy_head_data(&In, &Out);
    Vect_hist_copy(&In, &Out);
    Vect_hist_command(&Out);

    Vect_build(&Out);

    /* Free, close ... */
    Vect_close(&In);
    Vect_close(&Out);

    exit(EXIT_SUCCESS);
}

int cmp_along(const void *pa, const void *pb)
{
    RSEGMENT *p1 = (RSEGMENT *)pa;
    RSEGMENT *p2 = (RSEGMENT *)pb;

    if (p1->start_map < p2->start_map)
        return -1;
    if (p1->start_map > p2->start_map)
        return 1;
    return 0;
}
