#!/usr/bin/env python
import os, subprocess, sys, signal, termios, atexit, socket, getopt

def testament_child_death():
    rv = os.fork()
    if rv == 0:
        os.kill(os.getpid(), signal.SIGSTOP)
        os._exit(0)
    os.waitpid(rv, os.WUNTRACED)

PREFIX = subprocess.Popen(". $(pwd)/.configuration && echo $prefix", shell = True, stdout=subprocess.PIPE).communicate()[0].rstrip()

base_direct_port = 12000
base_api_port = 9000
base_couch_port = 9500
base_mccouch_port = 13000

LOGLEVELS = ["debug", "info", "warn", "error", "critical"]

def setup_path():
    def ebin_search(path):
        return subprocess.Popen(["find", path, "-name", "ebin", "-type", "d"],
                                stdout=subprocess.PIPE).communicate()[0].split()

    path = ebin_search(".")
    couchpath = ebin_search("{0}/lib/couchdb/erlang/lib".format(PREFIX))
    couch_plugins = ebin_search("{0}/lib/couchdb/plugins".format(PREFIX))

    if len(couchpath) == 0:
       sys.exit("Couch libs wasn't found.\nCan't handle it")

    return couchpath + path + couch_plugins

def mk_node_couch_config(i):
    try:
        os.mkdir("couch")
    except os.error:
        pass

    with open("couch/n_{0}_conf.ini".format(i), "w") as f:
        f.write("[httpd]\n")
        f.write("port={0}\n".format(base_couch_port + i))
        f.write("[couchdb]\n")
        f.write("database_dir={0}/data/n_{1}/data\n".format(os.getcwd(), i))
        f.write("view_index_dir={0}/data/n_{1}/data\n".format(os.getcwd(), i))
        f.write("max_dbs_open=10000\n")

def couch_configs(i):
    mk_node_couch_config(i)
    return ["{0}/etc/couchdb/default.ini".format(PREFIX),
            "{0}/etc/couchdb/default.d/capi.ini".format(PREFIX),
            "{0}/etc/couchdb/default.d/geocouch.ini".format(PREFIX),
            "couch/n_{0}_conf.ini".format(i)]

def os_specific(args, params):
    """Add os-specific junk to the cluster startup."""
    import platform

    if platform.system() == 'Darwin':
        import resource
        ## OS X has a pretty tiny default fd limit.  Let's increase it
        resource.setrlimit(resource.RLIMIT_NOFILE, (2048, 2048))
        ## Also, we build stuff in kind of dumb ways, so fix the path.
        dypath = ("%(pwd)s/../install/lib/memcached:"
                  "%(pwd)s/../install/lib:/usr/local/lib") % {'pwd': os.getcwd()}
        params['env'] = {"DYLD_LIBRARY_PATH": dypath,
                         "ERL_MAX_PORTS": "2048"}
        params['env'].update(os.environ)

def start_cluster(num_nodes, start_index, host, extra_args, args_prefix):
    ebin_path = setup_path()

    def start_node(i):
        logdir = "logs/n_{0}".format(i)
        try:
            os.makedirs(logdir)
        except:
            pass
        try:
            os.stat("data/n_{0}/mnesia".format(i))
        except:
            os.makedirs("data/n_{0}/mnesia".format(i))
        args = args_prefix + ["erl", "+S", "16:16", "+sbtu", "+P", "327680", "-pa"] + ebin_path + [
            "-setcookie", "nocookie",
            "-kernel", "inet_dist_listen_min", "21100",
            "inet_dist_listen_max", "21199",
            "error_logger", "false",
            "-sasl", "sasl_error_logger", "false",
            "-couch_ini"] + couch_configs(i) + [
            "-ns_server", "config_path", '"etc/static_config.in"',
            "error_logger_mf_dir", '"{0}"'.format(logdir),
            "error_logger_mf_maxbytes", "10485760",
            "error_logger_mf_maxfiles", "10",
            "dont_suppress_stderr_logger", "true",
            "path_config_etcdir", '"priv"',
            "path_config_bindir", '"{0}"'.format(PREFIX+"/bin"),
            "path_config_libdir", '"{0}"'.format(PREFIX+"/lib"),
            "path_config_datadir", '"data/n_{0}"'.format(i),
            "path_config_tmpdir", '"tmp/"',
            "rest_port", str(base_api_port + i),
            "mccouch_port", str(base_mccouch_port + i),
            "memcached_port", str(base_direct_port + i * 2),
            "moxi_port", str(base_direct_port + i * 2 + 1),
            "memcached_dedicated_port", str(base_direct_port - 1 - i),
            "short_name", '"n_{0}"'.format(i)
            ] + extra_args

        params = {}

        if i > start_index:
            args += ['-noshell']
            params['stdin'] = subprocess.PIPE

        os_specific(args, params)

        if not params.has_key('env'):
            params['env'] = {}
            params['env'].update(os.environ)
        path = params['env']['PATH']
        path = (PREFIX+"/bin") + os.pathsep + path
        if not params['env'].has_key('ERL_FULLSWEEP_AFTER'):
            params['env']['ERL_FULLSWEEP_AFTER'] = '512'
        params['env']['PATH'] = path
        return subprocess.Popen(args, **params)

    return [start_node(i + start_index) for i in xrange(num_nodes)]

def usage():
    sys.exit("Usage: {0} [--nodes=N] [--dont-rename] [--dont-start] "
             "[--interactive] [--static-cookie] [--start-index=N] "
             "[--static-cookie] [--host=H] [--loglevel=L] "
             "[ns_server args]".format(sys.argv[0]))

def maybe_spawn_epmd():
    try:
        socket.create_connection(("127.0.0.1", 4369)).close()
    except socket.error:
        print("Spawning epmd...\n")
        subprocess.Popen("erl -noshell -setcookie nocookie -sname init -run init stop 2>&1 > /dev/null",
                         shell = True).communicate()

def find_primary_addr():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.connect(("8.8.8.8", 56))
        addr, port = s.getsockname()
        return addr
    except socket.error:
        return None
    finally:
        s.close()

def main():
    try:
        optlist, args = getopt.gnu_getopt(sys.argv[1:], "hn:i",
                                          ["help", "start-index=", "nodes=",
                                           "dont-rename", "interactive",
                                           "static-cookie", "dont-start",
                                           "host=", "loglevel=",
                                           "prepend-extras"])
    except getopt.GetoptError, err:
        # print help information and exit:
        print str(err) # will print something like "option -a not recognized"
        usage()
        sys.exit(2)

    dont_rename = False
    dont_start = False
    static_cookie = False
    interactive_shell = False
    start_index = 0
    num_nodes = 1
    prepend_extras = False
    host = "127.0.0.1"
    loglevel = 'debug'

    for o, a in optlist:
        if o in ("--nodes", "-n"):
            num_nodes = int(a)
        elif o in ("--interactive", "-i"):
            interactive_shell = True
        elif o == '--dont-start':
            dont_start = True
        elif o == '--host':
            host = a
        elif o == '--start-index':
            start_index = int(a)
        elif o == '--dont-rename':
            dont_rename = True
        elif o in ("--help", "-h"):
            usage()
            exit(0)
        elif o in("--static-cookie"):
            static_cookie = True
        elif o == '--loglevel':
            loglevel = a
        elif o == "--prepend-extras":
            prepend_extras = True
        else:
            assert False, "unhandled options"

    nodes = []
    terminal_attrs = None

    def kill_nodes(*args):
        for n in nodes:
            try:
                n.kill()
                n.wait()
            except OSError:
                pass
        if terminal_attrs != None:
            termios.tcsetattr(sys.stdin, termios.TCSANOW, terminal_attrs)

    testament_child_death()

    atexit.register(kill_nodes)

    try:
        terminal_attrs = termios.tcgetattr(sys.stdin)
    except:
        pass

    maybe_spawn_epmd()

    extra_args = []
    if not dont_rename:
        primary_addr = find_primary_addr()
        if primary_addr == None:
            print("was unable to detect 'internet' address of this machine."
                  + " node rename will be disabled")
        else:
            extra_args += ["rename_ip", '"' + primary_addr + '"']

    extra_args += args[1:]
    if prepend_extras:
        prepend_args = args[0:]
    else:
        prepend_args = []
        extra_args += args[0:]

    if static_cookie:
        extra_args += ["-ns_server", "dont_reset_cookie", "true"]

    if not dont_start:
        extra_args += ["-run", "ns_bootstrap"]

    if not interactive_shell:
        extra_args += ["-noinput"]

    if loglevel not in LOGLEVELS:
        print "Valid log levels are the following: %s" % ', '.join(LOGLEVELS)
        sys.exit(1)
    extra_args += ["-ns_server", "loglevel_stderr", loglevel]

    nodes = start_cluster(num_nodes, start_index, host, extra_args, prepend_args)

    for node in nodes:
        node.wait()


if __name__ == '__main__':
    main()
