import sys
import copy
import time
import operator

import px_process
import px_terminal


def adjust_cpu_times(current, baseline):
    """
    Identify processes in current that are also in baseline.

    For all matches, substract the baseline process' CPU time usage from the
    current process' one.

    This way we get CPU times computed from when "px --top" was started, rather
    than from when each process was started.

    Neither current nor baseline are changed by this function.
    """
    pid2proc = {}
    for proc in current:
        pid2proc[proc.pid] = proc

    for baseline_proc in baseline:
        current_proc = pid2proc.get(baseline_proc.pid)
        if current_proc is None:
            # This process is newer than the baseline
            continue

        if current_proc.start_time != baseline_proc.start_time:
            # This PID has been reused
            continue

        if current_proc.cpu_time_seconds is None:
            # We can't substract from None
            continue

        if baseline_proc.cpu_time_seconds is None:
            # We can't substract None
            continue

        current_proc = copy.copy(current_proc)
        current_proc.set_cpu_time_seconds(
            current_proc.cpu_time_seconds - baseline_proc.cpu_time_seconds)
        pid2proc[current_proc.pid] = current_proc

    return pid2proc.values()


def top():
    baseline = px_process.get_all()
    while True:
        adjusted = adjust_cpu_times(px_process.get_all(), baseline)

        window_size = px_terminal.get_window_size()
        if window_size is None:
            sys.stderr.write("Cannot find terminal window size, are you on a terminal?\n")
            exit(1)

        # Sort by CPU time used, then most interesting first
        ordered = px_process.order_best_first(adjusted)
        ordered = sorted(ordered, key=operator.attrgetter('cpu_time_seconds'), reverse=True)
        rows, columns = window_size
        lines = px_terminal.to_screen_lines(ordered, columns)

        # Clear the screen and move cursor to top left corner:
        # https://en.wikipedia.org/wiki/ANSI_escape_code
        #
        # Note that some experimentation was involved in coming up with this
        # exact sequence; if you do first "clear" then "home" for example, the
        # contents of the previous screen gets added to the scrollback buffer,
        # which isn't what we want. Tread carefully if you intend to change
        # these.
        CSI = "\x1b["
        sys.stderr.write(CSI + "1J")
        sys.stderr.write(CSI + "H")

        sys.stdout.write("\n".join(lines[0:rows]))
        sys.stdout.flush()

        # FIXME: Interrupt sleep and iterate if terminal window is resized
        # FIXME: Interrupt sleep and terminate if user presses "q"
        time.sleep(1)

    return

@@ -27,28 +27,41 @@
 ort 
-time%0Aimport operator
+operator%0Aimport select%0Aimport tty
 %0A%0Aim
@@ -97,16 +97,69 @@
 minal%0A%0A%0A
+# FIXME: Test run on bash%0A# FIXME: Test run on Linux%0A
 def adju
@@ -1442,16 +1442,371 @@
 )%0A%0A%0Adef 
+getch(timeout_seconds=0):%0A    %22%22%22%0A    Wait at most timeout_seconds for a character to become available on stdin.%0A%0A    Returns the character, or None on timeout.%0A    %22%22%22%0A    char = None%0A    can_read_from = select.select(%5Bsys.stdin%5D, %5B%5D, %5B%5D, timeout_seconds)%5B0%5D%0A    if len(can_read_from) %3E 0:%0A        char = can_read_from%5B0%5D.read(1)%0A%0A    return char%0A%0A%0Adef _
 top():%0A 
@@ -2685,16 +2685,32 @@
 t %22clear
+ the full screen
 %22 then %22
@@ -2714,16 +2714,26 @@
 n %22home%22
+%0A        #
  for exa
@@ -2741,26 +2741,16 @@
 ple, the
-%0A        #
  content
@@ -2787,24 +2787,34 @@
 added to the
+%0A        #
  scrollback 
@@ -2820,26 +2820,16 @@
  buffer,
-%0A        #
  which i
@@ -2869,16 +2869,26 @@
 y if you
+%0A        #
  intend 
@@ -2896,26 +2896,16 @@
 o change
-%0A        #
  these.%0A
@@ -3000,16 +3000,86 @@
 + %22H%22)%0A%0A
+        # We need both %5Cr and %5Cn when the TTY is in tty.setraw() mode%0A
         
@@ -3097,16 +3097,18 @@
 write(%22%5C
+r%5C
 n%22.join(
@@ -3238,97 +3238,213 @@
     
-# FIXME: Interrupt sleep and terminate if user presses %22q%22%0A        time.slee
+char = getch(timeout_seconds=1)%0A        if char == 'q':%0A            return%0A%0A%0Adef top():%0A    fd = sys.stdin.fileno()%0A    try:%0A        tty.setraw(fd)%0A        return _to
 p(
-1
 )%0A
-%0A    return
+    finally:%0A        tty.setcbreak(fd)
 %0A

Terminate if user presses "q"