/**
 * 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 java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;

import java.sql.Connection;
import java.sql.ResultSet;

import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

//the jakarta command line processing tools
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;


/**
 *  Class to add a table to a replication scheme. This is a rewrite of ers_addtable.
 *
 * @author     Andrew Rawnsley
 */
public class ERSAddTable extends ERS {

	/**
	 * Constructor for the ERSAddTable object
	 *
	 *USAGE: java org.postgresql.ers.ERSAddTable {options} <table 1> <table 2> ... <table n>
	 *options: 	-u columnName		Column name for the unique sequence id (default _ers_uniq)
	 *-d config			Install directory for eRServer (default /opt/erserver)
	 *
	 * @param  table             Table
	 * @param  primary           Connection to the primary
	 * @param  replicants        Connections to the replicants
	 * @param  columnName        Name of the ERS unique column
	 * @param  schemaSupport     Parameter
	 * @exception  SQLException
	 */
	public ERSAddTable(String table, Connection primary, Connection[] replicants, String columnName,
					   boolean schemaSupport, String ersSchema, String host, int port) throws SQLException {

		ResultSet rs;
		Statement stmnt;

		setSchemaSupport(schemaSupport);
        setErsSchema(ersSchema);

		if (!quiet) {
			System.out.println("Adding table " + table);
		}

		// Bad table name. Yell at user
		if (!tableExists(table, primary)) {
			System.out.println("Table " + table + " does not exist in primary.");
			return;
		}

		String schema;

		String tableName;
		String sequenceName;
		int pos;

		if (schemaSupport) {
			// Separate the table name to schema and table, if necessary
			if ((pos = table.indexOf(".")) >= 0) {
				schema = table.substring(0, pos);
				tableName = table.substring(pos + 1);
			} else {
				tableName = table;
				schema = "public";
				table = "public." + table;
			}
			sequenceName = schema + "._ers_" + tableName + "_seq";
		} else {
			tableName = table;
			schema = null;
			sequenceName = "_ers_" + tableName + "_seq";
		}

		int oid = 0;
		int attnum = 0;

		stmnt = primary.createStatement();
		// If the _ers_uniq column doesn't exist, add column, sequence, index, and trigger
		// PROBLEM:
		if (!hasERSUniq(table, columnName, primary)) {

			if (!quiet) {
				System.out.println("Adding " + columnName);
			}

			stmnt.execute("LOCK " + table + " IN EXCLUSIVE MODE");
			stmnt.execute("ALTER TABLE " + table + " ADD COLUMN " + columnName + " BIGINT");
			stmnt.execute("CREATE SEQUENCE " + sequenceName);
			stmnt.execute("ALTER TABLE " + table + " ALTER COLUMN " + columnName + " SET DEFAULT NEXTVAL('" + sequenceName + "')");
			stmnt.execute("UPDATE " + table + " SET " + columnName + " = nextval('" + sequenceName + "') WHERE " + columnName + " IS NULL");
			stmnt.execute("ALTER TABLE " + table + " ADD CONSTRAINT _ers_uniq_cons CHECK (" + columnName + " IS NOT NULL);");
			stmnt.execute("CREATE UNIQUE INDEX "+createRelName("_ers_" + table.replace('.', '_') + "_idx")+" ON " + table + " (" + columnName + ")");
		}

		rs = stmnt.executeQuery("SELECT oid,attnum FROM "+createRelName("_ers_class_attr") +
				" WHERE tablename = '" + tableName + "' " +
				" AND attname = '" + columnName + "'" +
				((schemaSupport) ? " AND schemaname = '" + schema + "' " : " "));

		/*if (schemaSupport) {
			rs = stmnt.executeQuery("SELECT pgc.oid, pga.attnum" +
					" FROM _ers_class_attr pgc, pg_attribute pga" +
					" WHERE pgc.tablename = '" + tableName +
					"' AND pgc.schemaname = '" + schema +
					"' AND pgc.oid = pga.attrelid" +
					" AND pga.attname = '" + columnName + "'");
		} else {
			rs = stmnt.executeQuery("SELECT oid,attnum FROM _ers_class_attr "+
				"WHERE relname = '" + tableName + "' " +
				"AND attname = '" + columnName + "'");
		}*/
		while (rs.next()) {
			oid = rs.getInt(1);
			attnum = rs.getInt(2);
		}

		rs.close();

		if (!quiet) {
			System.out.println("Creating trigger");
		}

		stmnt.execute("CREATE TRIGGER _RSERV_TRIGGER_T_ AFTER insert or update or delete on " + table +
				" for each row execute procedure _rserv_log_('" + attnum + "')");

		if (!quiet) {
			System.out.println("Adding to _rserv_tables_");
		}
		stmnt.execute("INSERT INTO "+createRelName("_rserv_tables_")+" (tname, cname, reloid, key) VALUES ('" + table + "', '" + columnName + "', " + oid + ", " + attnum + ")");
		stmnt.close();


		// for each defined replicant, create the table (if necessary) and add to control tables
		String insertSlaveTable = "INSERT INTO "+createRelName("_rserv_slave_tables_")+" (tname, cname, reloid, key) " +
				"SELECT relname, attname, oid, attnum  " +
				"FROM "+createRelName("_ers_class_attr") +
				" WHERE relname = '" + table + "' " +
				" AND attname = '" + columnName + "'";

		for (int i = 0; i < replicants.length; i++) {

			// if the table isn't there, create from master
			if (!tableExists(table, replicants[i])) {
				copyTable(table, columnName, primary, replicants[i]);
			} else {
				// If the table exists, but doesn't have _ers_uniq, assume it isn't set up at all
				if (!hasERSUniq(table, columnName, replicants[i])) {
					if (!quiet) {
						System.out.println("Adding " + columnName + " on replicant " + i);
					}
					stmnt = replicants[i].createStatement();
					stmnt.execute("ALTER TABLE " + table + " ADD COLUMN " + columnName + " BIGINT");
					if (!quiet) {
						System.out.println("Adding to _rserv_slave_tables_ on replicant " + i);
					}
					stmnt.execute(insertSlaveTable);
					stmnt.close();

				} else {
					// Check to see if the relation exists in _rserv_slave_tables_, which it won't if you are using
					// an existing column
					stmnt = replicants[i].createStatement();
					rs = stmnt.executeQuery("SELECT * FROM "+createRelName("_rserv_slave_tables_")+" where tname = '" + tableName + "'");
					if (!rs.next()) {
						stmnt.execute(insertSlaveTable);
						stmnt.close();
					}

				}
			}
		}

		if (host != null) {
			try {
				new ERSCtl(2, host, port);
				System.out.println("Backend successfully signaled.");
			} catch (Exception ex) {
				System.out.println("Backend signal failed.");
				//ex.printStackTrace();
			}
		}

	}

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

		int i;
		String fileName;
		String[] fileArgs;
		Properties properties = null;
		String[] replicantURL = null;
		String[] replicantUser = null;
		String[] replicantPass = null;

		Options options = new Options();
		options.addOption("u", "uniq", true, "Column name for the unique sequence id (default _ers_uniq)");
		options.addOption("h", "host", true, "Host name of running eRServer (default localhost)");
		options.addOption("p", "port", true, "Port # for signal (default 1099)");
		options.addOption("ns", "nosignal", false, "Don't signal backend");
		options.addOption("d", "ers_home", true, "Install directory for eRServer (default /opt/erserver)");
		options.addOption("f", "file", true, "File containing a list of tables to add");
		options.addOption("q", "quiet", false, "Run quiet");

		//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;
		}

		if (args.length == 0) {
			HelpFormatter formatter = new HelpFormatter();
			formatter.printHelp("USAGE: java org.postgresql.ers.ERSAddTable <options> <table1{,uniq}> ... <table n{,uniq}>", options);
			return;
		}

		String uniq = line.getOptionValue("u", "_ers_uniq");
		String ersHome = line.getOptionValue("d", "/opt/erserver/");
		quiet = line.hasOption("q");

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

		if (!quiet) {
			System.out.println("Using " + ersHome + " as ERS installation directory");
			System.out.println("Using " + uniq + " as ERS unique column name");
		}

		String s;
		String host = null;
		int port = 1099;

		if (!line.hasOption("ns")) {
			host = line.getOptionValue("h","localhost");
			if ((s = line.getOptionValue("p")) != null) {
				try {
                    port = Integer.parseInt(s);
				} catch (Exception ex) {
					ex.printStackTrace();
					port = 1099;
				}
			}
		}

		// Read the replication.cfg file
		try {
			File file = new File(ersHome + "etc/replication.cfg");
			if (!file.exists()) {
				System.out.println("Cannot find replication.cfg in " + ersHome);
				System.exit(1);
			}

			properties = new Properties();
			FileInputStream in = new FileInputStream(file);
			properties.load(in);
			in.close();
		} catch (IOException ix) {
			ix.printStackTrace();
			System.exit(1);
		}

		// Get connection properties for the primary
		String primaryURL = properties.getProperty("replic.master.JDBCConnectionURL");
		String primaryUser = properties.getProperty("replic.master.user");
		String primaryPass = properties.getProperty("replic.master.password");

		if (primaryURL == null || primaryUser == null || primaryPass == null) {
			System.out.println("Invalid primary URL/user/password");
			System.exit(1);
		}

		// Get connection properties for the replicants
		s = properties.getProperty("replic.slave.JDBCConnectionURL");

		if (s != null) {
			replicantURL = split(s, ",");
		}

		s = properties.getProperty("replic.slave.user");

		if (s != null) {
			replicantUser = split(s, ",");
		}

		s = properties.getProperty("replic.slave.password");

		if (s != null) {
			replicantPass = split(s, ",");
		}

		String ersSchema = properties.getProperty("replic.schema");

		if (replicantURL == null || replicantUser == null || replicantPass == null) {
			System.out.println("Bad replicant configuration");
			System.exit(1);
		}

		if (replicantURL.length != replicantUser.length || replicantURL.length != replicantPass.length) {
			System.out.println("Bad replicant configuration");
			System.exit(1);
		}

		// Create the primary and replicant DB connections
		Connection primaryConnection = getConnection(primaryURL, primaryUser, primaryPass);
		Connection[] replicants = new Connection[replicantURL.length];

		for (i = 0; i < replicantURL.length; i++) {
			replicants[i] = getConnection(replicantURL[i], replicantUser[i], replicantPass[i]);
		}

		boolean schemaSupport;
		s = properties.getProperty("schema_support");
		if (s != null) {
			schemaSupport = s.equals("true");
		} else {
			schemaSupport = false;
		}

		String col;
		String[] s2;

		if ((fileName = line.getOptionValue("f")) != null) {
			try {
				File f = new File(fileName);
				if (f.exists()) {
					LineNumberReader in = new LineNumberReader(new FileReader(f));
					while ((s = in.readLine()) != null) {
						s = s.trim();
						if (s.length() == 0) {
							continue;
						}
						if (s.charAt(0) == '#') {
							continue;
						}
						// Check for a custom column name on the same line as the table (table,column)
						if (s.indexOf(",") >= 0) {
							s2 = split(s, ",");
							s = s2[0];
							col = s2[1];
						} else {
							col = uniq;
						}
						try {
							new ERSAddTable(s, primaryConnection, replicants, col, schemaSupport, ersSchema, host, port);
						} catch (SQLException sx) {
							sx.printStackTrace();
						}

					}
				}
			} catch (IOException iox) {
				iox.printStackTrace();
			}
		} else {
			fileArgs = line.getArgs();
			// Process each table
			for (i = 0; i < fileArgs.length; i++) {
				try {
					if (fileArgs[i].indexOf(",") >= 0) {
						s2 = split(fileArgs[i], ",");
						s = s2[0];
						col = s2[1];
					} else {
						s = fileArgs[i];
						col = uniq;
					}
					new ERSAddTable(s, primaryConnection, replicants, col, schemaSupport, ersSchema, host, port);
				} catch (SQLException sx) {
					sx.printStackTrace();
				}
			}
		}

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

}

