// This software is released into the Public Domain.  See copying.txt for details.
package org.openstreetmap.osmosis.pgsimple.v0_6.impl;

import java.util.logging.Logger;

import org.openstreetmap.osmosis.pgsimple.common.DatabaseContext;


/**
 * Drops and creates indexes in support of bulk load activities.
 * 
 * @author Brett Henderson
 */
public class IndexManager {
	
	private static final Logger LOG = Logger.getLogger(IndexManager.class.getName());
	
	
	private static final String[] PRE_LOAD_SQL = {
		"ALTER TABLE users DROP CONSTRAINT pk_users",
		"ALTER TABLE nodes DROP CONSTRAINT pk_nodes",
		"ALTER TABLE ways DROP CONSTRAINT pk_ways",
		"ALTER TABLE way_nodes DROP CONSTRAINT pk_way_nodes",
		"ALTER TABLE relations DROP CONSTRAINT pk_relations",
		"ALTER TABLE relation_members DROP CONSTRAINT pk_relation_members",
		"DROP INDEX idx_node_tags_node_id",
		"DROP INDEX idx_nodes_geom",
		"DROP INDEX idx_way_tags_way_id",
		"DROP INDEX idx_relation_tags_relation_id",
		"DROP INDEX idx_way_nodes_node_id"
	};
	private static final String[] PRE_LOAD_SQL_WAY_BBOX = {
		"DROP INDEX idx_ways_bbox"
	};
	private static final String[] PRE_LOAD_SQL_WAY_LINESTRING = {
		"DROP INDEX idx_ways_linestring"
	};
	
	private static final String[] POST_LOAD_SQL = {
		"ALTER TABLE ONLY users ADD CONSTRAINT pk_users PRIMARY KEY (id)",
		"ALTER TABLE ONLY nodes ADD CONSTRAINT pk_nodes PRIMARY KEY (id)",
		"ALTER TABLE ONLY ways ADD CONSTRAINT pk_ways PRIMARY KEY (id)",
		"ALTER TABLE ONLY way_nodes ADD CONSTRAINT pk_way_nodes PRIMARY KEY (way_id, sequence_id)",
		"ALTER TABLE ONLY relations ADD CONSTRAINT pk_relations PRIMARY KEY (id)",
		"ALTER TABLE ONLY relation_members ADD CONSTRAINT pk_relation_members PRIMARY KEY (relation_id, sequence_id)",
		"CREATE INDEX idx_node_tags_node_id ON node_tags USING btree (node_id)",
		"CREATE INDEX idx_nodes_geom ON nodes USING gist (geom)",
		"CREATE INDEX idx_way_tags_way_id ON way_tags USING btree (way_id)",
		"CREATE INDEX idx_relation_tags_relation_id ON relation_tags USING btree (relation_id)",
		"CREATE INDEX idx_way_nodes_node_id ON way_nodes USING btree (node_id)"
	};
	private static final String[] POST_LOAD_SQL_WAY_BBOX = {
		"CREATE INDEX idx_ways_bbox ON ways USING gist (bbox)"
	};
	private static final String[] POST_LOAD_SQL_WAY_LINESTRING = {
		"CREATE INDEX idx_ways_linestring ON ways USING gist (linestring)"
	};
	private static final String POST_LOAD_SQL_POPULATE_WAY_BBOX =
		"UPDATE ways SET bbox = ("
		+ "SELECT ST_Envelope(ST_Collect(geom)) FROM nodes JOIN way_nodes ON way_nodes.node_id = nodes.id"
		+ " WHERE way_nodes.way_id = ways.id"
		+ ")";
	private static final String POST_LOAD_SQL_POPULATE_WAY_LINESTRING =
		"UPDATE ways w SET linestring = ("
		+ "SELECT ST_MakeLine(c.geom) AS way_line FROM ("
		+ "SELECT n.geom AS geom FROM nodes n INNER JOIN way_nodes wn ON n.id = wn.node_id"
		+ " WHERE (wn.way_id = w.id) ORDER BY wn.sequence_id"
		+ ") c"
		+ ")";
	
	
	private DatabaseContext dbCtx;
	private DatabaseCapabilityChecker capabilityChecker;
	private boolean populateBbox;
	private boolean populateLinestring;
	
	
	/**
	 * Creates a new instance.
	 * 
	 * @param dbCtx
	 *            Provides access to the database.
	 * @param populateBbox
	 *            If true, the bbox colum on the way table will be populated
	 *            after load.
	 * @param populateLinestring
	 *            If true, the linestring column on the way table will be
	 *            populated after load.
	 */
	public IndexManager(DatabaseContext dbCtx, boolean populateBbox, boolean populateLinestring) {
		this.dbCtx = dbCtx;
		this.populateBbox = populateBbox;
		this.populateLinestring = populateLinestring;
		
		capabilityChecker = new DatabaseCapabilityChecker(dbCtx);
	}
	
	
	/**
	 * Drops indexes and constraints in the database.
	 */
	public void prepareForLoad() {
		LOG.fine("Running pre-load SQL statements.");
		for (int i = 0; i < PRE_LOAD_SQL.length; i++) {
			LOG.finer("SQL: " + PRE_LOAD_SQL[i]);
			dbCtx.executeStatement(PRE_LOAD_SQL[i]);
		}
		if (capabilityChecker.isWayBboxSupported()) {
			LOG.fine("Running pre-load bbox SQL statements.");
			for (int i = 0; i < PRE_LOAD_SQL_WAY_BBOX.length; i++) {
				LOG.finer("SQL: " + PRE_LOAD_SQL_WAY_BBOX[i]);
				dbCtx.executeStatement(PRE_LOAD_SQL_WAY_BBOX[i]);
			}
		}
		if (capabilityChecker.isWayLinestringSupported()) {
			LOG.fine("Running pre-load linestring SQL statements.");
			for (int i = 0; i < PRE_LOAD_SQL_WAY_LINESTRING.length; i++) {
				LOG.finer("SQL: " + PRE_LOAD_SQL_WAY_LINESTRING[i]);
				dbCtx.executeStatement(PRE_LOAD_SQL_WAY_LINESTRING[i]);
			}
		}
		LOG.fine("Pre-load SQL statements complete.");
	}
	
	
	/**
	 * Creates indexes in the database and populates derived columns.
	 */
	public void completeAfterLoad() {
		LOG.fine("Running post-load SQL.");
		for (int i = 0; i < POST_LOAD_SQL.length; i++) {
			LOG.finer("SQL: " + POST_LOAD_SQL[i]);
			dbCtx.executeStatement(POST_LOAD_SQL[i]);
		}
		if (capabilityChecker.isWayBboxSupported()) {
			LOG.fine("Running post-load bbox SQL statements.");
			if (populateBbox) {
				LOG.finer("SQL: " + POST_LOAD_SQL_POPULATE_WAY_BBOX);
				dbCtx.executeStatement(POST_LOAD_SQL_POPULATE_WAY_BBOX);
			}
			for (int i = 0; i < POST_LOAD_SQL_WAY_BBOX.length; i++) {
				LOG.finer("SQL: " + POST_LOAD_SQL_WAY_BBOX[i]);
				dbCtx.executeStatement(POST_LOAD_SQL_WAY_BBOX[i]);
			}
		}
		if (capabilityChecker.isWayLinestringSupported()) {
			LOG.fine("Running post-load linestring SQL statements.");
			if (populateLinestring) {
				LOG.finer("SQL: " + POST_LOAD_SQL_POPULATE_WAY_LINESTRING);
				dbCtx.executeStatement(POST_LOAD_SQL_POPULATE_WAY_LINESTRING);
			}
			for (int i = 0; i < POST_LOAD_SQL_WAY_LINESTRING.length; i++) {
				LOG.finer("SQL: " + POST_LOAD_SQL_WAY_LINESTRING[i]);
				dbCtx.executeStatement(POST_LOAD_SQL_WAY_LINESTRING[i]);
			}
		}
	}
}
