# -*- coding: utf-8 -*-
"""
PKMeter Utilites
"""
import datetime, os, re, socket, struct, xmltodict
import shlex, subprocess, threading, queue
from urllib.parse import urlencode
from urllib.request import urlopen
from urllib.error import URLError
from PyQt5 import QtGui, QtWidgets
from pkm import log

DICTTYPES = ['dict', 'ordereddict']
LISTTYPES = ['list', 'set', 'tuple']
SECONDS = (('years',31556926), ('months',2629744), ('weeks',604800),
           ('days',86400), ('hours',3600), ('min',60), ('sec',1))


class Bunch(dict):
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__


def get_stdout(command):
    log.debug('Running command: %s' % command)
    result = subprocess.check_output(shlex.split(command))
    return result.decode('utf8')


def find_parent(widget, id):
    widget = widget.parent
    while widget and widget.id != id:
        widget = widget.parent
    if not widget:
        raise Exception("Parent widget with id '%s' not found." % id)
    return widget


def flatten_datatree(root, path):
    if not getattr(root, 'items', None):
        return [(path, str(root), value_type(root))]
    flatlist = []
    for key, value in root.items():
        subpath = '%s.%s' % (path, key)
        vtype = value_type(value)
        if vtype in DICTTYPES:
            flatlist += flatten_datatree(value, subpath)
        elif vtype in LISTTYPES:
            valuestr = '<%s: %s items>' % (vtype, len(value))
            flatlist.append((subpath, valuestr, vtype))
            for i in range(len(value)):
                flatlist += flatten_datatree(value[i], '%s.%s' % (subpath, i))
        else:
            flatlist.append((subpath, str(value), vtype))
    return sorted(flatlist, key=lambda x: x[0])


def hex_to_qcolor(hexstr):
    hexstr = hexstr.lstrip('#')
    if len(hexstr) == 6:
        return QtGui.QColor(*struct.unpack('BBB', bytes.fromhex(hexstr)))
    elif len(hexstr) == 8:
        return QtGui.QColor(*struct.unpack('BBBB', bytes.fromhex(hexstr)))
    raise Exception('Invalid hexstr format: %s' % hexstr)


def addr_to_ip(addr):
    try:
        socket.inet_aton(addr)
        return True
    except socket.error:
        return False


def http_request(url, data=None, timeout=30):
    log.debug("Requesting URL: %s" % url)
    data = urlencode(data).encode('utf8') if data else None
    try:
        response = urlopen(url, data=data, timeout=timeout)
        return {'success':True, 'response':response, 'url':url}
    except Exception as err:
        log.error("Error requesting URL: %s; %s" % (url, err))
        return {'success':False, 'error':err, 'url':url}


def iter_responses(urls, data=None, timeout=30):
    responses = queue.Queue()
    threads = []
    _req = lambda url, data, timeout: responses.put(http_request(url, data, timeout))
    for url in urls:
        threads.append(threading.Thread(target=_req, args=(url, data, timeout)))
        threads[-1].daemon = True
        threads[-1].start()
    for thread in threads:
        thread.join()
    responses.put(None)
    for response in iter(responses.get, None):
        yield response


def name(module):
    return getattr(module, 'NAME', namespace(module))


def namespace(module):
    if isinstance(module, str):
        return module
    return os.path.basename(module.__file__).split('.')[0]


def natural_time(timedelta, precision=2, default='NA'):
    # Convert timedelta to seconds
    remaining = timedelta
    if isinstance(timedelta, datetime.timedelta):
        remaining = (timedelta.days * 86400) + timedelta.seconds
    # Create and return the natural string
    rtnvals = []
    for name, seconds in SECONDS:
        if remaining > seconds * 2:
            value = int(float(remaining) / float(seconds))
            remaining = remaining - (value * seconds)
            rtnvals.append('%s %s' % (value, name))
            precision -= 1
        if precision <= 0 or remaining <= 0:
            break
    if not rtnvals:
        rtnvals.append('0 %s' % SECONDS[-1][0])
    rtnval = ', '.join(rtnvals)
    return rtnval


def percent(numerator, denominator, precision=2, maxval=999.9, default=0.0):
    if not denominator:
        return default
    return min(maxval, round((numerator / float(denominator)) * 100.0, precision))


def remove_children(widget):
    remove_widget(widget, False)


def remove_widget(widget, delete=True):
    item = widget.layout().takeAt(0)
    while item:
        subwidget = item.widget()
        if subwidget: subwidget.setParent(None)
        item = widget.layout().takeAt(0)
    if delete:
        widget.setParent(None)


def rget(obj, attrstr, default=None, delim='.'):
    try:
        parts = attrstr.split(delim, 1)
        attr = parts[0]
        attrstr = parts[1] if len(parts) == 2 else None
        if isinstance(obj, dict): value = obj[attr]
        elif isinstance(obj, list): value = obj[int(attr)]
        elif isinstance(obj, tuple): value = obj[int(attr)]
        elif isinstance(obj, object): value = getattr(obj, attr)
        if attrstr: return rget(value, attrstr, default, delim)
        return value
    except:
        return default


def rset(obj, attrstr, value, delim='.'):
    parts = attrstr.split(delim, 1)
    attr = parts[0]
    attrstr = parts[1] if len(parts) == 2 else None
    if attr not in obj: obj[attr] = {}
    if attrstr: rset(obj[attr], attrstr, value, delim)
    else: obj[attr] = value


def to_int(value, default=None):
    try:
        return int(value)
    except:
        return default


def value_type(value):
    return re.findall(r"(\w+?)\'", str(type(value)))[0].lower()


def window_bgcolor():
    tmp = QtWidgets.QWidget()
    return tmp.palette().color(QtGui.QPalette.Window)


def xml_to_dict(xml, listpaths=None, **kwargs):
    listpaths = listpaths or []
    opts = dict(
        attr_prefix = '',
        dict_constructor = dict,
        postprocessor = lambda p,k,d: (k.split(':')[1] if ':' in k else k,d)
    )
    opts.update(kwargs)
    content = xmltodict.parse(xml, **opts)
    for listpath in listpaths:
        pathdata = rget(content, listpath, [])
        pathdata = pathdata if isinstance(pathdata, list) else [pathdata]
        rset(content, listpath, pathdata)
    return content