/*
 * $Source: /usr/local/cvsroot/erserver/erserver/java/src/com/postgres/replic/server/ReplicationServer.java,v $
 * $Author: asullivan $ $Revision: 1.1.1.1 $ $Date: 2003/08/26 19:02:22 $
 *
 */

package com.postgres.replic.server;

import com.postgres.replic.server.props.*;
import com.postgres.replic.util.*;
import com.postgres.replic.util.struct.*;
import com.postgres.util.Logger.*;
import com.postgres.util.cmd.*;
import com.postgres.util.config.*;
import com.postgres.util.jdbc.*;
import com.postgres.util.timer.*;
import java.sql.*;
import java.util.*;
import sun.misc.*;

public class ReplicationServer implements TimerListener {
    private static final Signal sig_int__ = new Signal("INT");
    private static final Signal sig_term__ = new Signal("TERM");
    private ShutdownHandler shutdown__ = new ShutdownHandler();
    private boolean done__;

    private boolean shutDown = false;
    private ConfiguratorIntf config;
    private ConnectionPoolProps connPoolProps;
    private ServerProps serverProps;
    private ConnectionPool masterPool;
    private ConnectionPool [] slavePools;
    private RservThread [] replicThreads;
    private RservThread cleanThread;
    private ReplicationRunnable [] replicRun;
    private CleanLogRunnable cleanRun;
    private boolean debug;
    private boolean localDebug;
    private boolean verbose = true;
    private static int run;
    private static int REPLICATE = 0;
    private static int CLEAN = 1;
    private String replicHome;
    private boolean replic;
    private boolean clean;
    private boolean threads;
    private boolean recover;
    private int recoveryInterval = 30; // (sec) default interval to check up threads
    private boolean [] replicThreadState;
    private boolean masterThreadState;
    private com.postgres.util.timer.Timer timer;

    private static final String[] labels =         {};
    private static final String[] flags =          {
                                 "-debug",   // extra debugging
                                 "-verbose", // verbose
                                 "-replic",  // execute replication thread only
                                 "-clean",   // execute clean thread only
                                 "-threads", // start threads ( w/o this flag it will execute thread once)
                                 "-recover", // recover dead threads
                                 };

    private static final String APP_NAME = "java com.postgres.replic.server.ReplicationServer";

    public static void main(String args[]) {
        ReplicationServer replicServer = new ReplicationServer();
        replicServer.init(args);
        replicServer.run();
    }

    private Logger getLogger() {
        return LogRepl.getInstance();
    }

    private boolean init(String[] args)  {
        boolean rc = false;
        CmdLine cline = null;

        if (args == null || args.length <= 0) {
            //System.out.println("args == null || args.length");
            // In no flags set - use default - all true:
            replic = true;
            clean = true;
            threads = true;
            recover = true;


        }
        try {
            cline = CmdLine.instance(args, labels, flags);
            // Flags:
            verbose = verbose ? verbose : cline.getFlag("-verbose");
            debug = debug ? debug : cline.getFlag("-debug");
            replic = cline.getFlag("-replic")  | replic;

            // disable cleanlog temporarily:
            // clean = false;
            clean = cline.getFlag("-clean") | clean;
            threads = cline.getFlag("-threads") | threads;
            recover = cline.getFlag("-recover") | recover;

            if (!replic && !clean) {
                throw new Exception("You must specify at least one of the options: [-replic] [-clean]");
            }

            if (verbose) {
                System.out.println("verbose=" + verbose);
                System.out.println("debug=" + debug);
                System.out.println("replic=" + replic);
                System.out.println("clean=" + clean);
                System.out.println("threads=" + threads);
                System.out.println("recover=" + recover);
            }
            rc = true;
        } catch (Exception e) {
            System.out.println("ReplicationServer::init: " + e.toString());
            String usage = CmdLine.getUsage(APP_NAME, labels, flags);
            System.out.println(usage);
        }
        return rc;
    }

    private void postInit() {
        // Start our timer so we can timeout connections. The
        // clock cycle will be recoveryInterval seconds
        if (recover) {
            getLogger().info("ReplicationServer::run: STARTING TIMER FOR RECOVERY OPTION");
            timer = new com.postgres.util.timer.Timer(this, recoveryInterval);
            timer.start();
        }
    }

    private void run() {
        boolean loggerStarted = false;
        try {
            initializeHome();
            if (localDebug) System.out.println("ReplicationServer::run: initialized Home");
            initializeLogger();
            initializeProps();
            //getLogger().info("ReplicationServer::run: test-1");
            //getLogger().info("ReplicationServer::run: test-2");
            loggerStarted = true;
            addHook();
            sig_int__.handle(sig_int__, shutdown__);
            sig_term__.handle(sig_term__, shutdown__);
            createConnPools();
            startProcesses();
            postInit();
        } catch (Exception e) {
            if (loggerStarted) {
                System.out.println("ReplicationServer::run: " + e.toString());
            } else {
                getLogger().error("ReplicationServer::run: ", e);
            }
        } finally {

        }
    }

    private Thread spawnReplicationThread(ConnectionPool slavePools)
    throws ReplicationException {
        ReplicationRunnable replicRun = new ReplicationRunnable(serverProps,
        connPoolProps,
        masterPool,
        slavePools);
        replicRun.setDebug(debug);
        replicRun.setVerbose(verbose);
        return new Thread(replicRun);
    }

    /*
    */

    private void createCleanThread() throws ReplicationException {
        if (slavePools == null || masterPool == null) {
            throw new  ReplicationException("ReplicationServer::createCleanThread: slavePools == null || masterPool == null");
        }
        if (cleanThread != null && cleanThread.isAlive()) {
            return;
        }

        try {
            cleanRun = new CleanLogRunnable(serverProps,
            connPoolProps,
            masterPool,
            slavePools[0]) ;
            cleanRun.setDebug(debug);
            cleanThread = new RservThread(cleanRun);
            getLogger().info("STARTING CLEANUP THREAD");
            cleanThread.start();
        } catch (Exception e) {
            throw new  ReplicationException("ReplicationServer::createCleanThread: "
            + e.toString());
        } finally {

        }
    }

     /*
    */

    private void runCleanThread() throws ReplicationException {
        if (slavePools == null || masterPool == null) {
            throw new  ReplicationException("ReplicationServer::runCleanThread: slavePools == null || masterPool == null");
        }

        if (cleanThread != null && cleanThread.isAlive()) {
            return;
        }

        try {
            cleanRun = new CleanLogRunnable(serverProps,
            connPoolProps,
            masterPool,
            slavePools[0]) ;
            cleanRun.setDebug(debug);
            cleanThread = new RservThread(cleanRun);
            getLogger().info("STARTING CLEANUP THREAD");
            //cleanThread.start();
            cleanThread.run();
        } catch (Exception e) {
            throw new  ReplicationException("ReplicationServer::runCleanThread: "
            + e.toString());
        } finally {

        }
    }



    /*
    */

    private void runReplicationThreads() throws ReplicationException {
        if (slavePools == null || masterPool == null ||
        slavePools.length == 0) {
            throw new  ReplicationException("ReplicationServer::runReplicationThreads: slavePools == null || masterPool == null || slavePools.length == 0");
        }

        try {
            if (replicThreads == null) {
                replicThreads = new RservThread[slavePools.length];
                replicRun = new ReplicationRunnable[slavePools.length];
            }

            for (int i = 0; i < slavePools.length; i++) {
                if (replicThreads[i] == null || !replicThreads[i].isAlive()) {
                    if (debug)
                        getLogger().debug("ReplicationServer::runReplicationThreads: before spawnReplicationThread #" + i);
                    //replicThreads[i] = spawnReplicationThread(slavePools[i]);
                    replicRun[i] = new ReplicationRunnable(serverProps,
                    connPoolProps,
                    masterPool,
                    slavePools[i]);
                    replicRun[i].setDebug(debug);
                    replicThreads[i] = new RservThread(replicRun[i]);
                    if (debug)
                        getLogger().debug("ReplicationServer::runReplicationThreads: before replicRun[" +
                        i + "].start()");
                    getLogger().info("STARTING REPLICATION RUN " + i);
                    //replicThreads[i].start();
                    replicRun[i].run();
                }
            }
        } catch (Exception e) {
            throw new  ReplicationException("ReplicationServer::runReplicationThreads: "
            + e.toString());
        } finally {

        }
    }

    /*
    */

    private boolean [] getReplicThreadState () throws Exception {
        boolean allThreadsDead = true;
        if (slavePools == null || slavePools.length == 0) {
            throw new Exception("getReplicThreadState: you may not call this method when slavePools == null || slavePools.length == 0 ");
        } else {
            if (replicThreadState == null) {
                replicThreadState = new boolean[slavePools.length];
            }

            for (int i = 0; i < slavePools.length; i++) {
                if (replicThreads[i] == null || !replicThreads[i].isAlive()) {
                    replicThreadState[i] = false;
                } else {
                    replicThreadState[i] = true;
                    if (allThreadsDead) allThreadsDead = false; // at least 1 thread is alive
                }
            }

            masterThreadState = !allThreadsDead;

        }
        return replicThreadState;
    }



    private void createReplicationThreads() throws ReplicationException {
        if (slavePools == null || masterPool == null ||
        slavePools.length == 0) {
            throw new  ReplicationException("ReplicationServer::createReplicationThreads: slavePools == null || masterPool == null || slavePools.length == 0");
        }

        try {
            if (replicThreads == null) {
                replicThreads = new RservThread[slavePools.length];
                replicRun = new ReplicationRunnable[slavePools.length];
            }

            boolean [] replicTreadState = getReplicThreadState();
            if (!masterThreadState) {
                resetMasterPool();
            }

            for (int i = 0; i < slavePools.length; i++) {
                if (!replicTreadState[i]) {
                    if (debug)
                        getLogger().debug("ReplicationServer::createReplicationThreads: before spawnReplicationThread #" + i);

                    resetSlavePool(i);
                    replicRun[i] = new ReplicationRunnable(serverProps,
                    connPoolProps,
                    masterPool,
                    slavePools[i]);
                    replicRun[i].setDebug(debug);
                    replicThreads[i] = new RservThread(replicRun[i]);
                    if (debug)
                        getLogger().debug("ReplicationServer::createReplicationThreads: before replicThreads[" +
                        i + "].start()");
                    getLogger().info("STARTING REPLICATION THREAD " + i);
                    replicThreads[i].start();
                }
            }
        } catch (Exception e) {
            throw new  ReplicationException("ReplicationServer::createReplicationThreads: "
            + e.toString());
        } finally {

        }
    }

    private void createConnPools() throws ReplicationException {
        if (connPoolProps == null ) {
            throw new  ReplicationException("ReplicationServer::createConnPools: connPoolProps == null");
        }

        try {
            Properties masterProps = connPoolProps.getMasterProperties();
            Properties [] slaveProps = connPoolProps.getSlaveProperties();
            if (slaveProps.length == 0) {
                throw new ReplicationException("ReplicationServer::createConnPools: slaveProps.length == 0");
            }

            if (debug)
                getLogger().debug("ReplicationServer::createConnPools: Got masterProps & [] slaveProps");
            if (masterPool == null) {
                masterPool = new ConnectionPool();
                if (debug)
                    getLogger().debug("ReplicationServer::createConnPools: CREATED MASTER CONNECTION POOL");

                masterPool.initialize(masterProps);
                masterPool.setLogger(getLogger());
                if (debug)
                    getLogger().debug("ReplicationServer::createConnPools: INITIALIZED MASTER CONNECTION POOL");
            }

            if (slavePools == null) {
                slavePools = new ConnectionPool[slaveProps.length];
                for (int i = 0; i < slaveProps.length; i++) {
                    if (debug)
                        getLogger().debug("ReplicationServer::createConnPools: trying to CREATE SLAVE CONNECTION POOL #" + i);
                    slavePools[i] = new ConnectionPool();
                    if (debug)
                        getLogger().debug("ReplicationServer::createConnPools: CREATED SLAVE CONNECTION POOL #" + i);

                    slavePools[i].initialize(slaveProps[i]);
                    slavePools[i].setLogger(getLogger());
                    if (debug)
                        getLogger().debug("ReplicationServer::createConnPools: INITIALIZED SLAVE CONNECTION POOL #" + i);
                }
            }
        } catch (Exception e) {
            getLogger().error("ReplicationServer::createConnPools: Cannot create pool: "
            + e.toString());
        } finally {

        }
    }

    private void startProcesses()
    throws ReplicationException {
        if (replic) {
            if (threads) {
                createReplicationThreads();
                if (localDebug)
                    System.out.println("ReplicationServer::run: GOING TO createReplicationThreads");
            } else {
                runReplicationThreads();
                if (localDebug)
                    System.out.println("ReplicationServer::run: GOING TO runReplicationThreads");
            }
        }

        if (clean) {
            if (threads) {
                if (localDebug)
                    System.out.println("ReplicationServer::run: GOING to createCleanThread");
                createCleanThread();
            } else {
                if (localDebug)
                    System.out.println("ReplicationServer::run: GOING to runCleanThread");
                runCleanThread();
            }
        }
    }

    private void resetMasterPool() {
        if (masterPool == null) {
            return;
        } else {
            try {
                resetPool(connPoolProps.getMasterProperties(), masterPool);
            } catch (Exception e) {
                getLogger().error("ReplicationServer::resetMasterPool" + e.toString());
            }
        }
    }

    private void resetSlavePool(int i) {
        if (slavePools == null || slavePools[i] == null) {
            return;
        } else {
            try {
                resetPool(connPoolProps.getSlaveProperties() [i], slavePools[i]);
            } catch (Exception e) {
                getLogger().error("ReplicationServer::resetSlavePool" + e.toString());
            }
        }
    }

    private boolean resetPool(Properties props, ConnectionPool cPool)
    throws ReplicationException {
        boolean rc = false;
        try {
            cPool.destroy();
            cPool.initialize(props);
            rc = true;
        } catch (Exception e) {
            getLogger().error("ReplicationServer::resetPool: Cannot reset pool: "
            + e.toString());
            rc = false;
        } finally {

        }
        return rc;
    }

    private void initializeLogger() throws Exception {
        if (replicHome == null) {
            throw new ReplicationException("ReplicationServer::intitializeLogger: replicHome == null");
        }
        LogRepl.initialize(replicHome);
    }

    private void initializeHome() {
        // try -Dreplic=replic.home option
        replicHome = System.getProperty("replic.home");
        if (replicHome == null || replicHome.trim().equals("")) {
            System.out.println("replic.home has not been initialized!");
            System.exit(2);
        } else {
            System.out.println("replic.home=" + replicHome);
        }
    }


    private void initializeProps() throws ReplicationException {
        try { // try -Dreplic.home option
            if (replicHome == null || replicHome.trim().equals("")) {
                throw new ReplicationException("ReplicationServer::initializeProps: replicHome is empty");
            }

            config = ReplicationConfig.getInstance();
            config.initialize(); // use default config file "replication.cfg"
            config.setDebug(debug);

            connPoolProps = new ConnectionPoolProps(config, false);
            if (debug) connPoolProps.printMasterProps() ;
            if (debug) connPoolProps.printSlaveProps() ;

            serverProps = new ServerProps(config, false);
            if (debug) serverProps.printProperties();

            debug = serverProps.getDebug();
            verbose = serverProps.getVerbose();

            if (verbose) {
                //config.printProps(null);
                connPoolProps.printMasterProps() ;
                connPoolProps.printSlaveProps() ;
                serverProps.printProperties();
            }
        } catch (Exception e) {
            throw new ReplicationException("ReplicationServer::initializeProps: "
            + e.toString());
        } finally {

        }
    }

    /**
     * <p>Called by the timer each time a clock cycle expires.
     * This gives us the opportunity to timeout connections
     */
    public synchronized void TimerEvent(Object object) {
        try {
            if (replic && !shutDown) {
                createReplicationThreads();
            }
        } catch (Exception e) { }

        try {
            if (clean && !shutDown) {
                createCleanThread();
            }
        } catch (Exception e) { }

    }

    /* Close everything cleanly
    */

    private void addHook() {
       /* Runtime.getRuntime().addShutdownHook(
            new Thread() {
                public void run() {
                    getLogger().info("Terminate all the threads & database connections...");
                    // Stop all the threads:
                    if (replicRun != null && replicRun.length > 0) {
                        for (int i = 0; i < replicRun.length; i++) {
                            getLogger().info("Terminate all the Replication threads...");
                            replicRun[i].setSignal(AbstractReplicationRunnable.STOP);
                            try {
                                replicThreads[i].stop();
                            } catch (Exception e) { }
                        }
                    }
                    if (cleanRun != null) {
                        getLogger().info("Terminate all the Cleanup threads...");
                        cleanRun.setSignal(AbstractReplicationRunnable.STOP);
                        try {
                            cleanThread.stop();
                        } catch (Exception e) { }
                    }

                    if (masterPool != null) {
                        masterPool.destroy();
                    }

                    if (slavePools != null && slavePools.length > 0) {
                        for (int i = 0; i < slavePools.length; i++) {
                            slavePools[i].destroy();
                        }
                    }

                    if (timer != null) {
                        try {
                                timer.stop();
                        } catch (Exception e) { }
                    }
                }
            }); */
    }

    /* jdk1.2.2
    */

    private class ShutdownHandler implements SignalHandler {
        public void handle(Signal sig) {
            System.err.println("CAUGHT:" + sig);

            try {
                getLogger().warn("SHUTDOWN");
                shutDown = true;

                getLogger().warn("SHUTDOWN WAIT");
                getLogger().info("Terminate all the threads & database connections...");

                // Stop all the threads:
                if (replicRun != null && replicRun.length > 0) {
                    getLogger().info("Terminate all the Replication threads...");
                    for (int i = 0; i < replicRun.length; i++) {
                        replicThreads[i].interrupt();
                        replicThreads[i].join();
                    }

                }

                if (cleanRun != null) {
                    getLogger().info("Terminate all the Cleanup threads...");
                    cleanThread.interrupt();
                    cleanThread.join();
                }

                if (timer != null) {
                    try {
                        timer.stop();
                    } catch (Exception e) { }
                }

                getLogger().warn("SHUTDOWN DONE");
               
            } catch (Exception xcp) {
                getLogger().log(xcp);
            }
        }
    }
}
