import subprocess
import os
import shlex
import logging
import re
import string
import time
import sys
from datetime import datetime

debug_stdout = None

global_env = os.environ

# don't get distracted from other proxy settings, we have our own thank you
global_env[ 'http_proxy' ] = ''
global_env[ 'https_proxy' ] = ''

# TODO: would be nice to cmd output to debug log, and write to debug_stdout as it goes,
#       unfontuntally that will require some pipes and magic, Popen expets a .fileno on the writer sent to it

printable = re.compile( r'[^{0}]'.format( string.printable ) )


class ExecutionException( Exception ):
  pass


def open_output( filename ):
  global debug_stdout
  debug_stdout = open( filename, 'w' )


def close_output():
  global debug_stdout
  debug_stdout.close()
  debug_stdout = None


# NOTE: if there is stdin, the log will not follow the log
def _execute( cmd, dir, stdin, extra_env, ignore_stderr=False ):
  logging.info( 'procutils: executing "{0}" in "{1}"'.format( cmd, dir ) )
  debug_stdout.write( '\n=================================================\n' )
  debug_stdout.write( '{0}\n'.format( datetime.utcnow() ) )
  debug_stdout.write( 'Executing:\n' )
  debug_stdout.write( cmd )

  if stdin:
    debug_stdout.write( '\nstdin:\n' )
    debug_stdout.write( stdin )

  debug_stdout.write( '\n-------------------------------------------------\n' )
  debug_stdout.flush()

  env = global_env.copy()
  if extra_env is not None:
    env.update( extra_env )

  args = {
            'args': shlex.split( cmd ),
            'cwd': dir,
            'env': env,
            'stdout': subprocess.PIPE
         }

  if ignore_stderr:
    args[ 'stderr' ] = subprocess.DEVNULL
  else:
    args[ 'stderr' ] = subprocess.STDOUT

  if sys.version_info.minor >= 7:  # assuming major is 3, pretty sure there will be rework for version python 4, when ever that is
    args[ 'text' ] = True
  else:
    args[ 'universal_newlines' ] = True

  result_buff = ''
  try:
    if stdin:
      proc = subprocess.Popen( stdin=subprocess.PIPE, **args )
      ( stdout, _ ) = proc.communicate( stdin )
      debug_stdout.write( stdout )
      result_buff = stdout[ -100000: ]

    else:
      proc = subprocess.Popen( **args )
      os.set_blocking( proc.stdout.fileno(), False )
      while proc.returncode is None:
        try:
          proc.wait( timeout=5 )
          os.set_blocking( proc.stdout.fileno(), True )  # the process finished, make sure we get everything
        except subprocess.TimeoutExpired:
          pass

        buff = proc.stdout.read( 40960 )
        while buff:
          debug_stdout.write( buff )
          result_buff = ( result_buff + buff )[ -100000: ]
          buff = proc.stdout.read( 40960 )

        debug_stdout.flush()

  except Exception as e:
    raise ExecutionException( 'Exception {0} while executing "{1}"'.format( e, cmd ) )

  debug_stdout.write( '\n-------------------------------------------------\n' )
  debug_stdout.write( 'rc: {0}\n'.format( proc.returncode ) )
  debug_stdout.write( '{0}\n'.format( datetime.utcnow() ) )
  debug_stdout.flush()

  logging.info( 'procutils: returned "{0}"'.format( proc.returncode ) )

  return ( printable.sub( '', result_buff ), proc.returncode )


def execute( cmd, dir=None, stdin=None, extra_env=None, retry_rc_list=None, ignore_stderr=False ):
  retry = 10
  while retry:
    ( _, rc ) = _execute( cmd, dir, stdin, extra_env, ignore_stderr )
    if rc == 0:
      return

    if retry_rc_list is None or rc not in retry_rc_list:
      break

    retry -= 1
    time.sleep( 30 )

  raise ExecutionException( 'Error Executing "{0}", rc: {1}'.format( cmd, rc ) )


def execute_rc( cmd, dir=None, stdin=None, extra_env=None, ignore_stderr=False  ):
  ( results, rc ) = _execute( cmd, dir, stdin, extra_env, ignore_stderr )
  return rc


def execute_lines_rc( cmd, dir=None, stdin=None, extra_env=None, ignore_stderr=False  ):
  ( results, rc ) = _execute( cmd, dir, stdin, extra_env, ignore_stderr )
  return ( results.splitlines(), rc )


def execute_lines( cmd, dir=None, stdin=None, extra_env=None, ignore_stderr=False  ):
  ( results, rc ) = _execute( cmd, dir, stdin, extra_env, ignore_stderr )
  if rc != 0:
    raise ExecutionException( 'Error Executing "{0}", rc: {1}'.format( cmd, rc ) )

  return results.splitlines()
