# -*- coding: utf-8 -*-
# circle/common/transport.py

from django.db import models
from django.db.models.query import QuerySet
from django.utils.functional import SimpleLazyObject, Promise
from datetime import datetime, date
from decimal import Decimal
from django.apps import apps

# Global keys
GLOBAL_MODEL_FIELDS = {
    'user': 'auth.User',
    'node': 'vm.Node',
    'to_node': 'vm.Node',
    'instance': 'vm.Instance',
    'activity': 'vm.InstanceActivity',
    'vlan':     'firewall.Vlan',
}
MODEL_TAG_KEY = '__model__'

def _merge_fields(extra):
    """extra: dict or None. Returna an usable mapping (global + override)."""
    mf = GLOBAL_MODEL_FIELDS.copy()
    if extra:
        mf.update(extra)
    return mf

def _model_label(obj):
    # "app_label.ModelName"
    return obj._meta.app_label + '.' + obj.__class__.__name__

def to_wire(data, model_fields=None):
    """Recursive JSON-friendly encoder
    - model_fields: opcional dict {key -> 'app_label.ModelName'} – per-message
    - known Django model -> pk
    - unknown Django model -> {__model__: "app.Model", pk: <id>}
    - QuerySet -> [pk, ...]
    - datetime/date -> ISO
    - Decimal -> str
    - set -> list
    - keys -> always strings (JSON)
    """
    mf = _merge_fields(model_fields)

    if data is None or isinstance(data, (bool, int, long, float, basestring)):
        return data

    if isinstance(data, models.Model):
        return {MODEL_TAG_KEY: _model_label(data), 'pk': data.pk}

    if isinstance(data, SimpleLazyObject):
        # example: request.user – 
        try:
            return to_wire(data._wrapped, model_fields=mf)
        except Exception:
            return to_wire(data.__dict__, model_fields=mf)

    if isinstance(data, QuerySet):
        return list(data.values_list('pk', flat=True))

    if isinstance(data, (datetime, date)):
        return data.isoformat()

    if isinstance(data, Decimal):
        return str(data)

    if isinstance(data, set):
        return [to_wire(v, model_fields=mf) for v in data]

    if isinstance(data, dict):
        out = {}
        for k, v in data.iteritems():
            if not isinstance(k, basestring):
                k = unicode(k)
            # If key found in the mapping -> pk
            if k in mf and isinstance(v, models.Model):
                out[k] = getattr(v, 'pk', None)
            else:
                out[k] = to_wire(v, model_fields=mf)
        return out

    if isinstance(data, (list, tuple)):
        return [to_wire(v, model_fields=mf) for v in data]

    if isinstance(data, Promise):
        return unicode(data)

    if hasattr(data, '__call__'):
        # bound method/function name – JSON-friendly
        return getattr(data, '__name__', unicode(data))

    # fallback: reprezentation
    return unicode(data)

def from_wire(data, model_fields=None):
    """Recursive JSON-friendly decoder
     - Django model objects are loaded form Django DB using the MODEL_FIELDS
    """
    mf = _merge_fields(model_fields)


    # tagged model 
    if isinstance(data, dict) and MODEL_TAG_KEY in data and 'pk' in data:
        label = data[MODEL_TAG_KEY]
        pk = data['pk']
        Model = apps.get_model(label)
        return Model.objects.get(pk=pk)

    if isinstance(data, dict):
        out = {}
        for k, v in data.iteritems():
            if k in mf and v is not None:
                Model = apps.get_model(mf[k])
                out[k] = Model.objects.get(pk=v)
            else:
                out[k] = from_wire(v, model_fields=mf)
        return out

    if isinstance(data, (list, tuple)):
        return [from_wire(v, model_fields=mf) for v in data]

    # primitives
    return data

