# Copyright (c) 2016-present, Facebook, Inc.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

import datetime
import gc
import logging
import sys
import time
from contextlib import contextmanager
from functools import wraps
from typing import Any, Callable, List, Optional


log = logging.getLogger("sapp")


class retryable(object):
    def __init__(
        self, num_tries: int = 1, retryable_exs: Optional[List[Any]] = None
    ) -> None:
        self.num_tries = num_tries
        self.retryable_exs = retryable_exs

    def __call__(self, func):
        @wraps(func)
        def new_func(*args, **kwargs):
            try_num = 1
            while True:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if self.retryable_exs and type(e) not in self.retryable_exs:
                        raise
                    try_num += 1
                    if try_num > self.num_tries:
                        raise

        new_func.__wrapped__ = func
        return new_func


def log_time(func: Callable[..., Any]) -> Callable[..., Any]:
    """Log the time it takes to run a function. It's sort of like timeit, but
    prettier.
    """

    def wrapper(*args, **kwargs):
        start_time = time.time()
        log.info("%s starting...", func.__name__.title())
        ret = func(*args, **kwargs)
        log.info(
            "%s finished (%s)",
            func.__name__.title(),
            datetime.timedelta(seconds=int(time.time() - start_time)),
        )
        return ret

    return wrapper


class UserError(Exception):
    pass


@contextmanager
def catch_user_error():
    try:
        yield
    except UserError as error:
        print(str(error), file=sys.stderr)


@contextmanager
def catch_keyboard_interrupt():
    try:
        yield
    except KeyboardInterrupt:
        print("\nOperation aborted.", file=sys.stderr)


# For use on enums to alias upper case value.
#
# FLAKE8 does not understand that this is a static property
# flake8: noqa B902
class classproperty(property):
    def __get__(self, cls, owner):
        return classmethod(self.fget).__get__(None, owner)()
