from __future__ import print_function
import os
import sys
import subprocess
from os import environ as env
from shellpython import config

_colorama_intialized = False
_colorama_available = True
try:
    import colorama
    from colorama import Fore, Style
except ImportError:
    _colorama_available = False


def _is_colorama_enabled():
    return _colorama_available and config.COLORAMA_ENABLED


def _print_stdout(text):
    print(text)


def _print_stderr(text):
    print(text, file=sys.stderr)

# print all stdout of executed command
_PARAM_PRINT_STDOUT = 'p'

# print all stderr of executed command
_PARAM_PRINT_STDERR = 'e'

# runs command in interactive mode when user can read output line by line and send to stdin
_PARAM_INTERACTIVE = 'i'

# no throw mode. With this parameter user explicitly says that NonZeroReturnCodeError must not be thrown for this
# specific command. It may be useful if for some reason this command does not return 0 even for successful run
_PARAM_NO_THROW = 'n'


def exe(cmd, params):
    """This function runs after preprocessing of code. It actually executes commands with subprocess

    :param cmd: command to be executed with subprocess
    :param params: parameters passed before ` character, i.e. p`echo 1 which means print result of execution
    :return: result of execution. It may be either Result or InteractiveResult
    """

    global _colorama_intialized
    if _is_colorama_enabled() and not _colorama_intialized:
        _colorama_intialized = True
        colorama.init()

    if config.PRINT_ALL_COMMANDS:
        if _is_colorama_enabled():
            _print_stdout(Fore.GREEN + '>>> ' + cmd + Style.RESET_ALL)
        else:
            _print_stdout('>>> ' + cmd)

    if _is_param_set(params, _PARAM_INTERACTIVE):
        return _create_interactive_result(cmd, params)
    else:
        return _create_result(cmd, params)


def _is_param_set(params, param):
    return True if params.find(param) != -1 else False


class ShellpyError(Exception):
    """Base error for shell python
    """
    pass


class NonZeroReturnCodeError(ShellpyError):
    """This is thrown when the executed command does not return 0
    """
    def __init__(self, cmd, result):
        self.cmd = cmd
        self.result = result

    def __str__(self):
        if _is_colorama_enabled():
            return 'Command {red}\'{cmd}\'{end} failed with error code {code}, stderr output is {red}{stderr}{end}'\
                .format(red=Fore.RED, end=Style.RESET_ALL, cmd=self.cmd, code=self.result.returncode,
                        stderr=self.result.stderr)
        else:
            return 'Command \'{cmd}\' failed with error code {code}, stderr output is {stderr}'.format(
                    cmd=self.cmd, code=self.result.returncode, stderr=self.result.stderr)


class Stream:
    def __init__(self, file, encoding, print_out_stream=False, color=None):
        self._file = file
        self._encoding = encoding
        self._print_out_stream = print_out_stream
        self._color = color

    def __iter__(self):
        return self

    def next(self):
        return self.sreadline()

    __next__ = next

    def sreadline(self):
        line = self._file.readline()
        if sys.version_info[0] == 3:
            line = line.decode(self._encoding)

        if line == '':
            raise StopIteration
        else:
            line = line.rstrip(os.linesep)
            if self._print_out_stream:
                if self._color is None:
                    _print_stdout(line)
                else:
                    _print_stdout(self._color + line + Style.RESET_ALL)

            return line

    def swriteline(self, text):
        text_with_linesep = text + os.linesep
        if sys.version_info[0] == 3:
            text_with_linesep = text_with_linesep.encode(self._encoding)

        self._file.write(text_with_linesep)
        self._file.flush()


class InteractiveResult:
    """Result of a shell command execution.

    To get the result as string use str(Result)
    To get lines use the Result.lines field
    You can also iterate over lines of result like this: for line in Result:
    You can compaire two results that will mean compaire of result strings
    """
    def __init__(self, process, params):
        self._process = process
        self._params = params
        self.stdin = Stream(process.stdin, sys.stdin.encoding)

        print_stdout = _is_param_set(params, _PARAM_PRINT_STDOUT) or config.PRINT_STDOUT_ALWAYS
        self.stdout = Stream(process.stdout, sys.stdout.encoding, print_stdout)

        print_stderr = _is_param_set(params, _PARAM_PRINT_STDERR) or config.PRINT_STDERR_ALWAYS
        color = None if not _is_colorama_enabled() else Fore.RED
        self.stderr = Stream(process.stderr, sys.stderr.encoding, print_stderr, color)

    def sreadline(self):
        return self.stdout.sreadline()

    def swriteline(self, text):
        self.stdin.swriteline(text)

    @property
    def returncode(self):
        self._process.wait()
        return self._process.returncode

    def __iter__(self):
        return iter(self.stdout)

    def __bool__(self):
        return self.returncode == 0

    __nonzero__ = __bool__


class Result:
    """Result of a shell command execution.

    To get the result stdout as string use str(Result) or Result.stdout or print Result
    To get output of stderr use Result.stderr()

    You can also iterate over lines of stdout like this: for line in Result:

    You can access underlying lines of result streams as Result.stdout_lines Result.stderr_lines.
    E.g. line_two = Result.stdout_lines[2]

    You can also compaire two results that will mean compaire of result stdouts
    """
    def __init__(self):
        self._stdout_lines = []
        self._stderr_lines = []
        self.returncode = None

    @property
    def stdout(self):
        """Stdout of Result as text
        """
        return os.linesep.join(self._stdout_lines)

    @property
    def stderr(self):
        """Stderr of Result as text
        """
        return os.linesep.join(self._stderr_lines)

    @property
    def stdout_lines(self):
        """List of all lines from stdout
        """
        return self._stdout_lines

    @property
    def stderr_lines(self):
        """List of all lines from stderr
        """
        return self._stderr_lines

    def _add_stdout_line(self, line):
        line = line.rstrip(os.linesep)
        self._stdout_lines.append(line)

    def _add_stderr_line(self, line):
        line = line.rstrip(os.linesep)
        self._stderr_lines.append(line)

    def __str__(self):
        return self.stdout

    def __iter__(self):
        return iter(self._stdout_lines)

    def __eq__(self, other):
        return self.__str__() == other.__str__()

    def __bool__(self):
        return self.returncode == 0

    __nonzero__ = __bool__


def _create_result(cmd, params):
    p = subprocess.Popen(cmd,
                         shell=True,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE,
                         env=os.environ)

    result = Result()

    for line in p.stdout.readlines():
        if sys.version_info[0] == 3:
            line = line.decode(sys.stdout.encoding)

        result._add_stdout_line(line)

    for line in p.stderr.readlines():
        if sys.version_info[0] == 3:
            line = line.decode(sys.stderr.encoding)

        result._add_stderr_line(line)

    p.wait()

    if (_is_param_set(params, _PARAM_PRINT_STDOUT) or config.PRINT_STDOUT_ALWAYS) and len(result.stdout) > 0:
        _print_stdout(result.stdout)

    if (_is_param_set(params, _PARAM_PRINT_STDERR) or config.PRINT_STDERR_ALWAYS) and len(result.stderr) > 0:
        if _is_colorama_enabled():
            _print_stderr(Fore.RED + result.stderr + Style.RESET_ALL)
        else:
            _print_stderr(result.stderr)

    result.returncode = p.returncode

    if p.returncode != 0 and not _is_param_set(params, _PARAM_NO_THROW):
        raise NonZeroReturnCodeError(cmd, result)

    return result


def _create_interactive_result(cmd, params):
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)

    result = InteractiveResult(p, params)

    return result