import errno
import os
import signal
import functools
import contextlib
import sys
import tqdm
import argparse
import warnings
import pathlib


# ------------------------------- argument parser -------------------------------------
def setup_parser():
    parser = argparse.ArgumentParser(
        description="Script to build Espresso, with/without pre/post-build validation"
    )
    parser.add_argument(
        "-v", "--checks", "--post", "--validate",
        dest="post", action="store_true", 
        default=False, help="Run tests after building the package")
    parser.add_argument(
        "--pre", dest="pre", action="store_true",
        default=False, help="Run tests before building the package")
    parser.add_argument(
        "--timeout", "-t", dest="timeout", action="store", default=999, type=int,
        help="Specify the number of seconds as timeout limit for each attribute")
    parser.add_argument(
        "--contrib", "-c", "--contribution", 
        dest="contribs", action="append", default=None,
        help="Specify which contribution to validate")
    parser.add_argument(
        "--all", "-a", default=None,
        dest="all", action="store_true")
    parser.add_argument(
        "--no-install", default=False, 
        dest="dont_install", action="store_true")
    return parser

def args():
    args, unknown = setup_parser().parse_known_args()
    return args

def pre_build():
    _args = args()
    return _args.pre or (not _args.pre and not _args.post)


# ------------------------------- running timeout -------------------------------------
# Thanks to: https://stackoverflow.com/a/2282656
def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
    def decorator(func):
        def _handle_timeout(signum, frame):
            raise TimeoutError(error_message)

        if seconds is None:
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                return func(*args, **kwargs)
        else:
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                signal.signal(signal.SIGALRM, _handle_timeout)
                signal.alarm(seconds)
                try:
                    result = func(*args, **kwargs)
                finally:
                    signal.alarm(0)
                return result

        return wrapper

    return decorator


# ------------------------------- hide console output ---------------------------------
# Thanks to: 
# https://stackoverflow.com/a/25061573
# https://stackoverflow.com/a/72617364
@contextlib.contextmanager
def suppress_stdout():
    def tqdm_replacement(iterable_object,*args,**kwargs):
        return iterable_object

    with open(os.devnull, "w") as devnull:
        old_stdout = sys.stdout
        sys.stdout = devnull
        tqdm_copy = tqdm.tqdm # store it if you want to use it later
        tqdm.tqdm = tqdm_replacement
        try:  
            yield
        finally:
            sys.stdout = old_stdout
            tqdm.tqdm = tqdm_copy


# ------------------------------- contribution processing -----------------------------
PKG_NAME = "espresso"
ROOT = str(pathlib.Path(__file__).resolve().parent.parent.parent)
CONTRIB_FOLDER = ROOT + "/contrib"

def problem_name_to_class(problem_name):   # e.g. "xray_tomography" -> "XrayTomography"
    return problem_name.title().replace("_", "")

def get_folder_content(folder_name):
    names = [name for name in os.listdir(folder_name)]
    paths = [f"{folder_name}/{name}" for name in names]
    return names, paths

def problems_specified_from_args():
    return args().contribs

def problems_to_run(problems_specified=None):
    if problems_specified is None:
        problems_specified = problems_specified_from_args()
    all_problems = get_folder_content(CONTRIB_FOLDER)
    all_problems_zipped = list(zip(*all_problems))
    all_problems_zipped = [c for c in all_problems_zipped if "." not in c[0]]
    if problems_specified is None:
        return all_problems_zipped
    else:       # filter by specified problems list
        problems = [c for c in all_problems_zipped if c[0] in problems_specified]
        problems_not_in_folder = [
            c for c in problems_specified if c not in all_problems[0]
        ]
        if problems_not_in_folder:
            warnings.warn(
                "these examples are not detected in 'contrib' folder: " + 
                ", ".join(problems_not_in_folder)
            )
        return problems
