# -*- coding: utf-8 -*-
#
# escpos/helpers.py
#
# Copyright 2015 Base4 Sistemas Ltda ME
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals

import inspect
import time

from collections import namedtuple
from operator import attrgetter

from builtins import chr
from six.moves import zip_longest

from .exceptions import TimeoutException


_Model = namedtuple('_Model', 'name vendor')

Implementation = namedtuple('Implementation', 'model type fqname')


def find_implementations(sort_by=None):
    """
    Returns a tuple of :class:`~escpos.helpers.Implementation` objects
    containing metadata for all known implementations (subclasses of
    :class:`~escpos.impl.epson.GenericESCPOS`) with vendor and model names, the
    implementation type and its fully qualified name.

    This example will print all vendor and model names, sorted by vendor name:

    .. sourcecode::

        for impl in find_implementations(sort_by='model.vendor'):
            print impl.model.vendor, ':', impl.model.name

    :param str sort_by: Attribute name to sort the resulting list (optional).

    :rtype: tuple

    """
    impls = [_describe_impl(t) for t in _list_impls()]
    if sort_by:
        impls.sort(key=attrgetter(sort_by))
    return tuple(impls)


class TimeoutHelper(object):

    def __init__(self, timeout=1):
        self.timeout = 1
        self.set()

    def set(self):
        self._mark = time.time()

    def check(self):
        if self.timeout > 0:
            if time.time() - self._mark > self.timeout:
                raise TimeoutException((
                        '{!r} seconds have passed'
                    ).format(self.timeout))
        return False


def chunks(iterable, size):
    def chunk_factory(iterable, size):
        args = [iter(iterable)] * size
        return zip_longest(*args, fillvalue=None)
    for chunk in chunk_factory(iterable, size):
        yield ''.join([e for e in chunk if e is not None])


def hexdump(data):
    def _cut(sequence, size):
        for i in range(0, len(sequence), size):
            yield sequence[i:i+size]

    def _hex(sequence):
        return ['{0:02x}'.format(b) for b in sequence]

    def _chr(sequence):
        return [chr(b) if 32 <= b <= 126 else '.' for b in sequence]

    raw_data = map(ord, data)
    hexpanel = [' '.join(line) for line in _cut(_hex(raw_data), 16)]
    chrpanel = [''.join(line) for line in _cut(_chr(raw_data), 16)]
    hexpanel[-1] = hexpanel[-1] + (chr(32) * (47 - len(hexpanel[-1])))
    chrpanel[-1] = chrpanel[-1] + (chr(32) * (16 - len(chrpanel[-1])))
    return '\n'.join('{}  {}'.format(h, c) for h, c in zip(hexpanel, chrpanel))


def is_value_in(constants_group, value):
    """Checks whether value can be found in the given constants group,
    which in turn, must be a Django-like choices tuple.
    """
    return value in [k for k, v in constants_group]


def as_char(i):
    # Python 2: assert '\x20' == as_char(32)
    # Python 3: assert b'\x20' == as_char(32)
    # http://python-future.org/compatible_idioms.html#chr
    return chr(i).encode('latin-1')


def _list_impls():
    from escpos.impl.epson import GenericESCPOS
    return _impls_for(GenericESCPOS)


def _impls_for(t):
    impls = [t]
    for subcls in t.__subclasses__():
        impls.extend(_impls_for(subcls))
    return impls


def _describe_impl(t):
    impl = Implementation(
            model=_Model(name=t.model.name, vendor=t.model.vendor),
            type=t,
            fqname=_fqname(t)
        )
    return impl


def _fqname(t):
    m = inspect.getmodule(t)
    return '.'.join([m.__name__, t.__name__])