import subprocess
import os
import time


class Server():
    def __init__(self, first_server_p, first_monitor_p, n_servers, no_render=True, no_realtime=True) -> None:
        try:
            import psutil
            self.check_running_servers(psutil, first_server_p, first_monitor_p, n_servers)
        except ModuleNotFoundError:
            print("Info: Cannot check if the server is already running, because the psutil module was not found")

        self.first_server_p = first_server_p
        self.n_servers = n_servers
        self.rcss_processes = []
        first_monitor_p = first_monitor_p + 100

        # makes it easier to kill test servers without affecting train servers
        cmd = "rcssservermj"
        render_arg = "--no-render" if no_render else ""
        realtime_arg = "--no-realtime" if no_realtime else ""
        for i in range(n_servers):
            port = first_server_p + i
            mport = first_monitor_p + i

            server_cmd = f"{cmd} -c {port} -m {mport} {render_arg} {realtime_arg}".strip()

            proc = subprocess.Popen(
                server_cmd.split(),
                stdout=subprocess.DEVNULL,
                stderr=subprocess.STDOUT,
                start_new_session=True
            )

            # Avoid startup storm when launching many servers at once.
            time.sleep(0.03)

            rc = proc.poll()
            if rc is not None:
                raise RuntimeError(
                    f"rcssservermj exited early (code={rc}) on server port {port}, monitor port {mport}"
                )

            self.rcss_processes.append(proc)

    def check_running_servers(self, psutil, first_server_p, first_monitor_p, n_servers):
        ''' Check if any server is running on chosen ports '''
        found = False
        p_list = [p for p in psutil.process_iter() if p.cmdline() and "rcssservermj" in " ".join(p.cmdline())]
        range1 = (first_server_p, first_server_p + n_servers)
        range2 = (first_monitor_p, first_monitor_p + n_servers)
        bad_processes = []

        for p in p_list:
            # currently ignoring remaining default port when only one of the ports is specified (uncommon scenario)
            ports = [int(arg) for arg in p.cmdline()[1:] if arg.isdigit()]
            if len(ports) == 0:
                ports = [60000, 60100]  # default server ports (changing this is unlikely)

            conflicts = [str(port) for port in ports if (
                    (range1[0] <= port < range1[1]) or (range2[0] <= port < range2[1]))]

            if len(conflicts) > 0:
                if not found:
                    print("\nThere are already servers running on the same port(s)!")
                    found = True
                bad_processes.append(p)
                print(f"Port(s) {','.join(conflicts)} already in use by \"{' '.join(p.cmdline())}\" (PID:{p.pid})")

        if found:
            print()
            while True:
                inp = input("Enter 'kill' to kill these processes or ctrl+c to abort. ")
                if inp == "kill":
                    for p in bad_processes:
                        p.kill()
                    return

    def kill(self):
        for p in self.rcss_processes:
            p.kill()
        print(f"Killed {self.n_servers} rcssservermj processes starting at {self.first_server_p}")
