#!/usr/bin/env python
import glob, os, re, sys

USAGE = """
Usage:

    {program} add NAME

        Add a new test with the given NAME in the test directory and then
        update the list of tests in Main.hs (warning: discards manual edits).

    {program} update

        Update the list of tests in Main.hs (warning: discards manual edits).

"""[1:]

LIBRARY = "System.Directory"
TEST_DIR = "tests"
TEST_EXT = ".hs"
TEST_TEMPLATE = """
{{-# LANGUAGE CPP #-}}
module {name} where
#include "util.inl"
import {library}

main :: TestEnv -> IO ()
main _t = do

"""[1:]
MAIN_NAME = "Main"
MAIN_TEMPLATE = """
module Main (main) where
import qualified Util as T
{imports}
main :: IO ()
main = T.testMain $ \ _t -> do
{runs}
"""[1:-1]
MAIN_IMPORT_TEMPLATE = "import qualified {name}\n"
MAIN_RUN_TEMPLATE = '  T.isolatedRun _t "{name}" {name}.main\n'
BLACKLIST = "^(Main|.*Util.*)$"

CABAL_FILE = glob.glob("*.cabal")[0]
CABAL_SECTION_PATTERN = """(?s)
( *)-- test-modules-begin
.*?-- test-modules-end
"""
CABAL_SECTION_TEMPLATE = """
{0}-- test-modules-begin
{1}{0}-- test-modules-end
"""

program = os.path.basename(sys.argv[0])

def rename(src, dest):
    '''Rename a file (allows overwrites on Windows).'''
    import os
    if os.name == "nt":
        import ctypes, ctypes.wintypes
        MoveFileExW = ctypes.windll.kernel32.MoveFileExW
        MoveFileExW.restype = ctypes.wintypes.BOOL
        MOVEFILE_REPLACE_EXISTING = ctypes.wintypes.DWORD(0x1)
        success = MoveFileExW(ctypes.wintypes.LPCWSTR(src),
                              ctypes.wintypes.LPCWSTR(dest),
                              MOVEFILE_REPLACE_EXISTING)
        if not success:
            raise ctypes.WinError()
    else:
        os.rename(src, dest)

def usage():
    sys.stderr.write(USAGE.format(program=program))
    sys.exit(2)

def add(name):
    if not name[0].isupper():
        sys.stderr.write("{0}: must start with a capital letter: {1}\n"
                         .format(program, name))
        sys.exit(1)
    filename = os.path.join(TEST_DIR, name + TEST_EXT)
    if os.path.exists(filename):
        sys.stderr.write("{0}: test already exists: {1}\n"
                         .format(program, filename))
        sys.exit(1)
    with open(filename, "wb") as file:
        file.write(TEST_TEMPLATE.format(name=name, library=LIBRARY)
                   .encode("utf8"))
    update()
    print("{0}: test added: {1}".format(program, filename))

def update():
    tests = []
    for basename in os.listdir(TEST_DIR):
        name, ext = os.path.splitext(basename)
        if (ext == TEST_EXT and
            len(name) > 0 and
            not re.search(BLACKLIST, name) and
            name[0].isupper()):
            tests.append(name)
    tests.sort()
    with open(os.path.join(TEST_DIR, MAIN_NAME + TEST_EXT), "wb") as file:
        file.write(MAIN_TEMPLATE.format(
            imports="".join(MAIN_IMPORT_TEMPLATE.format(name=name)
                            for name in tests),
            runs="".join(MAIN_RUN_TEMPLATE.format(name=name)
                         for name in tests),
        ).encode("utf8"))
    with open(CABAL_FILE, "rb") as file:
        cabal_file = file.read().decode("utf8")
    with open(CABAL_FILE + ".tmp", "wb") as file:
        indent, = re.search(CABAL_SECTION_PATTERN, cabal_file).groups()
        repl = CABAL_SECTION_TEMPLATE.format(
            indent,
            "".join("{0}{1}\n".format(indent, name) for name in tests)
        )
        file.write(re.sub(CABAL_SECTION_PATTERN, repl, cabal_file)
                   .encode("utf8"))
    rename(CABAL_FILE + ".tmp", CABAL_FILE)

if len(sys.argv) < 2:
    usage()

command = sys.argv[1]
if command == "add":
    if len(sys.argv) > 3:
        usage()
    add(sys.argv[2])
elif command == "update":
    if len(sys.argv) > 2:
        usage()
    update()
