#!/usr/bin/env python
"""
 json utilities for larch objects
"""
import numpy as np
import json

from .. import isgroup, Group
from ..fitting import isParameter, Parameter
from lmfit import Parameters

def encode4js(obj, grouplist=None):
    """return an object ready for json encoding.
    has special handling for many Python types
      numpy array
      complex numbers
      Larch Groups
      Larch Parameters

    grouplist: list of subclassed Groups to assist reconstucting the object
    """
    _groups = {}
    if grouplist is not None:
        for g in grouplist:
            _groups[g.__name__] = g

    if isinstance(obj, np.ndarray):
        out = {'__class__': 'Array', '__shape__': obj.shape,
               '__dtype__': obj.dtype.name}
        out['value'] = obj.flatten().tolist()

        if 'complex' in obj.dtype.name:
            out['value'] = [(obj.real).tolist(), (obj.imag).tolist()]
        elif obj.dtype.name == 'object':
            out['value'] = [encode4js(i, grouplist=grouplist) for i in out['value']]
        return out
    elif isinstance(obj, (np.bool, np.bool_)):
        return bool(obj)
    elif isinstance(obj, (np.float, np.int)):
        return float(obj)
    elif isinstance(obj, str):
        return str(obj)
    elif isinstance(obj, np.complex):
        return {'__class__': 'Complex', 'value': (obj.real, obj.imag)}
    elif isgroup(obj):
        classname = 'Group'
        if obj.__class__.__name__ in _groups:
            classname = obj.__class__.__name__
        out = {'__class__': classname}
        for item in dir(obj):
            out[item] = encode4js(getattr(obj, item), grouplist=grouplist)
        return out
    elif isinstance(obj, Parameters):
        out = json.loads(obj.dumps())
        out['__class__'] = 'Parameters'
        return out
    elif isParameter(obj):
        out = {'__class__': 'Parameter'}
        for attr in ('value', 'name', 'vary', 'min', 'max',
                     'expr', 'stderr', 'correl'):
            val = getattr(obj, attr, None)
            if val is not None:
                out[attr] = val
        return out
    elif isinstance(obj, (tuple, list)):
        ctype = 'List'
        if isinstance(obj, tuple):
            ctype = 'Tuple'
        val = [encode4js(item, grouplist=grouplist) for item in obj]
        return {'__class__': ctype, 'value': val}
    elif isinstance(obj, dict):
        out = {'__class__': 'Dict'}
        for key, val in obj.items():
            out[encode4js(key, grouplist=grouplist)] = encode4js(val, grouplist=grouplist)
        return out
    elif callable(obj):
        return {'__class__': 'Method', '__name__': repr(obj)}

    return obj

def decode4js(obj, grouplist=None):
    """
    return decoded Python object from encoded object.

    grouplist: list of subclassed Groups to assist reconstucting the object
    """
    if not isinstance(obj, dict):
        return obj
    out = obj
    classname = obj.pop('__class__', None)
    if classname is None:
        return obj

    _groups = {'Group': Group, 'Parameter': Parameter}
    if grouplist is not None:
        for g in grouplist:
            _groups[g.__name__] = g
    if classname == 'Complex':
        out = obj['value'][0] + 1j*obj['value'][1]
    elif classname in ('List', 'Tuple'):
        out = []
        for item in obj['value']:
            out.append(decode4js(item, grouplist))
        if classname == 'Tuple':
            out = tuple(out)
    elif classname == 'Array':
        if obj['__dtype__'].startswith('complex'):
            re = np.fromiter(obj['value'][0], dtype='double')
            im = np.fromiter(obj['value'][1], dtype='double')
            out = re + 1j*im
        elif obj['__dtype__'].startswith('object'):
            val = [decode4js(v, grouplist=grouplist) for v in obj['value']]
            out = np.array(val,  dtype=obj['__dtype__'])

        else:
            out = np.fromiter(obj['value'], dtype=obj['__dtype__'])
        out.shape = obj['__shape__']
    elif classname in ('Dict', 'dict'):
        out = {}
        for key, val in obj.items():
            out[key] = decode4js(val, grouplist)
    elif classname == 'Parameters':
        out = Parameters()
        out.loads(json.dumps(obj))

    elif classname in ('Parameter', 'parameter'):
        out = {}
        extras = {}
        for key, val in obj.items():
            if key in ('name', 'value', 'vary', 'min', 'max', 'expr'):
                out[key] = decode4js(val, grouplist)
            else:
                extras[key] = decode4js(val, grouplist)
        out = Parameter(**out)
        for key, val in extras.items():
            setattr(out, key, val)

    elif classname in ('Group', 'group'):
        out = {}
        for key, val in obj.items():
            if (isinstance(val, dict) and
                val.get('__class__', None) == 'Method' and
                val.get('__name__', None) is not None):
                pass  # ignore class methods for subclassed Groups
            else:
                out[key] = decode4js(val, grouplist)
        out = Group(**out)

    return out
