/*
 * Copyright 2013 NGDATA nv
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ngdata.hbaseindexer;

import com.ngdata.hbaseindexer.master.IndexerMaster;
import com.ngdata.hbaseindexer.model.api.IndexerProcessRegistry;
import com.ngdata.hbaseindexer.model.api.WriteableIndexerModel;
import com.ngdata.hbaseindexer.model.impl.IndexerModelImpl;
import com.ngdata.hbaseindexer.model.impl.IndexerProcessRegistryImpl;
import com.ngdata.hbaseindexer.supervisor.IndexerRegistry;
import com.ngdata.hbaseindexer.supervisor.IndexerSupervisor;
import com.ngdata.hbaseindexer.util.zookeeper.StateWatchingZooKeeper;
import com.ngdata.sep.SepModel;
import com.ngdata.sep.impl.SepModelImpl;
import com.ngdata.sep.util.io.Closer;
import com.sun.akuma.Daemon;
import com.sun.jersey.api.core.PackagesResourceConfig;
import com.sun.jersey.spi.container.servlet.ServletContainer;
import com.yammer.metrics.reporting.GangliaReporter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.util.Strings;
import org.apache.hadoop.net.DNS;
import org.mortbay.jetty.Connector;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.nio.SelectChannelConnector;
import org.mortbay.jetty.servlet.Context;
import org.mortbay.jetty.servlet.ServletHolder;

import java.util.concurrent.TimeUnit;

public class Main {
    private final static Log log = LogFactory.getLog(Main.class);
    private Connection tablePool;
    private WriteableIndexerModel indexerModel;
    private SepModel sepModel;
    private IndexerMaster indexerMaster;
    private IndexerSupervisor indexerSupervisor;
    private StateWatchingZooKeeper zk;
    private Server server;

    public static void main(String[] args) {
        Daemon d = new Daemon() {
            @Override
            public void init() throws Exception {
                init("/var/run/hbase-indexer.pid");
            }
        };
        try {
        if(d.isDaemonized()) {
            // perform initialization as a daemon
            // this involves in closing file descriptors, recording PIDs, etc.
            d.init();
        } else {
            // if you are already daemonized, no point in daemonizing yourself again,
            // so do this only when you aren't daemonizing.
            if(args != null && args.length > 0 && "daemon".equals(args[0])) {
                d.daemonize();
                System.exit(0);
            }
        }
        } catch (Exception e) {
            log.error("Error setting up hbase-indexer daemon", e);
            System.exit(1);
        }

        try {
            new Main().run(args);
        } catch (Exception e) {
            log.error(e);
            System.exit(1);
        }
    }

    public void run(String[] args) throws Exception {
        Runtime.getRuntime().addShutdownHook(new Thread(new ShutdownHandler()));

        Configuration conf = HBaseIndexerConfiguration.create();
        setupMetrics(conf);
        startServices(conf);
    }

    /**
     * @param conf the configuration object containing the hbase-indexer configuration, as well
     *             as the hbase/hadoop settings. Typically created using {@link HBaseIndexerConfiguration}.
     */
    public void startServices(Configuration conf) throws Exception {
        String hostname = Strings.domainNamePointerToHostName(DNS.getDefaultHost(
                conf.get("hbase.regionserver.dns.interface", "default"),
                conf.get("hbase.regionserver.dns.nameserver", "default")));

        log.debug("Using hostname " + hostname);

        String zkConnectString = conf.get(ConfKeys.ZK_CONNECT_STRING);
        int zkSessionTimeout = HBaseIndexerConfiguration.getSessionTimeout(conf);
        zk = new StateWatchingZooKeeper(zkConnectString, zkSessionTimeout);

        tablePool = ConnectionFactory.createConnection(conf);

        String zkRoot = conf.get(ConfKeys.ZK_ROOT_NODE);

        indexerModel = new IndexerModelImpl(zk, zkRoot);

        sepModel = new SepModelImpl(zk, conf);

        indexerMaster = new IndexerMaster(zk, indexerModel, conf, conf, zkConnectString,
                sepModel);
        indexerMaster.start();

        IndexerRegistry indexerRegistry = new IndexerRegistry();
        IndexerProcessRegistry indexerProcessRegistry = new IndexerProcessRegistryImpl(zk, conf);
        indexerSupervisor = new IndexerSupervisor(indexerModel, zk, hostname, indexerRegistry,
                indexerProcessRegistry, tablePool, conf);

        indexerSupervisor.init();
        startHttpServer();

    }

    private void startHttpServer() throws Exception {
        server = new Server();
        SelectChannelConnector selectChannelConnector = new SelectChannelConnector();
        selectChannelConnector.setPort(11060);
        server.setConnectors(new Connector[]{selectChannelConnector});

        PackagesResourceConfig packagesResourceConfig = new PackagesResourceConfig("com/ngdata/hbaseindexer/rest");

        ServletHolder servletHolder = new ServletHolder(new ServletContainer(packagesResourceConfig));
        servletHolder.setName("HBase-Indexer");


        Context context = new Context(server, "/", Context.NO_SESSIONS);
        context.addServlet(servletHolder, "/*");
        context.setContextPath("/");
        context.setAttribute("indexerModel", indexerModel);
        context.setAttribute("indexerSupervisor", indexerSupervisor);

        server.setHandler(context);
        server.start();
    }

    private void setupMetrics(Configuration conf) {
        String gangliaHost = conf.get(ConfKeys.GANGLIA_SERVER);
        if (gangliaHost != null) {
            int gangliaPort = conf.getInt(ConfKeys.GANGLIA_PORT, 8649);
            int interval = conf.getInt(ConfKeys.GANGLIA_INTERVAL, 60);
            log.info("Enabling Ganglia reporting to " + gangliaHost + ":" + gangliaPort);
            GangliaReporter.enable(interval, TimeUnit.SECONDS, gangliaHost, gangliaPort);
        }
    }

    public void stopServices() {
        log.debug("Stopping HTTP server");
        Closer.close(server);
        log.debug("Stopping indexer supervisor");
        Closer.close(indexerSupervisor);
        log.debug("Stopping indexer master");
        Closer.close(indexerMaster);
        log.debug("Stopping indexer model");
        Closer.close(indexerModel);
        log.debug("Stopping HBase table pool");
        Closer.close(tablePool);
        log.debug("Stopping ZooKeeper connection");
        Closer.close(zk);
    }

    public SepModel getSepModel() {
        return sepModel;
    }

    public WriteableIndexerModel getIndexerModel() {
        return indexerModel;
    }

    public IndexerSupervisor getIndexerSupervisor() {
        return indexerSupervisor;
    }

    public IndexerMaster getIndexerMaster() {
        return indexerMaster;
    }

    public class ShutdownHandler implements Runnable {
        @Override
        public void run() {
            stopServices();
        }
    }
}