#include <string.h>

#include <grass/gis.h>
#include <grass/vector.h>
#include <grass/dbmi.h>
#include <grass/gprojects.h>
#include <grass/glocale.h>

#ifdef HAVE_OGR
#include "ogr_api.h"
#endif
#ifdef HAVE_POSTGRES
#include <libpq-fe.h>
#endif
#include "local_proto.h"

static int cmp(const void *, const void *);
static char **format_list(int *, size_t *);
static char *feature_type(const char *);

#ifdef HAVE_OGR
static int list_layers_ogr(FILE *, const char *, char **, int);
#endif /* HAVE_OGR */
#ifdef HAVE_POSTGRES
static int list_layers_pg(FILE *, const char *, char **, int);
#endif /* HAVE_POSTGRES */

int cmp(const void *a, const void *b)
{
    return strcmp(*(char **)a, *(char **)b);
}

char **format_list(int *count, size_t *len)
{
    int i;
    char **list;

    list = NULL;
    *count = 0;
    if (len)
        *len = 0;

#ifdef HAVE_OGR
    char buf[2000];

    OGRSFDriverH Ogr_driver;

    /* Open OGR DSN */
    OGRRegisterAll();
    G_debug(2, "driver count = %d", OGRGetDriverCount());
    for (i = 0; i < OGRGetDriverCount(); i++) {
        Ogr_driver = OGRGetDriver(i);
        G_debug(2, "driver %d/%d : %s", i, OGRGetDriverCount(),
                OGR_Dr_GetName(Ogr_driver));

        list = G_realloc(list, ((*count) + 1) * sizeof(char *));

        /* chg white space to underscore in OGR driver names */
        sprintf(buf, "%s", OGR_Dr_GetName(Ogr_driver));
        G_strchg(buf, ' ', '_');
        list[(*count)++] = G_store(buf);
        if (len)
            *len += strlen(buf) + 1; /* + ',' */
    }

    /* order formats by name */
    qsort(list, *count, sizeof(char *), cmp);
#endif
#if defined HAVE_POSTGRES && !defined HAVE_OGR
    list = G_realloc(list, ((*count) + 1) * sizeof(char *));
    list[(*count)++] = G_store("PostgreSQL");
    if (len)
        *len += strlen("PostgreSQL") + 1;
#endif

    return list;
}

char *feature_type(const char *ftype)
{
    char *ftype_ret;

    ftype_ret = G_str_replace(ftype, " ", "");
    G_str_to_lower(ftype_ret);

    /* let's OS to release the memory */
    return ftype_ret;
}

void list_formats(void)
{
    int i, count;
    char **list;

    G_message(_("Supported formats:"));

    list = format_list(&count, NULL);

    for (i = 0; i < count; i++)
        fprintf(stdout, "%s\n", list[i]);
    fflush(stdout);

    G_free(list);
}

int list_layers(FILE *fd, const char *dsn, char **layer, int print_types,
                int use_ogr)
{
    if (!use_ogr) {
#ifdef HAVE_POSTGRES
        return list_layers_pg(fd, dsn, layer, print_types);
#else
        G_fatal_error(_("GRASS is not compiled with PostgreSQL support"));
#endif
    }
#ifdef HAVE_OGR
    return list_layers_ogr(fd, dsn, layer, print_types);
#else
    G_fatal_error(_("GRASS is not compiled with OGR support"));
#endif

    return -1;
}

void get_table_name(const char *table, char **table_name, char **schema_name)
{
    char **tokens;

    tokens = G_tokenize(table, ".");

    if (G_number_of_tokens(tokens) > 1) {
        *schema_name = G_store(tokens[0]);
        *table_name = G_store(tokens[1]);
    }
    else {
        *schema_name = NULL;
        *table_name = G_store(tokens[0]);
    }

    G_free_tokens(tokens);
}

#ifdef HAVE_POSTGRES
int list_layers_pg(FILE *fd, const char *conninfo, char **table,
                   int print_types)
{
    int row, ntables, ret, print_schema;
    char *value_schema, *value_table, *value_type, *value_column;
    char *schema_name, *table_name;

    PGconn *conn;
    PGresult *res;

    dbString sql;

    ret = -1;

    conn = PQconnectdb(conninfo);
    G_debug(1, "PQconnectdb(): %s", conninfo);
    if (PQstatus(conn) == CONNECTION_BAD)
        G_fatal_error("%s\n%s", _("Connection to PostgreSQL database failed."),
                      PQerrorMessage(conn));

    db_init_string(&sql);
    db_set_string(
        &sql, "SELECT f_table_schema, f_table_name, f_geometry_column, type "
              "FROM geometry_columns ORDER BY "
              "f_table_schema, f_table_name");
    G_debug(2, "SQL: %s", db_get_string(&sql));
    res = PQexec(conn, db_get_string(&sql));
    if (PQresultStatus(res) != PGRES_TUPLES_OK)
        G_fatal_error("%s\n%s", _("No feature tables found in database."),
                      PQresultErrorMessage(res));

    /* get schema & table_name */
    table_name = schema_name = NULL;
    if (table)
        get_table_name(*table, &table_name, &schema_name);

    ntables = PQntuples(res);
    G_debug(3, "   nrows = %d", ntables);
    if (fd)
        G_message(
            n_("PostGIS database <%s> contains %d feature table:",
               "PostGIS database <%s> contains %d feature tables:", ntables),
            PQdb(conn), ntables);

    /* report also schemas */
    print_schema = FALSE;
    if (fd) {
        for (row = 0; row < ntables; row++) {
            value_schema = PQgetvalue(res, row, 0);
            if (G_strcasecmp(value_schema, "public") != 0) {
                print_schema = TRUE;
                break;
            }
        }
    }

    /* report layers */
    for (row = 0; row < ntables; row++) {
        value_schema = PQgetvalue(res, row, 0);
        value_table = PQgetvalue(res, row, 1);
        value_column = PQgetvalue(res, row, 2);
        value_type = PQgetvalue(res, row, 3);

        if (fd) {
            if (print_types) {
                if (print_schema && G_strcasecmp(value_schema, "public") != 0)
                    fprintf(fd, "%s.", value_schema);

                fprintf(fd, "%s,%s,0,%s\n", value_table,
                        feature_type(value_type), value_column);
            }
            else {
                if (print_schema && G_strcasecmp(value_schema, "public") != 0)
                    fprintf(fd, "%s.%s\n", value_schema, value_table);
                else
                    fprintf(fd, "%s\n", value_table);
            }
        }
        if ((!schema_name || strcmp(value_schema, schema_name) == 0) &&
            table_name && strcmp(value_table, table_name) == 0) {
            ret = row;
        }
    }

    if (table_name)
        G_free(table_name);
    if (schema_name)
        G_free(schema_name);

    PQclear(res);
    PQfinish(conn);
    G_debug(1, "PQfinish()");

    return ret;
}
#endif /* HAVE_POSTGRES */

#ifdef HAVE_OGR
int list_layers_ogr(FILE *fd, const char *dsn, char **layer, int print_types)
{
    int i, ret;
    int nlayers;
    char *layer_name;

    struct Key_Value *loc_proj_info, *loc_proj_units;
    struct Key_Value *proj_info, *proj_units;
    struct Cell_head loc_wind;

    OGRDataSourceH Ogr_ds;
    OGRLayerH Ogr_layer;
    OGRFeatureDefnH Ogr_featuredefn;
    OGRwkbGeometryType Ogr_geom_type;

    ret = -1;
    loc_proj_info = loc_proj_units = NULL;
    proj_info = proj_units = NULL;

    /* open OGR DSN */
    Ogr_ds = OGROpen(dsn, FALSE, NULL);
    if (!Ogr_ds) {
        G_fatal_error(_("Unable to open data source '%s'"), dsn);
    }

    /* Make a list of available layers */
    nlayers = OGR_DS_GetLayerCount(Ogr_ds);

    if (fd) {
        G_message(
            n_("Data source <%s> (format '%s') contains %d layer:",
               "Data source <%s> (format '%s') contains %d layers:", nlayers),
            dsn, OGR_Dr_GetName(OGR_DS_GetDriver(Ogr_ds)), nlayers);
    }
    else if (layer && !(*layer)) {
        /* return first layer by default (if layer not defined) */
        if (nlayers > 0) {
            Ogr_layer = OGR_DS_GetLayer(Ogr_ds, 0);
            Ogr_featuredefn = OGR_L_GetLayerDefn(Ogr_layer);
            *layer = G_store((char *)OGR_FD_GetName(Ogr_featuredefn));
            return 0;
        }
        return -1;
    }

    G_get_default_window(&loc_wind);
    if (print_types && loc_wind.proj != PROJECTION_XY) {
        loc_proj_info = G_get_projinfo();
        loc_proj_units = G_get_projunits();
    }

    for (i = 0; i < nlayers; i++) {
        Ogr_layer = OGR_DS_GetLayer(Ogr_ds, i);
        Ogr_featuredefn = OGR_L_GetLayerDefn(Ogr_layer);
        layer_name = (char *)OGR_FD_GetName(Ogr_featuredefn);

        if (fd) {
            if (print_types) {
                int proj_same, igeom;
                OGRSpatialReferenceH Ogr_projection;

                OGRGeomFieldDefnH Ogr_geomdefn;
                /* projection check */
                Ogr_projection = OGR_L_GetSpatialRef(Ogr_layer);
                proj_same = 0;
                G_suppress_warnings(TRUE);
                if (GPJ_osr_to_grass(&loc_wind, &proj_info, &proj_units,
                                     Ogr_projection, 0) < 0) {
                    G_warning(
                        _("Unable to convert input map projection to GRASS "
                          "format. Projection check cannot be provided for "
                          "OGR layer <%s>"),
                        layer_name);
                }
                else {
                    if (TRUE == G_compare_projections(loc_proj_info,
                                                      loc_proj_units, proj_info,
                                                      proj_units))
                        proj_same = 1;
                    else
                        proj_same = 0;
                }
                G_suppress_warnings(FALSE);
                for (igeom = 0;
                     igeom < OGR_FD_GetGeomFieldCount(Ogr_featuredefn);
                     igeom++) {
                    Ogr_geomdefn =
                        OGR_FD_GetGeomFieldDefn(Ogr_featuredefn, igeom);
                    if (!Ogr_geomdefn) {
                        G_warning(_("Invalid geometry column %d"), igeom);
                        continue;
                    }

                    Ogr_geom_type = OGR_GFld_GetType(Ogr_geomdefn);
                    fprintf(fd, "%s,%s,%d,%s\n", layer_name,
                            feature_type(OGRGeometryTypeToName(Ogr_geom_type)),
                            proj_same, OGR_GFld_GetNameRef(Ogr_geomdefn));
                }
            }
            else {
                fprintf(fd, "%s\n", layer_name);
            }
        }
        if (layer)
            if (strcmp(layer_name, *layer) == 0) {
                ret = i;
            }
    }

    OGR_DS_Destroy(Ogr_ds);

    return ret;
}
#endif /* HAVE_OGR */
