import renderdoc as rd
import threading
import time

# This sample is intended as an example of how to do remote capture and replay
# as well as using device protocols to automatically enumerate remote targets.
#
# It is not complete since it requires filling in with custom logic to select
# the executable and trigger the capture at the desired time
raise RuntimeError("This sample should not be run directly, read the source")

rd.InitialiseReplay(rd.GlobalEnvironment(), [])

protocols = rd.GetSupportedDeviceProtocols()

print(f"Supported device protocols: {protocols}")

# Protocols are optional - they allow automatic detection and management of
# devices.
if protocol_to_use is not None:
    # the protocol must be supported
    if protocol_to_use not in protocols:
        raise RuntimeError(f"{protocol_to_use} protocol not supported")

    protocol = rd.GetDeviceProtocolController(protocol_to_use)

    devices = protocol.GetDevices()

    if len(devices) == 0:
        raise RuntimeError(f"no {protocol_to_use} devices connected")

    # Choose the first device
    dev = devices[0]
    name = protocol.GetFriendlyName(dev)

    print(f"Running test on {dev} - named {name}")

    URL = protocol.GetProtocolName() + "://" + dev

    # Protocols can enumerate devices which are not supported. Capture/replay
    # is not guaranteed to work on these devices
    if not protocol.IsSupported(URL):
        raise RuntimeError(f"{dev} doesn't support capture/replay - too old?")

    # Protocol devices may be single-use and not support multiple captured programs
    # If so, trying to execute a program for capture is an error
    if not protocol.SupportsMultiplePrograms(URL):
        # check to see if anything is running. Just use the URL
        ident = rd.EnumerateRemoteTargets(URL, 0)

        if ident != 0:
            raise RuntimeError(f"{name} already has a program running on {ident}")
else:
    # If you're not using a protocol then the URL can simply be a hostname.
    # The remote server must be running already - how that is done is up
    # to you. Everything else will work the same over a normal TCP connection
    protocol = None
    URL = hostname

# Let's try to connect
status,remote = rd.CreateRemoteServerConnection(URL)

if status == rd.ReplayStatus.NetworkIOFailed and protocol is not None:
    # If there's just no I/O, most likely the server is not running. If we have
    # a protocol, we can try to start the remote server
    print("Couldn't connect to remote server, trying to start it")

    status = protocol.StartRemoteServer(URL)

    if status != rd.ReplayStatus.Succeeded:
        raise RuntimeError(f"Couldn't launch remote server, got error {str(status)}")

    # Try to connect again!
    status,remote = rd.CreateRemoteServerConnection(URL)

if status != rd.ReplayStatus.Succeeded:
    raise RuntimeError(f"Couldn't connect to remote server, got error {str(status)}")

# We now have a remote connection. This works regardless of whether it's a device
# with a protocol or not. In fact we are done with the protocol at this point
protocol = None

print("Got connection to remote server")

# GetHomeFolder() gives you a good default path to start with.
# ListFolder() lists the contents of a folder and can recursively
# browse the remote filesystem.
home = remote.GetHomeFolder()
paths = remote.ListFolder(home)

print(f"Executables in home folder '{home}':")

for p in paths:
    print("  - " + p.filename)

# Select your executable, perhaps hardcoded or browsing using the above
# functions
exe,workingDir,cmdLine,env,opts = select_executable()

print(f"Running {exe}")

result = remote.ExecuteAndInject(exe, workingDir, cmdLine, env, opts)

if result.status != rd.ReplayStatus.Succeeded:
    remote.ShutdownServerAndConnection()
    raise RuntimeError(f"Couldn't launch {exe}, got error {str(result.status)}")

# Spin up a thread to keep the remote server connection alive while we make a capture,
# as it will time out after 5 seconds of inactivity
def ping_remote(remote, kill):
    success = True
    while success and not kill.is_set():
        success = remote.Ping()
        time.sleep(1)

kill = threading.Event()
ping_thread = threading.Thread(target=ping_remote, args=(remote,kill))
ping_thread.start()

# Create target control connection
target = rd.CreateTargetControl(URL, result.ident, 'remote_capture.py', True)

if target is None:
    kill.set()
    ping_thread.join()
    remote.ShutdownServerAndConnection()
    raise RuntimeError(f"Couldn't connect to target control for {exe}")

print("Connected - waiting for desired capture")

# Wait for the capture condition we want
capture_condition()

print("Triggering capture")

target.TriggerCapture(1)

# Pump messages, keep waiting until we get a capture message. Time out after 30 seconds
msg = None
start = time.clock()
while msg is None or msg.type != rd.TargetControlMessageType.NewCapture:
    msg = target.ReceiveMessage(None)

    if time.clock() - start > 30:
        break

# Close the target connection, we're done either way
target.Shutdown()
target = None

# Stop the background ping thread
kill.set()
ping_thread.join()

# If we didn't get a capture, error now
if msg.type != rd.TargetControlMessageType.NewCapture:
    remote.ShutdownServerAndConnection()
    raise RuntimeError("Didn't get new capture notification after triggering capture")

cap_path = msg.newCapture.path
cap_id = msg.newCapture.captureId

print(f"Got new capture at {cap_path} which is frame {msg.newCapture.frameNumber} with {msg.newCapture.api}")

# We could save the capture locally
# remote.CopyCaptureFromRemote(cap_path, local_path, None)


# Open a replay. It's recommended to set no proxy preference, but you could
# call remote.LocalProxies and choose an index.
#
# The path must be remote - if the capture isn't freshly created then you need
# to copy it with remote.CopyCaptureToRemote()
status,controller = remote.OpenCapture(rd.RemoteServer.NoPreference, cap_path, rd.ReplayOptions(), None)

if status != rd.ReplayStatus.Succeeded:
    remote.ShutdownServerAndConnection()
    raise RuntimeError(f"Couldn't open {cap_path}, got error {str(result.status)}")

# We can now use replay as normal.
#
# The replay is tunnelled over the remote connection, so you don't have to keep
# pinging the remote connection while using the controller. Use of the remote
# connection and controller can be interleaved though you should only access
# them from one thread at once. If they are both unused for 5 seconds though,
# the timeout will happen, so if the controller is idle it's advisable to ping
# the remote connection

sampleCode(controller)

print("Shutting down")

controller.Shutdown()

# We can still use remote here - e.g. capture again, replay something else,
# save the capture, etc

remote.ShutdownServerAndConnection()

rd.ShutdownReplay()
