# -*- coding: utf-8 -*-

"""
Convert between objects and numba representations.
"""

from __future__ import print_function, division, absolute_import
import ctypes

import numba2 as nb
from numba2 import typing
from .representation import stack_allocate, byref, c_primitive

#===------------------------------------------------------------------===
# Object Conversion
#===------------------------------------------------------------------===

def fromobject(value, type):
    """
    Convert a Python value to a numba representation according to `type`
    (e.g. list -> List)
    """
    cls = type.impl
    if hasattr(cls, 'fromobject') and not isinstance(value, cls):
        return cls.fromobject(value, type)
    return value


def toobject(value, type):
    """
    Convert a Numba value to a Python representation (e.g. List -> list)
    """
    cls = type.impl
    if hasattr(cls, 'toobject'):
        return cls.toobject(value, type)
    return value


def toctypes(value, type, keepalive, valmemo=None, typememo=None):
    """
    Convert a numba object given as a Python value to a low-level ctypes
    representation.

    Returns (ctypes_value, keep_alive)
    """
    from numba2.types import int8

    if hasattr(type, 'type'):
        type = type.type

    if valmemo is None:
        valmemo = {}
        typememo = {}
    if id(value) in valmemo:
        return valmemo[id(value)]

    cls = type.impl
    if hasattr(cls, 'toctypes'):
        result = cls.toctypes(value, type)
    else:
        cty = ctype(type, typememo)
        if not stack_allocate(type):
            cty = cty._type_ # Get the base type

        # Resolve types
        layout = type.resolved_layout
        types = [layout[name] for name, _ in cty._fields_] or [int8]

        # Dereference pointer to aggregate
        if hasattr(value, 'contents'):
            value = value.contents

        # Resolve values
        values = [getattr(value, name) for name, _ in cty._fields_]
        values = [toctypes(v, t, keepalive, valmemo, typememo)
                      for v, t in zip(values, types)]

        # Construct value from ctypes struct
        result = cty(*values)

        if not stack_allocate(type):
            keepalive.append(result)
            result = ctypes.pointer(result)

    valmemo[id(value)] = result
    return result

def fromctypes(value, ty, memo=None):
    """
    Construct a numba object from a ctypes representation.
    """
    from numba2.ctypes_support import is_ctypes_pointer_type, CTypesStruct

    if hasattr(ty, 'type'):
        ty = ty.type

    if memo is None:
        memo = {}
    if id(value) in memo:
        return memo[id(value)]

    cls = ty.impl
    if hasattr(cls, 'fromctypes'):
        result = cls.fromctypes(value, ty)
    else:
        cls = ty.impl
        layout = ty.resolved_layout
        values = {}

        if is_ctypes_pointer_type(type(value)):
            # TODO: stack jit
            # Recover original names from the type
            cty = ctype(ty)
            value = ctypes.cast(value, cty)

        for name, ty in ty.resolved_layout.iteritems():
            if is_ctypes_pointer_type(type(value)):
                value = value[0]
            cval = getattr(value, name)
            pyval = fromctypes(cval, ty, memo)
            values[name] = pyval

        result = cls(**values)

    memo[id(value)] = result
    return result

def ctype(type, memo=None):
    """
    Return the low-level ctypes type representation for a numba type instance.
    """

    # -------------------------------------------------
    # Setup cache

    if hasattr(type, 'type'):
        type = type.type

    if memo is None:
        memo = {}
    if type in memo:
        return memo[type]

    # -------------------------------------------------
    # Create ctypes type

    cls = type.impl
    if hasattr(cls, 'ctype'):
        result = cls.ctype(type)
    else:
        # -------------------------------------------------
        # Determine field ctypes

        names, types = zip(*type.resolved_layout.items()) or [(), ()]
        types = [ctype(ty, memo) for ty in types]
        if not types:
            types = [ctypes.c_int32]

        # -------------------------------------------------
        # Build struct

        class result(ctypes.Structure):
            _fields_ = zip(names, types)

            def __repr__(self):
                return "{ %s }" % (", ".join("%s:%s" % (name, getattr(self, name))
                                                 for name in names))

        result.__name__ = 'CTypes' + type.__class__.__name__

        # -------------------------------------------------
        # Handle stack allocation

        if not stack_allocate(type):
            result = ctypes.POINTER(result)

    # -------------------------------------------------
    # Cache result

    memo[type] = result
    return result


def make_coercers(type):
    """
    Build coercion functions that reconstruct the values.
    """

    cls = type.impl
    pycls = lambda *args: cls(*args)
    layout = cls.layout

    @jit('%s -> Type[Object] -> Object' % (type,))
    def topy(obj, _):
        args = []
        for name, type in unroll(layout):
            args.append(coerce(getattr(obj, name), Object))
        return pycls(*args)

    @jit('Object -> Type[%s] -> %s' % (type, type))
    def frompy(obj, _):
        args = []
        for name, type in unroll(layout):
            args.append(coerce(getattr(obj, name), type))
        return cls(*args)

    return topy, frompy

#===------------------------------------------------------------------===
# General Type Conversion
#===------------------------------------------------------------------===

# TODO:

@@ -1677,32 +1677,100 @@
 resolved_layout%0A
+        if not layout:%0A            types = %5Bint8%5D%0A        else:%0A    
         types = 
@@ -1815,18 +1815,8 @@
 ds_%5D
- or %5Bint8%5D
 %0A%0A  
@@ -1978,174 +1978,306 @@
  = %5B
-getattr(value, name)
+%5D%0A       
  for 
+(
 name, _
- in cty._fields_%5D%0A        values = %5Btoctypes(v, t, keepalive, valmemo, typememo)%0A                      for v, t in zip(values, types)%5D
+), ty in zip(cty._fields_, types):%0A            if hasattr(value, name):%0A                val = getattr(value, name)%0A            else:%0A                assert name == 'dummy', name%0A                val = 0%0A            values.append(toctypes(val, ty, keepalive, valmemo, typememo))
 %0A%0A  
@@ -4338,16 +4338,46 @@
  types:%0A
+            names = %5B'dummy'%5D%0A
         
@@ -4405,10 +4405,9 @@
 _int
-32
+8
 %5D%0A%0A 

Fix a ctypes layout issue for empty structs