#!/usr/bin/python

from collections import defaultdict
from os import listdir
from os.path import abspath, dirname, isdir, isfile, join, realpath, relpath, splitext
import re
from subprocess import Popen, PIPE
import sys

# Runs the tests.
WREN_DIR = dirname(realpath(__file__))
TEST_DIR = join(WREN_DIR, 'test')

if sys.platform == 'win32':
  WREN_APP = join(WREN_DIR, 'Debug', 'wren.exe')
  if not isfile(WREN_DIR):
    WREN_APP = join(WREN_DIR, 'Release', 'wren.exe')
  if not isfile(WREN_APP):
    sys.exit('Cannot find wren.exe!')
elif sys.platform.startswith('linux'):
  WREN_APP = join(WREN_DIR, '1', 'out', 'Debug', 'wren')
  if not isfile(WREN_APP):
    WREN_APP = join(WREN_DIR, '1', 'out', 'Release', 'wren')
  if not isfile(WREN_APP):
    sys.exit('Cannot find wren!')
elif sys.platform.startswith('darwin'):
  WREN_APP = join(WREN_DIR, 'build', 'Debug', 'wren')
  if not isfile(WREN_APP):
    WREN_APP = join(WREN_DIR, 'build', 'Release', 'wren')
  if not isfile(WREN_APP):
    sys.exit('Cannot find wren!')
else:
  sys.exit('System not supported!')

EXPECT_PATTERN = re.compile(r'// expect: (.*)')
EXPECT_ERROR_PATTERN = re.compile(r'// expect error')
EXPECT_ERROR_LINE_PATTERN = re.compile(r'// expect error line (\d+)')
ERROR_PATTERN = re.compile(r'\[Line (\d+)\] Error')
SKIP_PATTERN = re.compile(r'// skip: (.*)')
NONTEST_PATTERN = re.compile(r'// nontest')

if sys.platform == 'win32':
  class color:
    GREEN = ''
    RED = ''
    DEFAULT = ''
    PINK = ''
    YELLOW = ''
else:
  class color:
    GREEN = '\033[32m'
    RED = '\033[31m'
    DEFAULT = '\033[0m'
    PINK = '\033[91m'
    YELLOW = '\033[33m'

passed = 0
failed = 0
skipped = defaultdict(int)
num_skipped = 0;


def walk(dir, callback):
  """ Walks [dir], and executes [callback] on each file. """
  dir = abspath(dir)
  for file in [file for file in listdir(dir) if not file in [".",".."]]:
    nfile = join(dir, file)
    if isdir(nfile):
      walk(nfile, callback)
    else:
      callback(nfile)


def print_line(line=None):
  # Erase the line.
  print '\033[2K',
  # Move the cursor to the beginning.
  print '\r',
  if line:
    print line,
    sys.stdout.flush()


def run_test(path):
  global passed
  global failed
  global skipped
  global num_skipped

  if (splitext(path)[1] != '.wren'):
    return

  # Check if we are just running a subset of the tests.
  if len(sys.argv) == 2:
    this_test = relpath(path, join(WREN_DIR, 'test'))
    if not this_test.startswith(sys.argv[1]):
      return

  # Make a nice short path relative to the working directory.
  path = relpath(path)

  # Read the test and parse out the expectations.
  expect_output = []
  expect_error = []
  expect_return = 0

  print_line('Passed: ' + color.GREEN + str(passed) + color.DEFAULT +
             ' Failed: ' + color.RED + str(failed) + color.DEFAULT +
             ' Skipped: ' + color.YELLOW + str(num_skipped) + color.DEFAULT)

  line_num = 1
  with open(path, 'r') as file:
    for line in file:
      match = EXPECT_PATTERN.search(line)
      if match:
        expect_output.append((match.group(1), line_num))

      match = EXPECT_ERROR_PATTERN.search(line)
      if match:
        expect_error.append(line_num)
        # If we expect compile errors in the test, it should return
        # exit code 1.
        expect_return = 1

      match = EXPECT_ERROR_LINE_PATTERN.search(line)
      if match:
        expect_error.append(int(match.group(1)))
        # If we expect compile errors in the test, it should return
        # exit code 1.
        expect_return = 1

      match = SKIP_PATTERN.search(line)
      if match:
        num_skipped += 1
        skipped[match.group(1)] += 1
        return

      match = NONTEST_PATTERN.search(line)
      if match:
        # Not a test file at all, so ignore it.
        return

      line_num += 1

  # Invoke wren and run the test.
  proc = Popen([WREN_APP, path], stdout=PIPE, stderr=PIPE)
  (out, err) = proc.communicate()
  (out, err) = out.replace('\r\n', '\n'),  err.replace('\r\n', '\n')

  fails = []

  # Validate that no unexpected errors occurred.
  if expect_return == 1 and err != '':
    lines = err.split('\n')
    while len(lines) > 0:
      line = lines.pop(0)
      match = ERROR_PATTERN.search(line)
      if match:
        if float(match.group(1)) not in expect_error:
          fails.append('Unexpected error:')
          fails.append(line)
      elif line != '':
        fails.append('Unexpected output on stderr:')
        fails.append(line)
  else:
    for line in expect_error:
      fails.append('Expected error on line ' + str(line) + ' and got none.')

  # Validate the exit code.
  if proc.returncode != expect_return:
    fails.append('Expected return code {0} and got {1}.'
        .format(expect_return, proc.returncode))
  else:
    # Validate the output.
    expect_index = 0

    # Remove the trailing last empty line.
    out_lines = out.split('\n')
    if out_lines[-1] == '':
      del out_lines[-1]

    for line in out_lines:
      if expect_index >= len(expect_output):
        fails.append('Got output "{0}" when none was expected.'.format(line))
      elif expect_output[expect_index][0] != line:
        fails.append('Expected output "{0}" on line {1} and got "{2}".'.
            format(expect_output[expect_index][0],
                   expect_output[expect_index][1], line))
      expect_index += 1

    while expect_index < len(expect_output):
      fails.append('Missing expected output "{0}" on line {1}.'.
          format(expect_output[expect_index][0],
                 expect_output[expect_index][1]))
      expect_index += 1

  # Display the results.
  if len(fails) == 0:
    passed += 1
    #print color.GREEN + 'PASS' + color.DEFAULT + ': ' + path
  else:
    failed += 1
    print_line(color.RED + 'FAIL' + color.DEFAULT + ': ' + path)
    print
    for fail in fails:
      print '     ', color.PINK + fail + color.DEFAULT
    print

walk(TEST_DIR, run_test)

print_line()
if failed == 0:
  print 'All ' + color.GREEN + str(passed) + color.DEFAULT + ' tests passed.'
else:
  print (color.GREEN + str(passed) + color.DEFAULT + ' tests passed. ' +
       color.RED + str(failed) + color.DEFAULT + ' tests failed.')

for key in sorted(skipped.keys()):
  print ('Skipped ' + color.YELLOW + str(skipped[key]) + color.DEFAULT +
       ' tests: ' + key)
