#!/usr/bin/python
# -*- coding: utf-8 -*-

# Hive Colony Framework
# Copyright (c) 2008-2024 Hive Solutions Lda.
#
# This file is part of Hive Colony Framework
#
# Hive Colony Framework is free software: you can redistribute it and/or modify
# it under the terms of the Apache License as published by the Apache
# Foundation, either version 2.0 of the License, or (at your option) any
# later version.
#
# Hive Colony Framework is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# Apache License for more details.
#
# You should have received a copy of the Apache License along with
# Hive Colony Framework If not, see <http://www.apache.org/licenses/>.

__author__ = "João Magalhães <joamag@hive.pt>"
""" The author(s) of the module """

__copyright__ = "Copyright (c) 2008-2024 Hive Solutions Lda."
""" The copyright for the module """

__license__ = "Apache License, Version 2.0"
""" The license for the module """

import os
import sys
import inspect
import functools
import itertools
import contextlib
import collections

import urllib  # @UnusedImport

ArgSpec = collections.namedtuple("ArgSpec", ["args", "varargs", "keywords", "defaults"])


@contextlib.contextmanager
def ctx_absolute():
    root = sys.path.pop(0)
    try:
        yield
    finally:
        sys.path.insert(0, root)


with ctx_absolute():
    try:
        import urllib2
    except ImportError:
        urllib2 = None

with ctx_absolute():
    try:
        import httplib
    except ImportError:
        httplib = None

with ctx_absolute():
    try:
        import http
    except ImportError:
        http = None

with ctx_absolute():
    try:
        import types
    except ImportError:
        types = None

with ctx_absolute():
    try:
        import urllib.error
    except ImportError:
        pass

with ctx_absolute():
    try:
        import urllib.request
    except ImportError:
        pass

with ctx_absolute():
    try:
        import http.client
    except ImportError:
        pass

with ctx_absolute():
    try:
        import importlib.util
    except ImportError:
        pass

try:
    import HTMLParser
except ImportError:
    import html.parser

    HTMLParser = html.parser

try:
    import cPickle
except ImportError:
    import pickle

    cPickle = pickle

try:
    import imp
except ImportError:
    import importlib

    imp = importlib

try:
    import importlib
except ImportError:
    import imp

    importlib = imp

try:
    import cStringIO
except ImportError:
    import io

    cStringIO = io

try:
    import StringIO as _StringIO
except ImportError:
    import io

    _StringIO = io

try:
    import urlparse as _urlparse
except ImportError:
    import urllib.parse

    _urlparse = urllib.parse

PYTHON_3 = sys.version_info[0] >= 3
""" Global variable that defines if the current Python
interpreter is at least Python 3 compliant, this is used
to take some of the conversion decision for runtime """

PYTHON_35 = sys.version_info[0] >= 3 and sys.version_info[1] >= 5
""" Global variable that defines if the current Python
interpreter is at least Python 3.5 compliant """

PYTHON_36 = sys.version_info[0] >= 3 and sys.version_info[1] >= 6
""" Global variable that defines if the current Python
interpreter is at least Python 3.6 compliant """

PYTHON_39 = sys.version_info[0] >= 3 and sys.version_info[1] >= 9
""" Global variable that defines if the current Python
interpreter is at least Python 3.9 compliant """

PYTHON_ASYNC = PYTHON_35
""" Global variable that defines if the current Python
interpreter support the async/await syntax responsible
for the easy to use async methods """

PYTHON_ASYNC_GEN = PYTHON_36
""" Global variable that defines if the current Python
interpreter support the async/await generator syntax
responsible for the async generator methods """

PYTHON_V = int("".join([str(v) for v in sys.version_info[:3]]))
""" The Python version integer describing the version of
a the interpreter as a set of three integer digits """

if PYTHON_3:
    LONG = int
else:
    LONG = long  # @UndefinedVariable

if PYTHON_3:
    BYTES = bytes
else:
    BYTES = str  # @UndefinedVariable

if PYTHON_3:
    UNICODE = str
else:
    UNICODE = unicode  # @UndefinedVariable

if PYTHON_3:
    OLD_UNICODE = None
else:
    OLD_UNICODE = unicode  # @UndefinedVariable

if PYTHON_3:
    STRINGS = (str,)
else:
    STRINGS = (str, unicode)  # @UndefinedVariable

if PYTHON_3:
    ALL_STRINGS = (bytes, str)
else:
    ALL_STRINGS = (bytes, str, unicode)  # @UndefinedVariable

if PYTHON_3:
    INTEGERS = (int,)
else:
    INTEGERS = (int, long)  # @UndefinedVariable

# saves a series of global symbols that are going to be
# used latter for some of the legacy operations
_ord = ord
_chr = chr
_str = str
_bytes = bytes
_range = range

try:
    _xrange = xrange  # @UndefinedVariable
except Exception:
    _xrange = None

if PYTHON_3:
    Request = urllib.request.Request
else:
    Request = urllib2.Request

if PYTHON_3:
    HTTPHandler = urllib.request.HTTPHandler
else:
    HTTPHandler = urllib2.HTTPHandler

if PYTHON_3:
    HTTPError = urllib.error.HTTPError
else:
    HTTPError = urllib2.HTTPError

if PYTHON_3:
    HTTPConnection = http.client.HTTPConnection  # @UndefinedVariable
else:
    HTTPConnection = httplib.HTTPConnection

if PYTHON_3:
    HTTPSConnection = http.client.HTTPSConnection  # @UndefinedVariable
else:
    HTTPSConnection = httplib.HTTPSConnection

try:
    _execfile = execfile  # @UndefinedVariable
except Exception:
    _execfile = None

try:
    _reduce = reduce  # @UndefinedVariable
except Exception:
    _reduce = None

try:
    _reload = reload  # @UndefinedVariable
except Exception:
    _reload = None

try:
    _unichr = unichr  # @UndefinedVariable
except Exception:
    _unichr = None


def with_meta(meta, *bases):
    return meta("Class", bases, {})


def eager(iterable):
    if PYTHON_3:
        return list(iterable)
    return iterable


def iteritems(associative):
    if PYTHON_3:
        return associative.items()
    return associative.iteritems()


def iterkeys(associative):
    if PYTHON_3:
        return associative.keys()
    return associative.iterkeys()


def itervalues(associative):
    if PYTHON_3:
        return associative.values()
    return associative.itervalues()


def items(associative):
    if PYTHON_3:
        return eager(associative.items())
    return associative.items()


def keys(associative):
    if PYTHON_3:
        return eager(associative.keys())
    return associative.keys()


def values(associative):
    if PYTHON_3:
        return eager(associative.values())
    return associative.values()


def xrange(start, stop=None, step=1):
    if PYTHON_3:
        return _range(start, stop, step) if stop else _range(start)
    return _xrange(start, stop, step) if stop else _range(start)


def range(start, stop=None, step=None):
    if PYTHON_3:
        return eager(_range(start, stop, step)) if stop else eager(_range(start))
    return _range(start, stop, step) if stop else _range(start)


def ord(value):
    if PYTHON_3 and type(value) == int:
        return value
    return _ord(value)


def chr(value):
    if PYTHON_3:
        return _bytes([value])
    if type(value) in INTEGERS:
        return _chr(value)
    return value


def chri(value):
    if PYTHON_3:
        return value
    if type(value) in INTEGERS:
        return _chr(value)
    return value


def bytes(value, encoding="latin-1", errors="strict", force=False):
    if not PYTHON_3 and not force:
        return value
    if value == None:
        return value
    if type(value) == _bytes:
        return value
    return value.encode(encoding, errors)


def str(value, encoding="latin-1", errors="strict", force=False):
    if not PYTHON_3 and not force:
        return value
    if value == None:
        return value
    if type(value) in STRINGS:
        return value
    return value.decode(encoding, errors)


def u(value, encoding="utf-8", errors="strict", force=False):
    if PYTHON_3 and not force:
        return value
    if value == None:
        return value
    if type(value) == UNICODE:
        return value
    return value.decode(encoding, errors)


def ascii(value, encoding="utf-8", errors="replace"):
    if is_bytes(value):
        value = value.decode(encoding, errors)
    else:
        value = UNICODE(value)
    value = value.encode("ascii", errors)
    value = str(value)
    return value


def orderable(value):
    if not PYTHON_3:
        return value
    return Orderable(value)


def is_str(value):
    return type(value) == _str


def is_unicode(value):
    if PYTHON_3:
        return type(value) == _str
    else:
        return type(value) == unicode  # @UndefinedVariable


def is_bytes(value):
    if PYTHON_3:
        return type(value) == _bytes
    else:
        return type(value) == _str  # @UndefinedVariable


def is_string(value, all=False):
    target = ALL_STRINGS if all else STRINGS
    return type(value) in target


def is_generator(value):
    if inspect.isgenerator(value):
        return True
    if type(value) in (itertools.chain,):
        return True
    if hasattr(value, "_is_generator"):
        return True
    return False


def is_async_generator(value):
    if not hasattr(inspect, "isasyncgen"):
        return False
    return inspect.isasyncgen(value)


def is_unittest(name="unittest"):
    current_stack = inspect.stack()
    for stack_frame in current_stack:
        for program_line in stack_frame[4]:
            is_unittest = not name in program_line
            if is_unittest:
                continue
            return True
    return False


def execfile(path, global_vars, local_vars=None, encoding="utf-8"):
    if local_vars == None:
        local_vars = global_vars
    if not PYTHON_3:
        return _execfile(path, global_vars, local_vars)
    file = open(path, "rb")
    try:
        data = file.read()
    finally:
        file.close()
    data = data.decode(encoding)
    code = compile(data, path, "exec")
    exec(code, global_vars, local_vars)  # @UndefinedVariable


def walk(path, visit, arg):
    for root, dirs, _files in os.walk(path):
        names = os.listdir(root)
        visit(arg, root, names)
        for dir in list(dirs):
            exists = dir in names
            not exists and dirs.remove(dir)


def getargspec(func):
    has_full = hasattr(inspect, "getfullargspec")
    if has_full:
        return ArgSpec(*inspect.getfullargspec(func)[:4])
    else:
        return inspect.getargspec(func)


def has_module(name):
    if PYTHON_3:
        try:
            spec = importlib.util.find_spec(name)
        except ImportError:
            return False
        if spec == None:
            return False
        return True
    try:
        file, _path, _description = imp.find_module(name)
    except ImportError:
        return False
    if file:
        file.close()
    return True


def new_module(name):
    if hasattr(types, "ModuleType"):
        return types.ModuleType(name)
    if hasattr(imp, "new_module"):
        return imp.new_module(name)
    raise ValueError("No module build method available")


def reduce(*args, **kwargs):
    if PYTHON_3:
        return functools.reduce(*args, **kwargs)
    return _reduce(*args, **kwargs)


def reload(*args, **kwargs):
    if PYTHON_3:
        return importlib.reload(*args, **kwargs)
    return _reload(*args, **kwargs)


def unichr(*args, **kwargs):
    if PYTHON_3:
        return _chr(*args, **kwargs)
    return _unichr(*args, **kwargs)


def urlopen(*args, **kwargs):
    if PYTHON_3:
        return urllib.request.urlopen(*args, **kwargs)
    else:
        return urllib2.urlopen(*args, **kwargs)  # @UndefinedVariable


def build_opener(*args, **kwargs):
    if PYTHON_3:
        return urllib.request.build_opener(*args, **kwargs)
    else:
        return urllib2.build_opener(*args, **kwargs)  # @UndefinedVariable


def urlparse(*args, **kwargs):
    return _urlparse.urlparse(*args, **kwargs)


def urlunparse(*args, **kwargs):
    return _urlparse.urlunparse(*args, **kwargs)


def parse_qs(*args, **kwargs):
    return _urlparse.parse_qs(*args, **kwargs)


def urlencode(*args, **kwargs):
    if PYTHON_3:
        return urllib.parse.urlencode(*args, **kwargs)
    else:
        return urllib.urlencode(*args, **kwargs)  # @UndefinedVariable


def quote(*args, **kwargs):
    if PYTHON_3:
        return urllib.parse.quote(*args, **kwargs)
    else:
        return urllib.quote(*args, **kwargs)  # @UndefinedVariable


def quote_plus(*args, **kwargs):
    if PYTHON_3:
        return urllib.parse.quote_plus(*args, **kwargs)
    else:
        return urllib.quote_plus(*args, **kwargs)  # @UndefinedVariable


def unquote(*args, **kwargs):
    if PYTHON_3:
        return urllib.parse.unquote(*args, **kwargs)
    else:
        return urllib.unquote(*args, **kwargs)  # @UndefinedVariable


def unquote_plus(*args, **kwargs):
    if PYTHON_3:
        return urllib.parse.unquote_plus(*args, **kwargs)
    else:
        return urllib.unquote_plus(*args, **kwargs)  # @UndefinedVariable


def cmp_to_key(*args, **kwargs):
    if PYTHON_3:
        return dict(key=functools.cmp_to_key(*args, **kwargs))  # @UndefinedVariable
    else:
        return dict(cmp=args[0])


def tobytes(self, *args, **kwargs):
    if PYTHON_3:
        return self.tobytes(*args, **kwargs)
    else:
        return self.tostring(*args, **kwargs)


def tostring(self, *args, **kwargs):
    if PYTHON_3:
        return self.tobytes(*args, **kwargs)
    else:
        return self.tostring(*args, **kwargs)


def StringIO(*args, **kwargs):
    if PYTHON_3:
        return cStringIO.StringIO(*args, **kwargs)
    else:
        return _StringIO.StringIO(*args, **kwargs)


def BytesIO(*args, **kwargs):
    if PYTHON_3:
        return cStringIO.BytesIO(*args, **kwargs)
    else:
        return cStringIO.StringIO(*args, **kwargs)


class Orderable(tuple):
    """
    Simple tuple type wrapper that provides a simple
    first element ordering, that is compatible with
    both the Python 2 and Python 3+ infra-structures.
    """

    def __cmp__(self, value):
        return self[0].__cmp__(value[0])

    def __lt__(self, value):
        return self[0].__lt__(value[0])
