import os
import re
import subprocess

from functools import partial
from multiprocessing.dummy import Pool

import sys

from gooey.gui.pubsub import pub
from gooey.gui.util.casting import safe_float
from gooey.gui.util.functional import unit, bind
from gooey.gui.util.taskkill import taskkill


class ProcessController(object):
  def __init__(self, progress_regex, progress_expr):
    self._process = None
    self.progress_regex = progress_regex
    self.progress_expr = progress_expr

  def was_success(self):
    self._process.communicate()
    return self._process.returncode == 0

  def poll(self):
    if not self._process:
      raise Exception('Not started!')
    self._process.poll()

  def stop(self):
    if self.running():
      taskkill(self._process.pid)

  def running(self):
    return self._process and self.poll() is None

  def run(self, command):
    env = os.environ.copy()
    env["GOOEY"] = "1"
    self._process = subprocess.Popen(
      command.encode(sys.getfilesystemencoding()),
      bufsize=1, stdout=subprocess.PIPE,
      stderr=subprocess.STDOUT, shell=True, env=env)
    Pool(1).apply_async(self._forward_stdout, (self._process,))

  def _forward_stdout(self, process):
    '''
    Reads the stdout of `process` and forwards lines and progress
    to any interested subscribers
    '''
    while True:
      line = process.stdout.readline()
      if not line:
        break
      pub.send_message('console_update', msg=line)
      pub.send_message('progress_update', progress=self._extract_progress(line))
    pub.send_message('execution_complete')

  def _extract_progress(self, text):
    '''
    Finds progress information in the text using the
    user-supplied regex and calculation instructions
    '''
    # monad-ish dispatch to avoid the if/else soup
    find = partial(re.search, string=text.strip())
    regex = unit(self.progress_regex)
    match = bind(regex, find)
    result = bind(match, self._calculate_progress)
    return result

  def _calculate_progress(self, match):
    '''
    Calculates the final progress value found by the regex
    '''
    if not self.progress_expr:
      return safe_float(match.group(1))
    else:
      return self._eval_progress(match)

  def _eval_progress(self, match):
    '''
    Runs the user-supplied progress calculation rule
    '''
    _locals = {k: safe_float(v) for k, v in match.groupdict().items()}
    if "x" not in _locals:
      _locals["x"] = [safe_float(x) for x in match.groups()]
    try:
      return int(eval(self.progress_expr, {}, _locals))
    except:
      return None