import sys, os

# Hack to figure out appropriate paths and add them to sys.path. Will
# replace this with something saner eventually.

POSSIBLE_SFSNET_PATHS = ["/usr/home/dan/chord-build/sfsnet", # m-a
                         "/home/am3/drkp/build/sfsnet",  # pdos
                         "/home/drkp/build/sfsnet"] # flute
for path in POSSIBLE_SFSNET_PATHS:
    if os.path.exists(path):
        sys.path.append(os.path.join(path, "devel/"))
        sys.path.append(os.path.join(path, "svc/"))
        sys.path.append(os.path.join(path, "arpeggio/"))

import asyncore
from optparse import OptionParser
from twisted.python import util, threadable
from twisted.internet import reactor, defer, task
from twisted.spread import pb
from index import *
from indexserver import *
from simplesyncer import SimpleSyncer
from chord import Chord
from connectioncache import ConnectionCache
from utils import *
from web import *
DEBUG = True
PORTNUM = 44268

EXPIRE_TIME = 3600                      # In seconds
KSS_K = 2
NUM_REPLICAS = 2

# Make Twisted threadable
threadable.init(1)

# Single instances of global services
chord = Chord()
connectionCache = ConnectionCache(300)
indexCollection = IndexCollection()
indexServer = None


@typechecked(str, int, str, int)
def startChord(localHost, localPort,
               wellKnownHost, wellKnownPort):
    """Start the Chord daemon, join a Chord ring, and return an
    interface.

    Takes as arguments the local host (as string) and port number to
    bind to, as well as the host and port for another node in the
    ring. To bootstrap, these can be the same as the local
    host. Returns a deferred Chord object."""
    return chord.startChord(localHost, localPort,
                            wellKnownHost, wellKnownPort)

@typechecked(int)
def startArpRPC(portnum):
    global indexServer, indexCollection, chord, connectionCache
    if chord.status != Chord.STATUS_CONNECTED:
        raise TypeError("Chord client is not connected")

    print "Starting arpd RPC on port", portnum
    indexServer = IndexServer(EXPIRE_TIME, EXPIRE_TIME,
                              KSS_K, NUM_REPLICAS,
                              indexCollection,
                              chord, connectionCache,
                              SimpleSyncer())
    factory = pb.PBServerFactory(indexServer)
    
    if DEBUG:
        factory.unsafeTracebacks = 1
        
    reactor.listenTCP(portnum, factory)

@typechecked(int)
def startArpWeb(portnum):
    global indexServer, indexCollection, chord, connectionCache
    if chord.status != Chord.STATUS_CONNECTED:
        raise TypeError("Chord client is not connected")
    if indexServer == None:
        raise TypeError("Index server not created")
    
    print "Starting arpd web interface on port", portnum
    reactor.listenTCP(portnum, SearchServer(indexServer))
                      
def testSuccList():
    print "Testing successor/predecessor list"
    if chord.status == Chord.STATUS_CONNECTED:
        dfd = chord.getSuccList(chord.myVnode())
        #dfd.addCallback(printArg)
        dfd.addErrback(printArg)
        dfd2 = chord.getPredList(chord.myVnode())
        #dfd2.addCallback(printArg)
        dfd2.addErrback(printArg)

def main():
    usage = "usage: %prog [options]"

    parser = OptionParser(usage)
    parser.add_option("-b", action="store_true", dest="bootstrap",
                      default=False,
                      help="indicates this is a bootstrap node (exclusive"
                      " with -j)")
    def join_arg(option, opt, value, parser):
        """Parse host:port value of join option"""
        parts = value.split(":")
        if len(parts) != 2:
            raise optparse.OptionValueError, \
                  "option %s: must specify host:port" % opt
        host, port = parts
        port = int(port)
        parser.values.join = (host, port)
    parser.add_option("-j", type="string", action="callback",
                      callback=join_arg, dest="join", metavar="HOST:PORT",
                      help="specifies existing Chord node to join with")
    parser.add_option("-p", type="int", dest="chordport",
                      help="indicates local port to listen on for Chord"
                      " connections (defaults to joined node's port)")
    parser.add_option("-l", type="string", dest="myname", default="",
                      help="specifies local host name to bind to")

    (options, args) = parser.parse_args()
    if args:
        parser.error("too many arguments specified")
    if not ((options.join is None) ^ (not options.bootstrap)):
        parser.error("must specify either -j or -b")
    if options.chordport is None:
        if options.join is None:
            parser.error("-p is required when -j is not specified")
        options.chordport = options.join[1]


    if options.bootstrap:
        d = startChord(options.myname, options.chordport,
                       options.myname, options.chordport)
    else:
        d = startChord(options.myname, options.chordport,
                       options.join[0], options.join[1])

    d.addCallback(lambda obj: startArpRPC(options.chordport+1))
    d.addCallback(lambda x: startArpWeb(options.chordport+2))
    d.addErrback(printArg)

#    dbgtask = task.LoopingCall(testSuccList)
#    dbgtask.start(5)
    reactor.run()

if __name__ == '__main__':
    main()
