/****************************************************************
 *
 * MODULE:     v.net.timetable
 *
 * AUTHOR(S):  Daniel Bundala
 *
 * PURPOSE:    Routing with timetables
 *
 * COPYRIGHT:  (C) 2002-2005 by the GRASS Development Team
 *
 *             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 <grass/gis.h>
#include <grass/vector.h>
#include <grass/glocale.h>
#include <grass/neta.h>

struct Map_info In, Out;
neta_timetable_result result;
neta_timetable timetable;

struct segment {
    int from_stop, to_stop;
    int from_time, to_time;
    int route;
    struct segment *next;
} head;

double *stop_x, *stop_y, *stop_z;
int *stop_ids, *route_ids;
int *found, *stop_node, *edges, nnodes;
struct ilist **lines;
dglGraph_s *graph;

void init_route(int connection, int stop)
{
    if (result.prev_stop[connection][stop] == -1)
        return;
    struct segment *seg = (struct segment *)G_calloc(1, sizeof(struct segment));
    int prev_conn = result.prev_conn[connection][stop];

    seg->next = head.next;
    head.next = seg;
    seg->route = result.prev_route[connection][stop];
    seg->from_stop = result.prev_stop[connection][stop];
    seg->to_stop = stop;
    if (seg->route == -2)
        seg->from_time = result.dst[prev_conn][seg->from_stop];
    else
        seg->from_time = NetA_timetable_get_route_time(
            &timetable, seg->from_stop, seg->route);
    seg->to_time = result.dst[connection][stop];
    init_route(prev_conn, seg->from_stop);
}

void release_route(struct segment *seg)
{
    if (!seg)
        return;
    release_route(seg->next);
    G_free(seg->next);
}

static int int_cmp(const void *a, const void *b)
{
    return *(int *)a - *(int *)b;
}

void init_database(struct Map_info *Out, dbDriver **driver,
                   struct field_info **Fi, int layer, char *columns)
{
    dbString sql;
    char buf[2000];

    /* Create table */
    *Fi = Vect_default_field_info(Out, layer, NULL, GV_MTABLE);
    Vect_map_add_dblink(Out, layer, NULL, (*Fi)->table, GV_KEY_COLUMN,
                        (*Fi)->database, (*Fi)->driver);
    db_init_string(&sql);
    *driver = db_start_driver_open_database((*Fi)->driver, (*Fi)->database);
    if (*driver == NULL)
        G_fatal_error(_("Unable to open database <%s> by driver <%s>"),
                      (*Fi)->database, (*Fi)->driver);

    snprintf(buf, sizeof(buf), "create table %s (%s)", (*Fi)->table, columns);

    db_set_string(&sql, buf);
    G_debug(2, "%s", db_get_string(&sql));

    if (db_execute_immediate(*driver, &sql) != DB_OK) {
        db_close_database_shutdown_driver(*driver);
        G_fatal_error(_("Unable to create table: '%s'"), db_get_string(&sql));
    }

    if (db_create_index2(*driver, (*Fi)->table, GV_KEY_COLUMN) != DB_OK)
        G_warning(_("Cannot create index"));

    if (db_grant_on_table(*driver, (*Fi)->table, DB_PRIV_SELECT,
                          DB_GROUP | DB_PUBLIC) != DB_OK)
        G_fatal_error(_("Cannot grant privileges on table <%s>"), (*Fi)->table);

    db_free_string(&sql);
    db_begin_transaction(*driver);
}

void insert_point(dbDriver *driver, char *table, int cat, int path, int stop_id,
                  int index, int arrival_time, int departure_time)
{
    char buf[2000];
    dbString sql;

    db_init_string(&sql);

    snprintf(buf, sizeof(buf), "insert into %s values (%d, %d, %d, %d, %d, %d)",
             table, cat, path, stop_id, index, arrival_time, departure_time);
    db_set_string(&sql, buf);
    G_debug(3, "%s", db_get_string(&sql));
    if (db_execute_immediate(driver, &sql) != DB_OK) {
        db_close_database_shutdown_driver(driver);
        G_fatal_error(_("Cannot insert new record: %s"), db_get_string(&sql));
    };

    db_free_string(&sql);
}

void insert_line(dbDriver *driver, char *table, int cat, int path, int from_id,
                 int to_id, int route_id, int index, int from_time, int to_time)
{
    char buf[2000];
    dbString sql;

    db_init_string(&sql);

    snprintf(buf, sizeof(buf),
             "insert into %s values (%d, %d, %d, %d, %d, %d, %d, %d)", table,
             cat, path, from_id, to_id, route_id, index, from_time, to_time);
    db_set_string(&sql, buf);
    G_debug(3, "%s", db_get_string(&sql));
    if (db_execute_immediate(driver, &sql) != DB_OK) {
        db_close_database_shutdown_driver(driver);
        G_fatal_error(_("Cannot insert new record: %s"), db_get_string(&sql));
    };

    db_free_string(&sql);
}

int get_nearest_stop(double x, double y, double z, int with_z)
{
    int i, mini = -1;
    double mind = -1., d;

    for (i = 0; i < timetable.stops; i++) {
        if (!found[i])
            continue;
        d = Vect_points_distance(x, y, z, stop_x[i], stop_y[i], stop_z[i],
                                 with_z);
        if (mini == -1 || d < mind) {
            mind = d;
            mini = i;
        }
    }
    return mini;
}

void write_subroute(struct segment *seg, struct line_pnts *line, int line_id)
{
    int i, j, r;
    struct line_pnts *Points;
    struct line_cats *Cats;
    struct ilist *list;

    Cats = Vect_new_cats_struct();
    r = seg->route;

    Vect_cat_set(Cats, 2, line_id);

    if (r < 0) {
        Vect_write_line(&Out, GV_LINE, line, Cats);
        return;
    }

    Points = Vect_new_line_struct();
    list = Vect_new_list();

    for (i = 0; i < nnodes; i++)
        edges[i] = 0;
    for (i = 0; i < lines[r]->n_values; i++)
        edges[lines[r]->value[i]] = 1;

    for (i = 0; i < timetable.route_length[r]; i++)
        if (timetable.route_stops[r][i] == seg->from_stop)
            break;
    for (; timetable.route_stops[r][i] != seg->to_stop; i++)
        if (NetA_find_path(graph, stop_node[timetable.route_stops[r][i]],
                           stop_node[timetable.route_stops[r][i + 1]], edges,
                           list) != -1) {
            for (j = 0; j < list->n_values; j++) {
                int type = Vect_read_line(&In, Points, NULL, list->value[j]);

                Vect_write_line(&Out, type, Points, Cats);
            }
        }
        else {
            G_warning(_("Could not find a path between stops %d and %d"),
                      stop_ids[timetable.route_stops[r][i]],
                      stop_ids[timetable.route_stops[r][i + 1]]);
        }

    Vect_destroy_list(list);
    Vect_destroy_cats_struct(Cats);
    Vect_destroy_line_struct(Points);
}

int main(int argc, char *argv[])
{
    static struct line_pnts *Points, *Cur, *Prev;
    struct line_cats *Cats;
    struct GModule *module; /* GRASS module for parsing arguments */
    struct Option *map_in, *map_out;
    struct Option *tfield_opt, /* Input map: layer with existing timetable */
        *walk_layer_opt;       /* Input map: layer with existing walking routes
                                  between stops */

    struct Option *afield_opt, /* Input map: layer with arc costs */
        *nfield_opt,           /* Input map: layer with node costs */
        *afcol, *abcol, *ncol; /* Input map: cost columns */

    struct Option *route_id_opt, *stop_time_opt, *to_stop_opt, *walk_length_opt;
    int with_z;
    int tfield, mask_type, afield, nfield;
    int from_stop, to_stop, start_time, min_change, max_changes, walking_change,
        ret;
    int *stop_pnt, i, nlines, point_counter, *route_pnt;
    int line_counter, index, j;
    struct segment *cur;
    char buf[2000];

    /* Attribute table */
    dbDriver *point_driver, *line_driver;
    struct field_info *point_Fi, *line_Fi;

    /* initialize GIS environment */
    G_gisinit(
        argv[0]); /* reads grass env, stores program name to G_program_name() */

    /* initialize module */
    module = G_define_module();
    G_add_keyword(_("vector"));
    G_add_keyword(_("network"));
    G_add_keyword(_("shortest path"));
    module->description = _("Finds shortest path using timetables.");

    /* Define the different options as defined in gis.h */
    map_in = G_define_standard_option(G_OPT_V_INPUT);
    tfield_opt = G_define_standard_option(G_OPT_V_FIELD);

    map_out = G_define_standard_option(G_OPT_V_OUTPUT);

    afield_opt = G_define_standard_option(G_OPT_V_FIELD);
    afield_opt->key = "arc_layer";
    afield_opt->answer = "1";
    afield_opt->label = _("Arc layer");
    afield_opt->guisection = _("Cost");

    nfield_opt = G_define_standard_option(G_OPT_V_FIELD);
    nfield_opt->key = "node_layer";
    nfield_opt->answer = "2";
    nfield_opt->label = _("Node layer");
    nfield_opt->guisection = _("Cost");

    afcol = G_define_option();
    afcol->key = "arc_column";
    afcol->type = TYPE_STRING;
    afcol->required = NO;
    afcol->description =
        _("Arc forward/both direction(s) cost column (number)");
    afcol->guisection = _("Cost");

    abcol = G_define_option();
    abcol->key = "arc_backward_column";
    abcol->type = TYPE_STRING;
    abcol->required = NO;
    abcol->description = _("Arc backward direction cost column (number)");
    abcol->guisection = _("Cost");

    ncol = G_define_option();
    ncol->key = "node_column";
    ncol->type = TYPE_STRING;
    ncol->required = NO;
    ncol->description = _("Node cost column (number)");
    ncol->guisection = _("Cost");

    walk_layer_opt = G_define_standard_option(G_OPT_V_FIELD_ALL);
    walk_layer_opt->key = "walk_layer";
    walk_layer_opt->answer = "-1";
    walk_layer_opt->label =
        _("Layer number or name with walking connections or -1");

    route_id_opt = G_define_standard_option(G_OPT_DB_COLUMN);
    route_id_opt->key = "route_id";
    route_id_opt->required = YES;
    route_id_opt->answer = "route_id";
    route_id_opt->description = _("Name of column with route ids");

    stop_time_opt = G_define_standard_option(G_OPT_DB_COLUMN);
    stop_time_opt->key = "stop_time";
    stop_time_opt->required = YES;
    stop_time_opt->answer = "stop_time";
    stop_time_opt->description = _("Name of column with stop timestamps");

    to_stop_opt = G_define_standard_option(G_OPT_DB_COLUMN);
    to_stop_opt->key = "to_stop";
    to_stop_opt->required = YES;
    to_stop_opt->answer = "to_stop";
    to_stop_opt->description = _("Name of column with stop ids");

    walk_length_opt = G_define_standard_option(G_OPT_DB_COLUMN);
    walk_length_opt->key = "walk_length";
    walk_length_opt->required = YES;
    walk_length_opt->answer = "length";
    walk_length_opt->description = _("Name of column with walk lengths");

    /* options and flags parser */
    if (G_parser(argc, argv))
        exit(EXIT_FAILURE);
    /* TODO: make an option for this */
    mask_type = GV_LINE | GV_BOUNDARY;

    Points = Vect_new_line_struct();
    Cats = Vect_new_cats_struct();
    Cur = Vect_new_line_struct();
    Prev = Vect_new_line_struct();

    Vect_check_input_output_name(map_in->answer, map_out->answer, G_FATAL_EXIT);

    Vect_set_open_level(2);

    if (1 > Vect_open_old(&In, map_in->answer, ""))
        G_fatal_error(_("Unable to open vector map <%s>"), map_in->answer);

    with_z = Vect_is_3d(&In);

    if (0 > Vect_open_new(&Out, map_out->answer, with_z)) {
        Vect_close(&In);
        G_fatal_error(_("Unable to create vector map <%s>"), map_out->answer);
    }

    /* parse filter option and select appropriate lines */
    tfield = atoi(tfield_opt->answer);
    afield = atoi(afield_opt->answer);
    nfield = atoi(nfield_opt->answer);

    init_database(&Out, &point_driver, &point_Fi, 1,
                  "cat integer, path_id integer, stop_id integer, index "
                  "integer, arr_time integer, dep_time integer");
    init_database(
        &Out, &line_driver, &line_Fi, 2,
        "cat integer, path_id integer, from_id integer, to_id integer, "
        "route_id integer, index integer, from_time integer, to_time integer");

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

    if (NetA_init_timetable_from_db(
            &In, tfield, atoi(walk_layer_opt->answer), route_id_opt->answer,
            stop_time_opt->answer, to_stop_opt->answer, walk_length_opt->answer,
            &timetable, &route_ids, &stop_ids) != 0)
        G_fatal_error(_("Could not initialize the timetables"));

    stop_x = (double *)G_calloc(timetable.stops, sizeof(double));
    stop_y = (double *)G_calloc(timetable.stops, sizeof(double));
    stop_z = (double *)G_calloc(timetable.stops, sizeof(double));
    found = (int *)G_calloc(timetable.stops, sizeof(int));

    if (!stop_x || !stop_y || !stop_z || !found)
        G_fatal_error(_("Out of memory"));

    if (afield > 0) {
        nnodes = Vect_get_num_nodes(&In);
        stop_node = (int *)G_calloc(timetable.stops, sizeof(int));
        lines =
            (struct ilist **)G_calloc(timetable.routes, sizeof(struct ilist *));
        edges = (int *)G_calloc(nnodes + 1, sizeof(int));
        if (!edges || !stop_node || !lines)
            G_fatal_error(_("Out of memory"));
        for (i = 0; i < timetable.routes; i++)
            lines[i] = Vect_new_list();

        if (0 != Vect_net_build_graph(&In, mask_type, afield, nfield,
                                      afcol->answer, abcol->answer,
                                      ncol->answer, 0, 0))
            G_fatal_error(_("Unable to build graph for vector map <%s>"),
                          Vect_get_full_name(&In));

        graph = Vect_net_get_graph(&In);
    }

    nlines = Vect_get_num_lines(&In);
    for (i = 1; i <= nlines; i++) {
        int type = Vect_read_line(&In, Points, Cats, i);

        if (type == GV_POINT) {
            int cat, stop, node;

            for (j = 0; j < Cats->n_cats; j++) {
                if (Cats->field[j] != tfield)
                    continue;
                cat = Cats->cat[j];
                stop_pnt = (int *)bsearch(&cat, stop_ids, timetable.stops,
                                          sizeof(int), int_cmp);
                if (!stop_pnt)
                    continue;

                stop = stop_pnt - stop_ids;
                stop_x[stop] = Points->x[0];
                stop_y[stop] = Points->y[0];
                stop_z[stop] = Points->z[0];
                if (afield > 0) {
                    node = Vect_find_node(&In, Points->x[0], Points->y[0],
                                          Points->z[0], 0, 0);
                    if (!stop_node[stop] && node > 0)
                        stop_node[stop] = node;
                }
                found[stop] = 1;
            }
        }
        else if (type == GV_LINE && afield > 0) {
            int cat;

            for (j = 0; j < Cats->n_cats; j++) {
                if (Cats->field[j] != afield)
                    continue;
                cat = Cats->cat[j];
                route_pnt = (int *)bsearch(&cat, route_ids, timetable.routes,
                                           sizeof(int), int_cmp);
                if (!route_pnt)
                    continue;
                Vect_list_append(lines[route_pnt - route_ids], i);
            }
        }
    }

    for (i = 0; i < timetable.stops; i++)
        if (!found[i])
            G_warning(_("No stop with category: %d"), stop_ids[i]);

    point_counter = line_counter = 1;
    while (1) {
        double fx, fy, tx, ty;
        int path_id;

        if (fgets(buf, sizeof(buf), stdin) == NULL)
            break;
        ret = sscanf(buf, "%d %lf %lf %lf %lf %d %d %d %d", &path_id, &fx, &fy,
                     &tx, &ty, &start_time, &min_change, &max_changes,
                     &walking_change);
        if (ret == 9) {
            from_stop = get_nearest_stop(fx, fy, 0, with_z);
            to_stop = get_nearest_stop(tx, ty, 0, with_z);
        }
        else {
            ret = sscanf(buf, "%d %d %d %d %d %d %d", &path_id, &from_stop,
                         &to_stop, &start_time, &min_change, &max_changes,
                         &walking_change);
            if (ret < 7) {
                G_warning(_("Wrong input format: %s"), buf);
                continue;
            }

            stop_pnt = (int *)bsearch(&from_stop, stop_ids, timetable.stops,
                                      sizeof(int), int_cmp);
            if (!stop_pnt) {
                G_warning(_("No stop with category: %d"), from_stop);
                continue;
            }
            from_stop = stop_pnt - stop_ids;
            stop_pnt = (int *)bsearch(&to_stop, stop_ids, timetable.stops,
                                      sizeof(int), int_cmp);
            if (!stop_pnt) {
                G_warning(_("No stop with category: %d"), to_stop);
                continue;
            }
            to_stop = stop_pnt - stop_ids;
        }

        if (from_stop == to_stop) {
            G_warning(_("'From' and 'To' stops are the same"));
            continue;
        }

        ret = NetA_timetable_shortest_path(&timetable, from_stop, to_stop,
                                           start_time, min_change, max_changes,
                                           walking_change, &result);
        if (ret == -1) {
            G_warning(_("No path between the stops"));
            continue;
        }
        head.next = NULL;
        init_route(result.routes, to_stop);
        NetA_timetable_result_release(&result);

        Vect_reset_line(Points);
        Vect_reset_line(Cur);
        Vect_reset_line(Prev);
        Vect_append_point(Cur, stop_x[from_stop], stop_y[from_stop],
                          stop_z[from_stop]);

        Vect_reset_cats(Cats);
        Vect_cat_set(Cats, 1, point_counter);
        Vect_write_line(&Out, GV_POINT, Cur, Cats);
        insert_point(point_driver, point_Fi->table, point_counter, path_id,
                     stop_ids[from_stop], 1, start_time, head.next->from_time);
        point_counter++;
        Vect_append_points(Prev, Cur, GV_FORWARD);
        index = 1;
        for (cur = head.next; cur; cur = cur->next) {
            int dept_time, route_id;

            if (cur->route == -2) {
                printf("Walk ");
                route_id = -1;
            }
            else {
                printf("Route %d, ", route_ids[cur->route]);
                route_id = route_ids[cur->route];
            }
            printf("from %d leaving at %d arriving to %d at %d\n",
                   stop_ids[cur->from_stop], cur->from_time,
                   stop_ids[cur->to_stop], cur->to_time);

            Vect_reset_line(Cur);
            Vect_reset_line(Points);
            Vect_reset_cats(Cats);

            Vect_append_point(Cur, stop_x[cur->to_stop], stop_y[cur->to_stop],
                              stop_z[cur->to_stop]);

            Vect_cat_set(Cats, 1, point_counter);
            Vect_write_line(&Out, GV_POINT, Cur, Cats);
            if (cur->next)
                dept_time = cur->next->from_time;
            else
                dept_time = cur->to_time;
            insert_point(point_driver, point_Fi->table, point_counter, path_id,
                         stop_ids[cur->to_stop], index + 1, cur->to_time,
                         dept_time);

            Vect_append_points(Points, Prev, GV_FORWARD);
            Vect_append_points(Points, Cur, GV_FORWARD);
            Vect_reset_cats(Cats);
            Vect_cat_set(Cats, 2, line_counter);
            if (afield <= 0)
                Vect_write_line(&Out, GV_LINE, Points, Cats);
            else
                write_subroute(cur, Points, line_counter);
            insert_line(line_driver, line_Fi->table, line_counter, path_id,
                        stop_ids[cur->from_stop], stop_ids[cur->to_stop],
                        route_id, index, cur->from_time, cur->to_time);

            Vect_reset_line(Prev);
            Vect_append_points(Prev, Cur, GV_FORWARD);

            point_counter++;
            line_counter++;
            index++;
        }
        release_route(&head);
    }
    db_commit_transaction(line_driver);
    db_commit_transaction(point_driver);
    db_close_database_shutdown_driver(line_driver);
    db_close_database_shutdown_driver(point_driver);

    Vect_build(&Out);

    Vect_close(&In);
    Vect_close(&Out);

    G_free(stop_x);
    G_free(stop_y);
    G_free(stop_z);
    G_free(stop_node);

    exit(EXIT_SUCCESS);
}
