/**********************************************************************
 *
 * PostGIS - Spatial Types for PostgreSQL
 * http://www.postgis.org
 * Copyright 2008 OpenGeo.org
 * Copyright 2009 Mark Cave-Ayland <mark.cave-ayland@siriusit.co.uk>
 *
 * This is free software; you can redistribute and/or modify it under
 * the terms of the GNU General Public Licence. See the COPYING file.
 *
 * Maintainer: Paul Ramsey <pramsey@opengeo.org>
 *
 **********************************************************************/

#include "../postgis_config.h"

#include "shp2pgsql-core.h"
#include "../liblwgeom/liblwgeom.h" /* for SRID_UNKNOWN */

#define xstr(s) str(s)
#define str(s) #s

typedef struct {
	const char *name;
	int short_option;
	int takes_value;
} ShpLoaderLongOption;

static const ShpLoaderLongOption long_option_aliases[] = {
    {"column-map", 'm', 1},
    {"create-index", 'I', 0},
    {"dbf-only", 'n', 0},
    {"dimensionality", 't', 1},
    {"dump-format", 'D', 0},
    {"encoding", 'W', 1},
    {"feature-id-column", 'f', 1},
    {"force-int4", 'i', 0},
    {"geography", 'G', 0},
    {"geometry-column", 'g', 1},
    {"help", '?', 0},
    {"index-tablespace", 'X', 1},
    {"keep-case", 'k', 0},
    {"no-analyze", 'Z', 0},
    {"no-transaction", 'e', 0},
    {"null-policy", 'N', 1},
    {"simple-geometries", 'S', 0},
    {"srid", 's', 1},
    {"tablespace", 'T', 1},
    {"unlogged", 'u', 0},
    {"use-wkt", 'w', 0},
};

static int
consume_long_option(int argc, char **argv, int *option)
{
	char *arg = argv[pgis_optind];
	char *equals = strchr(arg, '=');
	size_t name_len = equals ? (size_t)(equals - arg - 2) : strlen(arg + 2);
	size_t i;

	if (strncmp(arg, "--", 2) != 0)
		return 0;

	for (i = 0; i < sizeof(long_option_aliases) / sizeof(long_option_aliases[0]); i++)
	{
		const ShpLoaderLongOption *alias = &long_option_aliases[i];
		if (strlen(alias->name) != name_len || strncmp(arg + 2, alias->name, name_len) != 0)
			continue;

		if (alias->takes_value)
		{
			if (equals)
			{
				pgis_optarg = equals + 1;
				pgis_optind++;
			}
			else if (pgis_optind + 1 < argc)
			{
				pgis_optarg = argv[pgis_optind + 1];
				pgis_optind += 2;
			}
			else
			{
				fprintf(stderr, "Option requires an argument: --%s\n", alias->name);
				exit(1);
			}
		}
		else
		{
			if (equals)
			{
				fprintf(stderr, "Option does not take an argument: --%s\n", alias->name);
				exit(1);
			}
			pgis_optarg = NULL;
			pgis_optind++;
		}

		*option = alias->short_option;
		return 1;
	}

	return 0;
}

static void
usage()
{
	printf(_( "RELEASE: %s (%s)\n" ),
		POSTGIS_LIB_VERSION, xstr(POSTGIS_REVISION));
	printf(
	    _("USAGE: shp2pgsql [<options>] <shapefile> [[<schema>.]<table>]\n"
	      "OPTIONS:\n"
	      "  Long options with values also accept --option=value.\n"));
	printf(_("  -s, --srid [<from>:]<srid> Set the SRID field. Defaults to %d.\n"
		 "      Optionally reprojects from given SRID.\n"),
	       SRID_UNKNOWN);
	printf(_( " (-d|a|c|p) These are mutually exclusive options:\n"
	          "     -d  Drops the table, then recreates it and populates\n"
	          "         it with current shape file data.\n"
	          "     -a  Appends shape file into current table, must be\n"
	          "         exactly the same table schema.\n"
	          "     -c  Creates a new table and populates it, this is the\n"
	          "         default if you do not specify any options.\n"
	          "     -p  Prepare mode, only creates the table.\n" ));
	printf(
	    _("  -g, --geometry-column <geocolumn> Specify the name of the geometry/geography column\n"
	      "      (mostly useful in append mode).\n"));
	printf(
	    _("  -f, --feature-id-column <fidcolumn> Specify the name of the feature id column\n"
	      "      (default: \"" FID_DEFAULT "\").\n"));
	printf(_("  -D, --dump-format  Use postgresql dump format (defaults to SQL insert statements).\n"));
	printf(_( "  -e  Execute each statement individually, do not use a transaction.\n"
	          "      Not compatible with -D.\n" ));
	printf(_("  -G, --geography  Use geography type (requires lon/lat data or -s to reproject).\n"));
	printf(_("  -k, --keep-case  Keep postgresql identifiers case.\n"));
	printf(_("  -i, --force-int4  Use int4 type for all integer dbf fields.\n"));
	printf(_( "  -I  Create a spatial index on the geocolumn.\n" ));
	printf(_("  --create-index  Create a spatial index on the geocolumn.\n"));
	printf(_("  -u, --unlogged  Create the table as UNLOGGED.\n"));
	printf(
	    _("  -m, --column-map <filename>  Specify a file containing a set of mappings of (long) column\n"
	      "     names to 10 character DBF column names. The content of the file is one or\n"
	      "     more lines of two names separated by white space and no trailing or\n"
	      "     leading space. For example:\n"
	      "         COLUMNNAME DBFFIELD1\n"
	      "         AVERYLONGCOLUMNNAME DBFFIELD2\n"));
	printf(_("  -S, --simple-geometries  Generate simple geometries instead of MULTI geometries.\n"));
	printf(_("  -t, --dimensionality <dimensionality> Force geometry to be one of '2D', '3DZ', '3DM', or '4D'\n"));

	printf(
	    _("  -w, --use-wkt  Output WKT instead of WKB.  Note that this can result in\n"
	      "      coordinate drift.\n"));
	printf(
	    _("  -W, --encoding <encoding> Specify the character encoding of Shape's\n"
	      "      attribute column. (default: \"UTF-8\")\n"));
	printf(_("  -N, --null-policy <policy> NULL geometries handling policy (insert*,skip,abort).\n"));
	printf(_("  -n, --dbf-only  Only import DBF file.\n"));
	printf(_("  --drop-table  Drop the target table before the selected actions.\n"));
	printf(_("  --create-table  Create the target table.\n"));
	printf(_("  --if-not-exists  Make active creation actions use IF NOT EXISTS.\n"));
	printf(_("  --load-data  Load shapefile rows into the target table.\n"));
	printf(
	    _("  -T, --tablespace <tablespace> Specify the tablespace for the new table.\n"
	      "      Note that indexes will still use the default tablespace unless the\n"
	      "      -X flag is also used.\n"));
	printf(
	    _("  -X, --index-tablespace <tablespace> Specify the tablespace for the table's indexes.\n"
	      "      This applies to the primary key, and the spatial index if\n"
	      "      the -I flag is used.\n"));
	printf(_( "  -Z  Prevent tables from being analyzed.\n" ));
	printf(_("  --analyze  Analyze the table after loading.\n"));
	printf(_("  --no-analyze  Prevent tables from being analyzed.\n"));
	printf(_("  --no-transaction  Execute each statement individually.\n"));
	printf(_("  -?, --help  Display this help screen.\n"));
	printf( "\n" );
	printf(_( "  An argument of `--' disables further option processing.\n" ));
	printf(_( "  (useful for unusual file names starting with '-')\n" ));
}

int
main (int argc, char **argv)
{
	SHPLOADERCONFIG *config;
	SHPLOADERSTATE *state;
	char *header, *footer, *record;
	int c;
	int ret, i;

#ifdef ENABLE_NLS
#ifdef PGSQL_LOCALEDIR
	setlocale (LC_ALL, "");
	bindtextdomain (PACKAGE, PGSQL_LOCALEDIR);
	textdomain (PACKAGE);
#endif
#endif

	/* If no options are specified, display usage */
	if (argc == 1)
	{
		usage();
		exit(0);
	}

	/* Parse command line options and set configuration */
	config = malloc(sizeof(SHPLOADERCONFIG));
	set_loader_config_defaults(config);

	/* Keep the flag list alphabetic so it's easy to see what's left. */
	while (pgis_optind < argc)
	{
		if (strcmp(argv[pgis_optind], "--") == 0)
		{
			pgis_optind++;
			break;
		}

		if (strcmp(argv[pgis_optind], "--drop-table") == 0)
		{
			config->actions.drop_table = 1;
			pgis_optind++;
			continue;
		}
		if (strcmp(argv[pgis_optind], "--create-table") == 0)
		{
			if (!loader_action_set_create_mode(
				&config->actions.create_table, &config->actions.create_table_set, LOADER_CREATE_ALWAYS))
			{
				fprintf(stderr,
					"Invalid argument combination - conflicting table creation semantics\n");
				exit(1);
			}
			pgis_optind++;
			continue;
		}
		if (strcmp(argv[pgis_optind], "--load-data") == 0)
		{
			config->actions.load_data = 1;
			config->actions.load_data_set = 1;
			pgis_optind++;
			continue;
		}
		if (strcmp(argv[pgis_optind], "--create-index") == 0)
		{
			if (!loader_action_set_create_mode(
				&config->actions.create_index, &config->actions.create_index_set, LOADER_CREATE_ALWAYS))
			{
				fprintf(stderr,
					"Invalid argument combination - conflicting index creation semantics\n");
				exit(1);
			}
			pgis_optind++;
			continue;
		}
		if (strcmp(argv[pgis_optind], "--if-not-exists") == 0)
		{
			config->actions.if_not_exists = 1;
			pgis_optind++;
			continue;
		}
		if (strcmp(argv[pgis_optind], "--analyze") == 0)
		{
			config->analyze = 1;
			pgis_optind++;
			continue;
		}
		if (strcmp(argv[pgis_optind], "--no-analyze") == 0)
		{
			config->analyze = 0;
			pgis_optind++;
			continue;
		}
		if (strcmp(argv[pgis_optind], "--no-transaction") == 0)
		{
			config->usetransaction = 0;
			pgis_optind++;
			continue;
		}
		if (strncmp(argv[pgis_optind], "--", 2) == 0)
		{
			if (!consume_long_option(argc, argv, &c))
			{
				fprintf(stderr, "Unknown option: %s\n", argv[pgis_optind]);
				usage();
				exit(1);
			}
		}
		else
		{
			c = pgis_getopt(argc, argv, "-?acdef:g:ikm:nps:t:uwDGIN:ST:W:X:Z");
			if (c == EOF)
				break;

			// can not do this inside the switch case
			if ('-' == c)
				break;
		}

		switch (c)
		{
		case 'c':
		case 'd':
		case 'a':
		case 'p':
			config->actions.mode = c;
			config->actions.mode_set = 1;
			break;

		case 'D':
			config->dump_format = 1;
			break;

		case 'G':
			config->geography = 1;
			break;

		case 'Z':
			config->analyze = 0;
			break;

		case 'S':
			config->simple_geometries = 1;
			break;

		case 's':
			if (pgis_optarg)
			{
				char *ptr = strchr(pgis_optarg, ':');
				if (ptr)
				{
					*ptr++ = '\0';
					sscanf(pgis_optarg, "%d", &config->shp_sr_id);
					sscanf(ptr, "%d", &config->sr_id);
				}
				else
				{
					/* Only TO_SRID specified */
					sscanf(pgis_optarg, "%d", &config->sr_id);
				}
			}
			else
			{
				/* With -s, user must specify TO_SRID or FROM_SRID:TO_SRID */
				fprintf(stderr, "The -s parameter must be specified in the form [FROM_SRID:]TO_SRID\n");
				exit(1);
			}
			break;
		case 'g':
			config->geo_col = pgis_optarg;
			break;
		case 'f':
			config->fid_col = pgis_optarg;
			break;
		case 'm':
			config->column_map_filename = pgis_optarg;
			break;

		case 'k':
			config->quoteidentifiers = 1;
			break;

		case 'i':
			config->forceint4 = 1;
			break;

		case 'I':
			if (!loader_action_set_create_mode(
				&config->actions.create_index, &config->actions.create_index_set, LOADER_CREATE_ALWAYS))
			{
				fprintf(stderr,
					"Invalid argument combination - conflicting index creation semantics\n");
				exit(1);
			}
			break;

		case 'u':
			config->unlogged = 1;
			break;

		case 'w':
			config->use_wkt = 1;
			break;

		case 'n':
			config->readshape = 0;
			break;

		case 'W':
			free(config->encoding);
			config->encoding = strdup(pgis_optarg);
			break;

		case 'N':
			switch (pgis_optarg[0])
			{
			case 'a':
				config->null_policy = POLICY_NULL_ABORT;
				break;
			case 'i':
				config->null_policy = POLICY_NULL_INSERT;
				break;
			case 's':
				config->null_policy = POLICY_NULL_SKIP;
				break;
			default:
				fprintf(stderr, "Unsupported NULL geometry handling policy.\nValid policies: insert, skip, abort\n");
				exit(1);
			}
			break;

		case 't':
			if (strcasecmp(pgis_optarg, "2D") == 0)
			{
				config->force_output = FORCE_OUTPUT_2D;
			}
			else if (strcasecmp(pgis_optarg, "3DZ") == 0 )
			{
				config->force_output = FORCE_OUTPUT_3DZ;
			}
			else if (strcasecmp(pgis_optarg, "3DM") == 0 )
			{
				config->force_output = FORCE_OUTPUT_3DM;
			}
			else if (strcasecmp(pgis_optarg, "4D") == 0 )
			{
				config->force_output = FORCE_OUTPUT_4D;
			}
			else
			{
				fprintf(stderr, "Unsupported output type: %s\nValid output types are 2D, 3DZ, 3DM and 4D\n", pgis_optarg);
				exit(1);
			}
			break;

		case 'T':
			config->tablespace = pgis_optarg;
			break;

		case 'X':
			config->idxtablespace = pgis_optarg;
			break;

		case 'e':
			config->usetransaction = 0;
			break;

		case '?':
			usage();
			exit(0);

		default:
			usage();
			exit(0);
		}
	}

	/* Once we have parsed the arguments, make sure certain combinations are valid */
	if (config->dump_format && !config->usetransaction)
	{
		fprintf(stderr, "Invalid argument combination - cannot use both -D and -e\n");
		exit(1);
	}

	{
		const int has_table_creation =
		    config->actions.create_table_set
			? config->actions.create_table != LOADER_CREATE_NONE
			: config->actions.mode != 'a';
		const int has_index_creation =
		    config->actions.create_index_set && config->actions.create_index == LOADER_CREATE_ALWAYS;
		const int has_load_data =
		    config->actions.load_data_set ? config->actions.load_data : config->actions.mode != 'p';

		if (config->actions.if_not_exists && !has_table_creation && !has_index_creation)
		{
			fprintf(stderr, "--if-not-exists requires an active creation action\n");
			exit(1);
		}

		if (config->actions.drop_table && has_load_data && !has_table_creation)
		{
			fprintf(stderr, "Invalid argument combination - --drop-table with load data requires --create-table\n");
			exit(1);
		}
	}

	/* Determine the shapefile name from the next argument, if no shape file, exit. */
	if (pgis_optind < argc)
	{
		config->shp_file = argv[pgis_optind];
		pgis_optind++;
	}
	else
	{
		usage();
		exit(0);
	}

	/* Determine the table and schema names from the next argument */
	if (pgis_optind < argc)
	{
		char *strptr = argv[pgis_optind];
		char *chrptr = strchr(strptr, '.');

		/* OK, this is a schema-qualified table name... */
		if (chrptr)
		{
			if ( chrptr == strptr )
			{
				/* ".something" ??? */
				usage();
				exit(0);
			}
			/* Null terminate at the '.' */
			*chrptr = '\0';
			/* Copy in the parts */
			config->schema = strdup(strptr);
			config->table = strdup(chrptr+1);
		}
		else
		{
			config->table = strdup(strptr);
		}
	}

	/* If the table parameter is not provided, use the shape file name as a proxy value.
	   Strip out the .shp and the leading path information first. */
	if ( config->shp_file && config->table == NULL)
	{
		char *shp_file = strdup(config->shp_file);
		char *ptr;

		if ( shp_file == NULL )
		{
			fprintf(stderr, "Unable to allocate memory for shapefile name\n");
			exit(1);
		}

		/* Remove the extension, if present */
		for ( ptr = shp_file + strlen(shp_file); ptr > shp_file; ptr-- )
		{
			if ( *ptr == '.' )
			{
				*ptr = '\0';
				break;
			}
		}

		/* The remaining non-path section is the table name */
		for ( ptr = shp_file + strlen(shp_file); ptr > shp_file; ptr-- )
		{
			if ( *ptr == '/' || *ptr == '\\' )
			{
				ptr++;
				break;
			}
		}
		config->table = strdup(ptr);
		free(shp_file);
	}


	/* Transform table name to lower case if no quoting specified */
	if (!config->quoteidentifiers)
	{
		if ( config->table )
			strtolower(config->table);
		if ( config->schema )
			strtolower(config->schema);
	}

	/* Create the shapefile state object */
	state = ShpLoaderCreate(config);
	if (!state)
	{
		fprintf(stderr, "Unable to allocate memory for loader state\n");
		exit(1);
	}

	/* Open the shapefile */
	ret = ShpLoaderOpenShape(state);
	if (ret != SHPLOADEROK)
	{
		fprintf(stderr, "%s\n", state->message);

		if (ret == SHPLOADERERR)
			exit(1);
	}

	/* If reading the whole shapefile, display its type */
	if (state->config->readshape)
	{
		fprintf(stderr, "Shapefile type: %s\n", SHPTypeName(state->shpfiletype));
		fprintf(stderr, "Postgis type: %s[%d]\n", state->pgtype, state->pgdims);
	}

	/* Print the header to stdout */
	ret = ShpLoaderGetSQLHeader(state, &header);
	if (ret != SHPLOADEROK)
	{
		fprintf(stderr, "%s\n", state->message);

		if (ret == SHPLOADERERR)
			exit(1);
	}

	printf("%s", header);
	free(header);

	/* If we are not in "prepare" mode, go ahead and write out the data. */
	if (state->config->plan.load_data)
	{

		/* If in COPY mode, output the COPY statement */
		if (state->config->dump_format)
		{
			ret = ShpLoaderGetSQLCopyStatement(state, &header);
			if (ret != SHPLOADEROK)
			{
				fprintf(stderr, "%s\n", state->message);

				if (ret == SHPLOADERERR)
					exit(1);
			}

			printf("%s", header);
			free(header);
		}

		/* Main loop: iterate through all of the records and send them to stdout */
		for (i = 0; i < ShpLoaderGetRecordCount(state); i++)
		{
			ret = ShpLoaderGenerateSQLRowStatement(state, i, &record);

			switch (ret)
			{
			case SHPLOADEROK:
				/* Simply display the geometry */
				printf("%s\n", record);
				free(record);
				break;

			case SHPLOADERERR:
				/* Display the error message then stop */
				fprintf(stderr, "%s\n", state->message);
				exit(1);
				break;

			case SHPLOADERWARN:
				/* Display the warning, but continue */
				fprintf(stderr, "%s\n", state->message);
				printf("%s\n", record);
				free(record);
				break;

			case SHPLOADERRECDELETED:
				/* Record is marked as deleted - ignore */
				break;

			case SHPLOADERRECISNULL:
				/* Record is NULL and should be ignored according to NULL policy */
				break;
			}
		}

		/* If in COPY mode, terminate the COPY statement */
		if (state->config->dump_format)
			printf("\\.\n");

	}

	/* Print the footer to stdout */
	ret = ShpLoaderGetSQLFooter(state, &footer);
	if (ret != SHPLOADEROK)
	{
		fprintf(stderr, "%s\n", state->message);

		if (ret == SHPLOADERERR)
			exit(1);
	}

	printf("%s", footer);
	free(footer);


	/* Free the state object */
	ShpLoaderDestroy(state);

	/* Free configuration variables */
	free(config->schema);
	free(config->table);
	free(config->encoding);
	free(config);

	return 0;
}
