/**
 * Class copyright 2003 by the Ravensfield Digital Resource Group, Ltd, Granville, OH.
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose, without fee, and without a written agreement
 * is hereby granted, provided that the above copyright notice and this
 * paragraph and the following two paragraphs appear in all copies.
 *
 * IN NO EVENT SHALL THE RAVENSFIELD DIGITAL RESOURCE GROUP, LTD BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
 * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
 * DOCUMENTATION, EVEN IF THE RAVENSFIELD DIGITAL RESOURCE GROUP, LTD HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * THE RAVENSFIELD DIGITAL RESOURCE GROUP, LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE RAVENSFIELD DIGITAL RESOURCE GROUP, LTD HAS NO OBLIGATIONS TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 *
 * (Quick readers will recognize that as the stock BSD license)
 */
package org.postgresql.ers;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

/**
 *  Class to set up the eRServer package.
 *
 * @author     ronz
 */
public class ERSSetup extends ERS {

	/**
	 *Constructor for the ERSSetup object
	 *
	 * @param  primaryURL        URL of the primary
	 * @param  primaryUser       User for the primary
	 * @param  primaryPass       Password for the primary
	 * @param  replicantURL      URL of the slave
	 * @param  replicantUser     User for the slave
	 * @param  replicantPass     Password for the slave
	 * @param  ersHome           eRServer home directory
	 * @param  adminUser         Admin user name
	 * @param  adminPass         Admin password
	 * @exception  SQLException
	 */
	public ERSSetup(String primaryURL, String primaryUser, String primaryPass,
			String replicantURL, String replicantUser, String replicantPass, String adminUser, String adminPass,
			String ersHome, boolean schemaSupport, int rmiPort, String ersSchema) throws SQLException {

		ResultSet rs;
		String[] s;
		String line;
		Statement stmnt = null;

		Connection primary = null;
		Connection replicant = null;
		Connection admin = null;

		setSchemaSupport(schemaSupport);
		setErsSchema(ersSchema);

				// Clean up ersHome
		if (!ersHome.endsWith(System.getProperty("file.separator"))) {
			ersHome += System.getProperty("file.separator");
		}

		try {
			// Create the primary, replicant, and admin DB connections, bail if any are null
			primary = getConnection(primaryURL, primaryUser, primaryPass);
			replicant = getConnection(replicantURL, replicantUser, replicantPass);
			admin = getConnection(primaryURL, adminUser, adminPass);

			if (primary == null) {
				System.out.println("Bad master URL/user/password");
				return;
			}

			if (replicant == null) {
				System.out.println("Bad slave URL/user/password");
				return;
			}

			if (admin == null) {
				System.out.println("Bad master URL/superuser/superuser password");
				return;
			}

			// Check to see if the replication environment already exists
			File file = new File(ersHome + "etc/replication.cfg");
			if (file.exists()) {
				System.out.println(ersHome + "etc/replication.cfg already exists. eRServer is already configured.");
				return;
			}

			if (!quiet) {
				System.out.println("\nPostgreSQL Enterprise Replication Server");
				System.out.println("*********************************************");
				System.out.println("\nSetting up eRServer...\n");
			}

			primary.setAutoCommit(false);
			replicant.setAutoCommit(false);
			admin.setAutoCommit(false);

			if (!quiet) {
				System.out.println("Master database");
				System.out.println("***************");
				System.out.println("Server                : " + primary.getMetaData().getURL());
			}

			// Create the functions on the primary, using the admin connection
			stmnt = admin.createStatement();
			stmnt.execute("set transaction isolation level serializable");

			boolean createSchema = false;
			if (schemaSupport && ersSchema != null) {
				rs = stmnt.executeQuery("SELECT * FROM pg_namespace WHERE nspname = '"+ersSchema+"'");
				if (!rs.next()) {
					createSchema = true;
				}
			}

			if (createSchema) {
				stmnt.execute("CREATE SCHEMA "+ersSchema);
				admin.commit();
			}

			rs = stmnt.executeQuery("SELECT * FROM pg_proc WHERE proname = '_rserv_log_'");
			if (!rs.next()) {
				stmnt.execute("CREATE FUNCTION "+createRelName("_rserv_log_()")+" RETURNS opaque AS '" + ersHome + "lib/erserver.so' LANGUAGE 'c'");
				stmnt.execute("CREATE FUNCTION "+createRelName("_rserv_sync_(int4)")+" RETURNS int4 AS '" + ersHome + "lib/erserver.so' LANGUAGE 'c'");
				stmnt.execute("CREATE FUNCTION "+createRelName("_rserv_debug_(int4)")+" RETURNS int4 AS '" + ersHome + "lib/erserver.so' LANGUAGE 'c'");
				stmnt.execute("CREATE FUNCTION "+createRelName("_rserv_lock_altid_(int4)")+" RETURNS int4 AS '" + ersHome + "lib/erserver.so' LANGUAGE 'c'");
				stmnt.execute("CREATE FUNCTION "+createRelName("_rserv_unlock_altid_(int4)")+" RETURNS int4 AS '" + ersHome + "lib/erserver.so' LANGUAGE 'c'");

				// PTE funcs
				stmnt.execute("CREATE FUNCTION "+createRelName("_pte_get_snapshot_()")+" RETURNS text AS '" + ersHome + "lib/pte.so' LANGUAGE 'c'");
				stmnt.execute("CREATE FUNCTION "+createRelName("_pte_set_snapshot_(text)")+" RETURNS int4 AS '" + ersHome + "lib/pte.so' LANGUAGE 'c'");
				stmnt.close();
			}

			if (!quiet) {
				System.out.println("\nFunction creation completed\n");
			}

			// Set up the rest of the primary
			stmnt = primary.createStatement();
			rs = stmnt.executeQuery("SELECT * FROM pg_class WHERE relname = '_rserv_servers_'");
			if (!rs.next()) {
				stmnt.execute("create table "+createRelName("_RSERV_SERVERS_")+" (server serial, host text, post int4, dbase text)");
				// List of replicated tables
				stmnt.execute("create table "+createRelName("_RSERV_TABLES_")+"  (tname name, cname name, reloid oid, key int4)");
				//# Bookkeeping log for row replication
				stmnt.execute("create table "+createRelName("_RSERV_LOG_1_")+" (reloid oid, logid xxid, logtime timestamp, deleted int4, key text)");
				//# This is to speedup trigger
				stmnt.execute("create index _RSERV_LOG_1_INDX_REL_KEY_ on "+createRelName("_RSERV_LOG_1_")+" (reloid, key)");
				//# This is to speedup lookup of updated tuples in PrepareSnapshot
				stmnt.execute("create index _RSERV_LOG_1_INDX_REL_ID_ on "+createRelName("_RSERV_LOG_1_")+" (reloid, logid xxid_ops)");
				//# Second log
				stmnt.execute("create table "+createRelName("_RSERV_LOG_2_")+" (reloid oid, logid xxid, logtime timestamp, deleted int4, key text)");
				//# This is to speedup trigger
				stmnt.execute("create index _RSERV_LOG_2_INDX_REL_KEY_ on "+createRelName("_RSERV_LOG_2_")+" (reloid, key)");
				//# This is to speedup lookup of updated tuples in PrepareSnapshot
				stmnt.execute("create index _RSERV_LOG_2_INDX_REL_ID_ on "+createRelName("_RSERV_LOG_2_")+" (reloid, logid xxid_ops)");
				//# Sync point for each slave server
				stmnt.execute("create table "+createRelName("_RSERV_SYNC_")+" (server int4, syncid int4, synctime timestamp, status int4, minid xxid, maxid xxid, active text)");
				stmnt.execute("create index _RSERV_SYNC_INDX_SRV_ID_ on "+createRelName("_RSERV_SYNC_")+" (server, syncid)");
				//# Sync point reference numbers
				stmnt.execute("create sequence "+createRelName("_rserv_sync_seq_"));
				//# Active log id
				stmnt.execute("create sequence "+createRelName("_rserv_active_log_id_")+" MINVALUE 1 MAXVALUE 2");
				stmnt.execute("select setval('"+createRelName("_rserv_active_log_id_")+"', 1)");

				//# Old log status
				stmnt.execute("create sequence "+createRelName("_rserv_old_log_status_")+" MINVALUE 0 MAXVALUE 2");
				stmnt.execute("select setval('"+createRelName("_rserv_old_log_status_")+"', 0)");

				createView(primary);

				stmnt.close();
			}

			if (!quiet) {
				System.out.println("Master database setup completed\n");
			}

			if (!quiet) {
				System.out.println("Slave database");
				System.out.println("***************");
				System.out.println("Server                : " + replicant.getMetaData().getURL());
			}

			// Create the slave environment
			stmnt = replicant.createStatement();
			stmnt.execute("set transaction isolation level serializable");

			createSchema = false;
			if (schemaSupport && ersSchema != null) {
				rs = stmnt.executeQuery("SELECT * FROM pg_namespace WHERE nspname = '"+ersSchema+"'");
				if (!rs.next()) {
					createSchema = true;


				}
			}

			if (createSchema) {
				stmnt.execute("CREATE SCHEMA "+ersSchema);
				replicant.commit();
			}

			rs = stmnt.executeQuery("SELECT * FROM pg_class WHERE relname = '_rserv_slave_tables_'");
			if (!rs.next()) {
				setupReplicant(replicant);
			}

			primary.commit();
			primary.setAutoCommit(true);
			admin.commit();
			admin.setAutoCommit(true);
			replicant.commit();
			replicant.setAutoCommit(true);

		} catch (SQLException sx) {
			primary.rollback();
			admin.rollback();
			replicant.rollback();
			primary.setAutoCommit(true);
			admin.setAutoCommit(true);
			replicant.setAutoCommit(true);
			throw sx;
		} finally {
			if (stmnt != null) {
				try {
					stmnt.close();
				} catch (SQLException sx) {}
			}
			if (primary != null) {
				primary.close();
			}
			if (admin != null) {
				admin.close();
			}
			if (replicant != null) {
				replicant.close();
			}
		}

		if (!quiet) {
			System.out.println("\nSlave database setup completed\n");
		}

		// Create config file

		try {
			RandomAccessFile in = new RandomAccessFile(ersHome + "templates/replication.cfg-template", "r");
			PrintWriter out = new PrintWriter(new FileOutputStream(ersHome + "etc/replication.cfg"));
			while ((line = in.readLine()) != null) {
				if (line.startsWith("replic.slave.JDBCConnectionURL")) {
					s = getProperty(line);
					out.println(s[0] + "=" + replicantURL);
				} else if (line.startsWith("replic.slave.user")) {
					s = getProperty(line);
					out.println(s[0] + "=" + replicantUser);
				} else if (line.startsWith("replic.slave.pass")) {
					s = getProperty(line);
					out.println(s[0] + "=" + replicantPass);
				} else if (line.startsWith("replic.master.JDBCConnectionURL")) {
					s = getProperty(line);
					out.println(s[0] + "=" + primaryURL);
				} else if (line.startsWith("replic.master.user")) {
					s = getProperty(line);
					out.println(s[0] + "=" + primaryUser);
				} else if (line.startsWith("replic.master.pass")) {
					s = getProperty(line);
					out.println(s[0] + "=" + primaryPass);
				} else {
					out.println(line);
				}

			}
			out.println("schema_support="+schemaSupport);
			if (ersSchema != null) {
				out.println("replic.schema="+ersSchema);
			}
			out.println("replic.rmi.port="+rmiPort);
			out.close();
			in.close();
		} catch (IOException iox) {
			iox.printStackTrace();
		}
/*
MASTER_HOST=_master_server_
MASTER_PORT=_master_port_
MASTER_USER=_master_user_
MASTER_PASS=_master_pass_
MASTER_DB=_master_database_

SLAVE_HOST=_slave_server_
SLAVE_PORT=_slave_port_
SLAVE_USER=_slave_user_
SLAVE_PASS=_slave_pass_
SLAVE_DB=_slave_database_
*/
		try {
			RandomAccessFile in = new RandomAccessFile(ersHome + "templates/ers_setvars-template", "r");
			PrintWriter out = new PrintWriter(new FileOutputStream(ersHome + "bin/ers_setvars"));
			while ((line = in.readLine()) != null) {
				if (line.startsWith("REPLIC_HOME")) {
					out.println("REPLIC_HOME=" + ersHome);
				} else {
					out.println(line);
				}

			}
			out.println("REPLIC_RMI_PORT="+rmiPort);
			out.close();
			in.close();
		} catch (IOException iox) {
			iox.printStackTrace();
		}
		if (!quiet) {
			System.out.println("Configuration files created\n");
		}
	}

	/**
	 *  Add tables to the replicator
	 *
	 * @param  args  The command line arguments
	 */
	public static void main(String[] args) {

		String replicantURL;
		String replicantUser;
		String replicantPass;
		String replicantServer;
		String replicantDb;
		String replicantPort;
		String primaryURL;
		String primaryUser;
		String primaryPass;
		String primaryServer;
		String primaryDb;
		String primaryPort;
		String adminUser;
		String adminPass;
		String ersSchema;

		boolean schemaSupport = true;

		Options options = new Options();
		options.addOption("ms", "masterserver", true, "Master host");
		options.addOption("mu", "masteruser", true, "Master user");
		options.addOption("mp", "masterpass", true, "Master password");
		options.addOption("mb", "masterdb", true, "Master db");
		options.addOption("mo", "masterport", true, "Master port");
		options.addOption("pu", "pgsuperuser", true, "Admin user (defaults to master user)");
		options.addOption("pp", "pgsuperpass", true, "Admin password (defaults to master password)");
		options.addOption("ss", "slaveserver", true, "Slave host");
		options.addOption("su", "slaveuser", true, "Slave user");
		options.addOption("sp", "slavepass", true, "Slave password");
		options.addOption("sb", "slavedb", true, "Slave db");
		options.addOption("so", "slaveport", true, "Slave port");
		//options.addOption("sc", "schema", true, "Schema for erserver relations (7.3+ only");

		options.addOption("p", "port", true, "Signaling port (default 1099)");
		options.addOption("q", "quiet", false, "quiet mode");
		//options.addOption("u", "uniq", true, "Column name for the unique sequence id (default _ers_uniq)");
		options.addOption("d", "ers_home", true, "Install directory for eRServer (default /opt/erserver)");
		options.addOption("ns", "noschema", false, "Disable schema support (schema support in 7.3+ only)");

		//parse the commandline arguments
		GnuParser parser = new GnuParser();
		CommandLine line = null;
		try {
			line = parser.parse(options, args);
		} catch (org.apache.commons.cli.ParseException exp) {
			System.out.println("Parsing failed. Reason: " + exp.getMessage());
			return;
		}

		// get options
		String ersHome = line.getOptionValue("d", "/opt/erserver/");

		primaryServer = line.getOptionValue("ms", null);
		primaryUser = line.getOptionValue("mu", null);
		primaryPass = line.getOptionValue("mp", null);
		primaryDb = line.getOptionValue("mb", null);
		primaryPort = line.getOptionValue("mo", "5432");

		replicantServer = line.getOptionValue("ss", null);
		replicantUser = line.getOptionValue("su", null);
		replicantPass = line.getOptionValue("sp", null);
		replicantDb = line.getOptionValue("sb", null);
		replicantPort = line.getOptionValue("so", "5432");

		adminUser = line.getOptionValue("pu", primaryUser);
		adminPass = line.getOptionValue("pp", primaryPass);
        ersSchema = line.getOptionValue("sc", "public");

		String s = line.getOptionValue("q");
		if (s != null) {
			setQuiet(true);
		}

		if (line.hasOption("ns")) {
			schemaSupport = false;
			ersSchema = null;
		}

		int rmiPort = 1099;
		if ((s = line.getOptionValue("p")) != null) {
			try {
				rmiPort = Integer.parseInt(s);
			} catch (Exception ex) {
				ex.printStackTrace();
				rmiPort = 1099;
			}
		}

		// Yell if any required parameters are null
		if (primaryServer == null || primaryUser == null || primaryDb == null || primaryPass == null ||
				replicantServer == null || replicantDb == null || replicantUser == null || replicantPass == null) {
			HelpFormatter formatter = new HelpFormatter();
			formatter.printHelp("USAGE: java org.postgresql.ers.ERSSetup <options>", options);
			return;
		}

		replicantURL = "jdbc:postgresql://" + replicantServer + ":" + replicantPort + "/" + replicantDb;
		primaryURL = "jdbc:postgresql://" + primaryServer + ":" + primaryPort + "/" + primaryDb;

		try {
			new ERSSetup(primaryURL, primaryUser, primaryPass, replicantURL, replicantUser, replicantPass, adminUser,
					adminPass, ersHome, schemaSupport, rmiPort, ersSchema);
		} catch (SQLException sx) {
			sx.printStackTrace();
		}

		if (!quiet) {
			System.out.println("\nDone\n");
		}
	}

}

