# To use the web() option, you should do:
# * cd misc
# * npm install d3 zepto

from __future__ import print_function
import os, sys, time, json, random


streams = sys.argv[1:]
if not streams:
    print("run like: python dump-timing.py tx.json rx.json")
    sys.exit(1)
num_streams = len(streams)
labels = dict([(num, " "*num + "[%d]" % (num+1) + " "*(num_streams-1-num))
               for num in range(num_streams)])
abs_timeline = []
sides = []

for side,fn in enumerate(streams):
    with open(fn, "rb") as f:
        for (start, sent, finish, what, details) in json.load(f):
            abs_timeline.append( (start, sent, finish, side, what, details) )
    print("%s is %s" % (labels[side], fn))
    sides.append(os.path.basename(fn))

# relativize all timestamps
all_times = [e[0] for e in abs_timeline] + [e[2] for e in abs_timeline if e[2]]
all_times.sort()
earliest = all_times[0]
def rel(t):
    if t is None: return None
    return t - earliest
timeline = [ (rel(start), rel(sent), rel(finish), side, what, details)
             for (start, sent, finish, side, what, details)
             in abs_timeline ]
data = {}

# we pre-calculate the "lane" that each item uses, here in python, rather
# than leaving that up to the javascript.
data["lanes"] = ["proc", # 0 gets high-level events and spans: process start,
                         # imports, command dispatch, code established, key
                         # established, transit connected, process exit
                 "API", # 1 gets API call spans (apps usually only wait for
                        # one at a time, so they won't overlap): get_code,
                        # input_code, get_verifier, get_data, send_data,
                        # close
                 "wait", # 2 shows waiting-for-human: input code, get
                         # permission
                 "app", # 3: file-xfer events
                 "skt", # 4: websocket message send/receives
                 "misc", # 5: anything else
                 ]
data["bounds"] = {"min": rel(all_times[0]), "max": rel(all_times[-1]),
                  }
data["sides"] = sides
print("started at %s" % time.ctime(earliest))
print("duration %s seconds" % data["bounds"]["max"])
items = data["items"] = []

for num, (start, sent, finish, side, what, details) in enumerate(timeline):
    background = False
    if what in ["wormhole",]:
        # background region for wormhole lifetime
        lane = 0
        background = True
    elif what in ["process start", "import", "command dispatch",
                  "code established", "key established", "transit connected",
                  "exit"]:
        lane = 0
    elif what in ["API get_code", "API input_code", "API set_code",
                  "API get_verifier", "API get_data", "API send_data",
                  #"API get data", "API send data",
                  "API close"]:
        lane = 1
    elif details.get("waiting") in ["user", "crypto"]: # permission or math
        lane = 2
    elif what in ["tx file", "get ack", "rx file", "unpack zip", "send ack"]:
        lane = 3
    elif what in ["websocket"]:
        # connection establishment
        lane = 4
        background = True
    elif (what in ["welcome", "error"] # rendezvous message receives
          or what in ["allocate", "list", "get", "add", "deallocate"]
          # rendezvous message sends
          ):
        lane = 4
    else:
        lane = 5 # unknown

    if background:
        continue # disable until I figure out how to draw these better

    details_str = ", ".join(["%s=%s" % (name, details[name])
                             for name in sorted(details)])

    items.append({"side": side,
                  "lane": lane,
                  "start_time": start,
                  "server_sent": sent,
                  "finish_time": finish, # maybe None
                  "what": what,
                  "details": details,
                  "details_str": details_str,
                  "wiggle": random.randint(0,4),
                  })

    #if "waiting" in details:
    #    viz_className += " wait-%s" % details["waiting"]

from pprint import pprint
pprint(data)

here = os.path.dirname(__file__)
web_root = os.path.join(here, "web")
lib_root = os.path.join(here, "node_modules")
if not os.path.isdir(lib_root):
    print("Cannot find 'd3' and 'd3-tip' in misc/node_modules/")
    print("Please run 'npm install d3 d3-tip zepto' from the misc/ directory.")
    sys.exit(1)

def web():
    # set up a server that serves web/ at the root, plus a /data.json built
    # from {timeline}. Quit when it fetches /done .
    from twisted.web import resource, static, server
    from twisted.internet import reactor, endpoints
    ep = endpoints.serverFromString(reactor, "tcp:8066:interface=127.0.0.1")
    root = static.File(web_root)
    root.putChild("data.json", static.Data(json.dumps(data).encode("utf-8"),
                                           "application/json"))
    root.putChild("lib", static.File(lib_root))
    class Shutdown(resource.Resource):
        def render_GET(self, request):
            if 0:
                print("timeline ready, server shutting down")
                reactor.stop()
            return "shutting down"
    root.putChild("done", Shutdown())
    site = server.Site(root)
    ep.listen(site)
    import webbrowser
    def launch_browser():
        webbrowser.open("http://localhost:%d/timeline.html" % 8066)
        print("browser opened, waiting for shutdown")
    reactor.callLater(0, launch_browser)
    reactor.run()
web()