# Copyright (c) 2015, Intel Corporation
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#     * Redistributions of source code must retain the above copyright notice,
#       this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright notice,
#       this list of conditions and the following disclaimer in the documentation
#       and/or other materials provided with the distribution.
#     * Neither the name of Intel Corporation nor the names of its contributors
#       may be used to endorse or promote products derived from this software
#       without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""bits.cdata module."""

from __future__ import print_function
import binascii
import ctypes, _ctypes
import textwrap
import ttypager
import uuid

def print_fields(cls):
    with ttypager.page():
        print("{}".format(cls.__name__))
        print("{:20s} {:6} {:6}".format('field', 'length', 'offset'))
        for f in cls._fields_:
            a = getattr(cls, f[0])
            print("{:20s} {:6} {:6}".format(f[0], a.size, a.offset))

def to_bytes(var):
    return (ctypes.c_char * ctypes.sizeof(var)).from_buffer(var).raw

_CTYPES_HEX_TYPES = (
    ctypes.c_void_p,
    ctypes.c_uint8, ctypes.c_uint16, ctypes.c_uint32, ctypes.c_uint64,
    ctypes.c_ubyte, ctypes.c_ushort, ctypes.c_uint, ctypes.c_ulong, ctypes.c_ulonglong,
)

class c_base(object):
    """Base class for ctypes structures and unions."""
    @staticmethod
    def _formatval(t, val):
        if val is not None and t in _CTYPES_HEX_TYPES:
            return "{:#x}".format(val)
        if issubclass(t, _ctypes.Array):
            if issubclass(t._type_, (ctypes.c_char, ctypes.c_wchar)):
                return "'{}'".format(val)
            else:
                return "[{}]".format(", ".join(Struct._formatval(t._type_, item) for item in val))
        return "{}".format(val)


    def _formatter(self, field):
        name = field[0]
        t = field[1]
        val = getattr(self, name)
        if hasattr(self, '_formats'):
            f = self._formats.get(name, None)
            if f:
                return f(val)
        if issubclass(t, (Struct, Union)):
            val._indent = self._indent
            return str(val)
        if issubclass(t, _ctypes.Array):
            if issubclass(t._type_, (Struct, Union)):
                s = "["
                for item in val:
                    item._indent = self._indent + "  "
                    s += "\n" + str(item)
                s += "]"
                return s
        return self._formatval(t, val)

    _indent = ""

    def _wrap(self, str, indent=True):
        line_len = 77 - len(self._indent + '  ')
        _wrapper = textwrap.TextWrapper(width=line_len, initial_indent=self._indent, subsequent_indent=self._indent + '  ')
        _wrapper_indentall = textwrap.TextWrapper(width=line_len, initial_indent=self._indent + '  ', subsequent_indent=self._indent + '  ')
        def __wrap():
            wrapper = _wrapper
            for line in str.split("\n"):
                # Preserve blank lines, for which wrapper emits an empty list
                if not line:
                    yield ""
                for wrapped_line in wrapper.wrap(line):
                    yield wrapped_line
                if indent:
                    wrapper = _wrapper_indentall
        return '\n'.join(__wrap())

    def preface_field(self, field):
        a = getattr(self.__class__, field[0])
        return "ofs={} ".format(a.offset)

    def bitfield_info(self, field):
        a = getattr(self.__class__, field[0])
        bit_count = a.size >> 16
        lo_bit = a.size & 0xFFFF
        hi_bit = lo_bit + bit_count - 1
        return bit_count, hi_bit, lo_bit

    def preface_bitfield(self, field):
        bit_count, hi_bit, lo_bit = self.bitfield_info(field)
        if bit_count > 1:
            return "bits[{}:{}]=".format(hi_bit, lo_bit)
        if bit_count == 1:
            return "bit[{}]=".format(lo_bit)
        return ""

    def __str__(self):
        self._indent += "  "
        s = "{}({})".format(self.__class__.__name__, "".join("\n{}{}={}{}".format(self.preface_field(field), field[0], self.preface_bitfield(field), self._formatter(field)) for field in self._fields_))
        self._indent = ""
        return self._wrap(s)

class Struct(ctypes.Structure, c_base):
    """Base class for ctypes structures."""
    def __hash__(self):
        buf = (ctypes.c_uint8 * ctypes.sizeof(self)).from_buffer(self)
        return binascii.crc32(buf)

    def __cmp__(self, other):
        return cmp(hash(self), hash(other))

class Union(ctypes.Union, c_base):
    """Base class for ctypes unions."""
    def __hash__(self):
        buf = (ctypes.c_uint8 * ctypes.sizeof(self)).from_buffer(self)
        return binascii.crc32(buf)

    def __cmp__(self, other):
        return cmp(hash(self), hash(other))

class GUID(Struct):
    _fields_ = [
        ('Data', ctypes.c_ubyte * 16),
    ]

    def __init__(self, *args, **kwargs):
        """Create a GUID.  Accepts any arguments the uuid.UUID constructor
        would accept.  Also accepts an instance of uuid.UUID, either as the
        first argument or as a keyword argument "uuid".  As with other
        ctypes structures, passing no parameters yields a zero-initialized
        structure."""
        u = kwargs.get("uuid")
        if u is not None:
            self.uuid = u
        elif not(args) and not(kwargs):
            self.uuid = uuid.UUID(int=0)
        elif args and isinstance(args[0], uuid.UUID):
            self.uuid = args[0]
        else:
            self.uuid = uuid.UUID(*args, **kwargs)

    def _get_uuid(self):
        return uuid.UUID(bytes_le=to_bytes(self))

    def _set_uuid(self, u):
        ctypes.memmove(ctypes.addressof(self), ctypes.c_char_p(u.bytes_le), ctypes.sizeof(self))

    uuid = property(_get_uuid, _set_uuid)

    def __cmp__(self, other):
        if isinstance(other, GUID):
            return cmp(self.uuid, other.uuid)
        if isinstance(other, uuid.UUID):
            return cmp(self.uuid, other)
        return NotImplemented

    def __hash__(self):
        return hash(self.uuid)

    def __repr__(self):
        return "GUID({})".format(self.uuid)

    def __str__(self):
        return "{}".format(self.uuid)

def _format_guid(val):
    try:
        import efi
        guid_str = efi.known_uuids.get(val.uuid, None)
    except:
        guid_str = None
    if guid_str:
        return '{} ({})'.format(val, guid_str)
    return '{}'.format(val)
