"""General utilities."""

import logging
from urllib.parse import urlsplit
from subprocess import CalledProcessError
from typing import Callable, Generic, List, Optional, Tuple, TypeVar

Alpha = TypeVar('Alpha')
Beta = TypeVar('Beta')

class Unspecified:
    pass

unspecified = Unspecified()

def ident(x: Alpha) -> Alpha:
    return x

def map_option(f: Callable[[Alpha], Beta], opt: Optional[Alpha]) \
        -> Optional[Beta]:
    if opt is None:
        return None
    return f(opt)

class Result(Generic[Alpha, Beta]):
    def is_ok(self) -> bool:
        raise NotImplementedError
    def is_error(self) -> bool:
        raise NotImplementedError
    def get(self) -> Alpha:
        raise NotImplementedError
    def get_error(self) -> Beta:
        raise NotImplementedError
    def __str__(self) -> str:
        raise NotImplementedError

class ResultOk(Result[Alpha, Beta]):

    def __init__(self, value):
        Result.__init__(self)
        self.value = value

    def is_ok(self) -> bool:
        return True

    def is_error(self) -> bool:
        return False

    def get(self) -> Alpha:
        return self.value

    def get_error(self) -> Beta:
        raise ValueError("ResultOk.get_error: Not an error.")

    def __str__(self) -> str:
        return str(self.value)

class ResultError(Result[Alpha, Beta]):

    def __init__(self, error):
        Result.__init__(self)
        self.error = error

    def is_ok(self) -> bool:
        return False

    def is_error(self) -> bool:
        return True

    def get(self) -> Alpha:
        if isinstance(self.error, Exception):
            raise self.error
        raise ValueError('ResultError.get: Not ok.')

    def get_error(self) -> Beta:
        return self.error

    def __str__(self) -> str:
        return 'ResultError: %s' % self.error

def counted_noun(
        count: int, sing_word: str, pl_word: Optional[str] = None) -> str:
    if count == 1:
        return '%d %s'%(count, sing_word)
    else:
        return '%d %s'%(count, pl_word or sing_word + 's')

def counted_adjectives(cws: List[Tuple[int, str]], if_empty: str = '') -> str:
    return ', '.join(map(lambda cw: '%d %s' % cw,
                         filter(lambda cw: cw[0] != 0, cws))) or if_empty

def nth(n: int) -> str:
    if n % 100 > 3 and n % 100 < 21:
        return str(n) + 'th'
    else:
        return str(n) + {1: 'st', 2: 'nd', 3: 'rd'}.get(n % 10, 'th')

def file_contents(path, encoding: str = 'utf-8') -> Optional[str]:
    try:
        with open(path, encoding=encoding) as fh:
            return fh.read()
    except IOError:
        return None

def host_of_uri(jobid: str) -> str:
    netloc = urlsplit(jobid)[1]
    if ':' in netloc:
        return netloc.split(':', 1)[0]
    else:
        return netloc

def log_process_error(
        log: logging.Logger,
        exn: CalledProcessError,
        synopsis: Optional[str] = None,
        prefix: Optional[str] = None) -> None:
    if exn.output:
        hint = 'stderr follows'
    else:
        hint = 'no stderr'
    if synopsis:
        log.error('%s: %s (%s)', synopsis.capitalize(), str(exn), hint)
    else:
        log.error('%s (%s)', str(exn), hint)
    if exn.output:
        if prefix:
            prefix = '[%s] ' % prefix
        else:
            prefix = ''

        # exn.output may be str or bytes depending on how the subprocess
        # function is invoked.
        if isinstance(exn.output, str):
            output = exn.output
        else:
            output = exn.output.decode('utf-8')

        for line in output.strip().split('\n'):
            log.error(prefix + line)
