#!/usr/bin/env python
from __future__ import print_function

import os
import subprocess
import doctest
import re

FIRST_SCRIPT = 'prepare'
LAST_SCRIPT = 'cleanup'

PRE_EXTENSION = '.pre'
POST_EXTENSION = '.post'

PYTHON_TEST_EXTENSION = '.pytest'
BASH_TEST_EXTENSION = '.shtest'


class RunTest:
    '''Runs the tests'''

    def __init__(self):
        self.path = os.path.abspath('.')

        # Only no-hide files
        self.all_files = [filename for filename in os.listdir(self.path)
                if filename[0] != '.' and os.path.isfile(filename)]

        self.all_files = sorted(self.all_files)

        self.python_tests = []
        self.bash_tests = []
        self.script_tests = []
        self.first_script = ''
        self.last_script = ''
        self.pre_scripts = []
        self.post_scripts = []

        for filename in self.all_files:
            if filename.endswith(PYTHON_TEST_EXTENSION):
                self.python_tests.append(filename)

            elif filename.endswith(BASH_TEST_EXTENSION):
                self.bash_tests.append(filename)

            elif os.access(filename, os.X_OK):
                basename, extension = os.path.splitext(filename)
                if basename == FIRST_SCRIPT:
                    if self.first_script:
                        raise MoreThanOneFirstScript()
                    self.first_script = filename
                elif basename == LAST_SCRIPT:
                    if self.last_script:
                        raise MoreThanOneLastScript()
                    self.last_script = filename
                elif extension == PRE_EXTENSION:
                    self.pre_scripts.append(filename)
                elif extension == POST_EXTENSION:
                    self.post_scripts.append(filename)
                else:
                    self.script_tests.append(filename)

        self.fails = 0

    def run_script(self, script):
        '''Run a script test'''
        path_script = os.path.join(self.path, script)
        proc = subprocess.Popen((path_script), shell=True, stdout=subprocess.PIPE,
                stderr=subprocess.PIPE)
        return_value = proc.wait()
        if return_value != 0:
            self.fails += 1
            stdout, stderr = proc.communicate()
            print("*******************************************************")
            print("Error %d in %s:" % (return_value, script))
            print(stdout.decode(), end='')
            print(stderr.decode(), end='')

        return return_value

    def run_bash_test(self, script):
        '''Run bash test'''
        #import pdb; pdb.set_trace()
        path_script = os.path.join(self.path, script)
        errors = 0
        test_no = 0

        for command, result, line in read_bash_tests(path_script):
            test_no += 1
            try:
                proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE,
                        stderr=subprocess.STDOUT)
                stdout = proc.communicate()[0]
            except OSError as exc:
                print('File "%s" line %d:' % (script, line))
                print('Failed example:')
                print('    ' + command)
                print("Exception was raised:")
                print("    ", end="")
                print(exc)
                print("*******************************************************")
                errors += 1

            else:
                if result != stdout.decode():
                    print('File "%s" line %d:' % (script, line))
                    print('Failed example:')
                    print('    ' + command)
                    print("Expected:")
                    for l in result.split('\n')[:-1]:
                        print('    ' + l)
                    print("Got:")
                    for l in stdout.decode().split('\n')[:-1]:
                        print('    ' + l)
                    errors += 1
                    print("*******************************************************")

        if errors != 0:
            print("%d items had failures:" % errors)
            print("    %d of %d in %s" % (errors, test_no, script))
            print("***Test failed***")

        return errors


    def run_pre_test(self, test):
        '''Run the pre-test of a test'''
        pre_test = test + PRE_EXTENSION
        return_value = 0
        if pre_test in self.pre_scripts:
            return_value = self.run_script(pre_test)
            if return_value:
                print("No running: %s %s" % (test, test + POST_EXTENSION))

        return return_value

    def run_post_test(self, test):
        '''Run the post-test of a test'''
        post_test = test + POST_EXTENSION
        if post_test in self.post_scripts:
            return self.run_script(post_test)

    def run_tests(self):
        '''Run the tests in the correct order'''
        if self.first_script:
            if self.run_script(self.first_script) != 0:
                print('*Error in prepare script. Aborting.*')
                return self.show_errors()

        all_tests = sorted(self.script_tests + self.python_tests + self.bash_tests)
        for test in all_tests:
            if self.run_pre_test(test) != 0:
                continue

            if test in self.script_tests:
                self.run_script(test)
            elif test in self.bash_tests:
                fails = self.run_bash_test(test)
                self.fails += fails
            elif test in self.python_tests:
                fails, n_tests = doctest.testfile(test, module_relative=False)
                self.fails += fails

            self.run_post_test(test)

        if self.last_script:
            self.run_script(self.last_script)

        return self.show_errors()

    def show_errors(self):
        '''Show the total errors'''
        if self.fails:
            print("*******************************************************")
            print("Total errors: %d" % self.fails)

        return self.fails

class MoreThanOneFirstScript(Exception):
    def __init__(self):
        super(MoreThanOneFirstScript, self).__init__(
                "More than one %s script" % FIRST_SCRIPT)

class MoreThanOneLastScript(Exception):
    def __init__(self):
        super(MoreThanOneLastScript, self).__init__(
                "More than one %s script" % LAST_SCRIPT)


def read_bash_tests(file_name):
    '''Iterator that yields the found tests'''
    fd = open(file_name)
    command = ''
    result = ''
    tests = []
    line_no = 0
    command_line = 0
    command_re = re.compile("^\$ (.*)\n")

    for line in fd.readlines():
        line_no += 1

        match = command_re.match(line)
        if match:
            # Is it a command?
            if command:
                # If it is a command but we have a previous command to send
                yield (command, result, command_line)
                result = ''

            command = match.group(1)
            command_line = line_no

        elif command:
            # If not a command but we have a previous command
            if line != "\n":
                # It's a part of the result
                if line == "<BLANKLINE>\n":
                    result += '\n'
                else:
                    result += line
                
            else:
                # Or it's the end of the result, yielding

                yield (command, result, command_line)
                command = ''
                result = ''


        else:
            # This line is a comment
            pass

    if command:
        # Cheking if the last command was sent
        yield (command, result, command_line)

    fd.close()


if __name__ == "__main__":
    r = RunTest()
    r.run_tests()
