cimport cpython
cimport libc.stdlib
from libc.stdint cimport *

cdef extern from *:
    ctypedef enum plist_type:
        PLIST_BOOLEAN,
        PLIST_INT,
        PLIST_REAL,
        PLIST_STRING,
        PLIST_ARRAY,
        PLIST_DICT,
        PLIST_DATE,
        PLIST_DATA,
        PLIST_KEY,
        PLIST_UID,
        PLIST_NULL,
        PLIST_NONE

    plist_t plist_new_bool(uint8_t val)
    void plist_get_bool_val(plist_t node, uint8_t *val)
    void plist_set_bool_val(plist_t node, uint8_t val)

    plist_t plist_new_uint(uint64_t val)
    void plist_get_uint_val(plist_t node, uint64_t *val)
    void plist_set_uint_val(plist_t node, uint64_t val)

    plist_t plist_new_int(int64_t val)
    void plist_get_int_val(plist_t node, int64_t *val)
    void plist_set_int_val(plist_t node, int64_t val)

    plist_t plist_new_real(double val)
    void plist_get_real_val(plist_t node, double *val)
    void plist_set_real_val(plist_t node, double val)

    plist_t plist_new_unix_date(int64_t sec)
    void plist_get_unix_date_val(plist_t node, int64_t *sec)
    void plist_set_unix_date_val(plist_t node, int64_t sec)

    void plist_get_key_val(plist_t node, char **val)
    void plist_set_key_val(plist_t node, char *val)

    plist_t plist_new_uid(uint64_t val)
    void plist_get_uid_val(plist_t node, uint64_t *val)
    void plist_set_uid_val(plist_t node, uint64_t val)

    plist_t plist_new_string(char *val)
    void plist_get_string_val(plist_t node, char **val)
    void plist_set_string_val(plist_t node, char *val)

    plist_t plist_new_data(char *val, uint64_t length)
    void plist_get_data_val(plist_t node, char **val, uint64_t * length)
    void plist_set_data_val(plist_t node, char *val, uint64_t length)

    plist_t plist_new_null();

    plist_t plist_new_dict()
    int plist_dict_get_size(plist_t node)
    plist_t plist_dict_get_item(plist_t node, char* key)
    void plist_dict_set_item(plist_t node, char* key, plist_t item)
    void plist_dict_insert_item(plist_t node, char* key, plist_t item)
    void plist_dict_remove_item(plist_t node, char* key)

    void plist_dict_new_iter(plist_t node, plist_dict_iter *iter)
    void plist_dict_next_item(plist_t node, plist_dict_iter iter, char **key, plist_t *val)

    plist_t plist_new_array()
    uint32_t plist_array_get_size(plist_t node)
    plist_t plist_array_get_item(plist_t node, uint32_t n)
    uint32_t plist_array_get_item_index(plist_t node)
    void plist_array_set_item(plist_t node, plist_t item, uint32_t n)
    void plist_array_append_item(plist_t node, plist_t item)
    void plist_array_insert_item(plist_t node, plist_t item, uint32_t n)
    void plist_array_remove_item(plist_t node, uint32_t n)

    void plist_free(plist_t plist)
    plist_t plist_copy(plist_t plist)
    void plist_to_xml(plist_t plist, char **plist_xml, uint32_t *length)
    void plist_to_bin(plist_t plist, char **plist_bin, uint32_t *length)

    plist_t plist_get_parent(plist_t node)
    plist_type plist_get_node_type(plist_t node)

    void plist_from_xml(char *plist_xml, uint32_t length, plist_t * plist)
    void plist_from_bin(char *plist_bin, uint32_t length, plist_t * plist)

    int plist_int_val_is_negative(plist_t node);

cdef class Node:
    def __init__(self, *args, **kwargs):
        self._c_managed = True

    def __dealloc__(self):
        if self._c_node is not NULL and self._c_managed:
            plist_free(self._c_node)

    cpdef object __deepcopy__(self, memo={}):
        return plist_t_to_node(plist_copy(self._c_node))

    cpdef object copy(self):
        cdef plist_t c_node = NULL
        c_node = plist_copy(self._c_node)
        return plist_t_to_node(c_node)

    cpdef unicode to_xml(self):
        cdef:
            char* out = NULL
            uint32_t length
        plist_to_xml(self._c_node, &out, &length)

        try:
            return cpython.PyUnicode_DecodeUTF8(out, length, 'strict')
        finally:
            if out != NULL:
                libc.stdlib.free(out)

    cpdef bytes to_bin(self):
        cdef:
            char* out = NULL
            uint32_t length
        plist_to_bin(self._c_node, &out, &length)

        try:
            return bytes(out[:length])
        finally:
            if out != NULL:
                libc.stdlib.free(out)

    property parent:
        def __get__(self):
            cdef plist_t c_parent = NULL
            cdef Node node

            c_parent = plist_get_parent(self._c_node)
            if c_parent == NULL:
                return None

            return plist_t_to_node(c_parent)

    def __str__(self):
        return str(self.get_value())

cdef class Bool(Node):
    def __cinit__(self, object value=None, *args, **kwargs):
        if value is None:
            self._c_node = plist_new_bool(0)
        else:
            self._c_node = plist_new_bool(bool(value))

    def __nonzero__(self):
        return self.get_value()

    def __richcmp__(self, other, op):
        cdef bint b = self.get_value()
        if op == 0:
            return b < other
        if op == 1:
            return b <= other
        if op == 2:
            return b == other
        if op == 3:
            return b != other
        if op == 4:
            return b > other
        if op == 5:
            return b >= other

    def __repr__(self):
        b = self.get_value()
        return '<Bool: %s>' % b

    cpdef set_value(self, object value):
        plist_set_bool_val(self._c_node, bool(value))

    cpdef bint get_value(self):
        cdef uint8_t value
        plist_get_bool_val(self._c_node, &value)
        return bool(value)

cdef Bool Bool_factory(plist_t c_node, bint managed=True):
    cdef Bool instance = Bool.__new__(Bool)
    instance._c_managed = managed
    instance._c_node = c_node
    return instance

cdef class Integer(Node):
    def __cinit__(self, object value=None, *args, **kwargs):
        if value is None:
            self._c_node = plist_new_int(0)
        else:
            if value < 0 or value <= INT64_MAX:
                self._c_node = plist_new_int(int(value))
            else:
                self._c_node = plist_new_uint(int(value))

    def __repr__(self):
        return '<Integer: %s>' % self.get_value()

    def __int__(self):
        return self.get_value()

    def __float__(self):
        v = self.get_value()
        return float(v)

    def __richcmp__(self, other, op):
        cdef int i = self.get_value()
        if op == 0:
            return i < other
        if op == 1:
            return i <= other
        if op == 2:
            return i == other
        if op == 3:
            return i != other
        if op == 4:
            return i > other
        if op == 5:
            return i >= other

    cpdef set_value(self, object value):
        plist_set_uint_val(self._c_node, int(value))

    cpdef get_value(self):
        cdef int64_t ivalue
        cdef uint64_t uvalue
        if self.is_negative():
            plist_get_int_val(self._c_node, &ivalue)
            return int(ivalue)
        else:
            plist_get_uint_val(self._c_node, &uvalue)
            return int(uvalue)

    cpdef bint is_negative(self):
        return plist_int_val_is_negative(self._c_node);

cdef Integer Integer_factory(plist_t c_node, bint managed=True):
    cdef Integer instance = Integer.__new__(Integer)
    instance._c_managed = managed
    instance._c_node = c_node
    return instance

cdef class Real(Node):
    def __cinit__(self, object value=None, *args, **kwargs):
        if value is None:
            self._c_node = plist_new_real(0.0)
        else:
            self._c_node = plist_new_real(float(value))

    def __repr__(self):
        r = self.get_value()
        return '<Real: %s>' % r

    def __float__(self):
        return self.get_value()

    def __int__(self):
        return int(self.get_value())

    def __richcmp__(self, other, op):
        cdef float f = self.get_value()
        if op == 0:
            return f < other
        if op == 1:
            return f <= other
        if op == 2:
            return f == other
        if op == 3:
            return f != other
        if op == 4:
            return f > other
        if op == 5:
            return f >= other

    cpdef set_value(self, object value):
        plist_set_real_val(self._c_node, float(value))

    cpdef float get_value(self):
        cdef double value
        plist_get_real_val(self._c_node, &value)
        return value

cdef Real Real_factory(plist_t c_node, bint managed=True):
    cdef Real instance = Real.__new__(Real)
    instance._c_managed = managed
    instance._c_node = c_node
    return instance

cdef class Uid(Node):
    def __cinit__(self, object value=None, *args, **kwargs):
        if value is None:
            self._c_node = plist_new_uid(0)
        else:
            self._c_node = plist_new_uid(int(value))

    def __repr__(self):
        cdef uint64_t i = self.get_value()
        return '<Uid: %s>' % i

    def __int__(self):
        return self.get_value()

    def __float__(self):
        v = self.get_value()
        return float(v)

    def __richcmp__(self, other, op):
        cdef int i = self.get_value()
        if op == 0:
            return i < other
        if op == 1:
            return i <= other
        if op == 2:
            return i == other
        if op == 3:
            return i != other
        if op == 4:
            return i > other
        if op == 5:
            return i >= other

    cpdef set_value(self, object value):
        plist_set_uid_val(self._c_node, int(value))

    cpdef uint64_t get_value(self):
        cdef uint64_t value
        plist_get_uid_val(self._c_node, &value)
        return value

cdef Uid Uid_factory(plist_t c_node, bint managed=True):
    cdef Uid instance = Uid.__new__(Uid)
    instance._c_managed = managed
    instance._c_node = c_node
    return instance

cdef class Null(Node):
    def __cinit__(self, object value=None, *args, **kwargs):
        self._c_node = plist_new_null()

    def __repr__(self):
        cdef uint64_t i = self.get_value()
        return '<Null>'

cdef Null Null_factory(plist_t c_node, bint managed=True):
    cdef Null instance = Null.__new__(Null)
    instance._c_managed = managed
    instance._c_node = c_node
    return instance

from cpython cimport PY_MAJOR_VERSION

cdef class Key(Node):
    def __cinit__(self, object value=None, *args, **kwargs):
        cdef:
            char* c_utf8_data = NULL
            bytes utf8_data
        if value is None:
            raise ValueError("Requires a value")
        else:
            if isinstance(value, unicode):
                utf8_data = value.encode('utf-8')
            elif (PY_MAJOR_VERSION < 3) and isinstance(value, str):
                value.encode('ascii') # trial decode
                utf8_data = value.encode('ascii')
            else:
                raise ValueError("Requires unicode input, got %s" % type(value))
            c_utf8_data = utf8_data
            self._c_node = plist_new_string("")
            plist_set_key_val(self._c_node, c_utf8_data)

    def __repr__(self):
        s = self.get_value()
        return '<Key: %s>' % s.encode('utf-8')

    def __richcmp__(self, other, op):
        cdef unicode s = self.get_value()
        if op == 0:
            return s < other
        if op == 1:
            return s <= other
        if op == 2:
            return s == other
        if op == 3:
            return s != other
        if op == 4:
            return s > other
        if op == 5:
            return s >= other

    cpdef set_value(self, object value):
        cdef:
            char* c_utf8_data = NULL
            bytes utf8_data
        if value is None:
            plist_set_key_val(self._c_node, c_utf8_data)
        else:
            if isinstance(value, unicode):
                utf8_data = value.encode('utf-8')
            elif (PY_MAJOR_VERSION < 3) and isinstance(value, str):
                value.encode('ascii') # trial decode
                utf8_data = value.encode('ascii')
            else:
                raise ValueError("Requires unicode input, got %s" % type(value))
            c_utf8_data = utf8_data
            plist_set_key_val(self._c_node, c_utf8_data)

    cpdef unicode get_value(self):
        cdef:
            char* c_value = NULL
        plist_get_key_val(self._c_node, &c_value)
        try:
            return cpython.PyUnicode_DecodeUTF8(c_value, len(c_value), 'strict')
        finally:
            libc.stdlib.free(c_value)

cdef Key Key_factory(plist_t c_node, bint managed=True):
    cdef Key instance = Key.__new__(Key)
    instance._c_managed = managed
    instance._c_node = c_node
    return instance

cdef class String(Node):
    def __cinit__(self, object value=None, *args, **kwargs):
        cdef:
            char* c_utf8_data = NULL
            bytes utf8_data
        if value is None:
            self._c_node = plist_new_string("")
        else:
            if isinstance(value, unicode):
                utf8_data = value.encode('utf-8')
            elif (PY_MAJOR_VERSION < 3) and isinstance(value, str):
                value.encode('ascii') # trial decode
                utf8_data = value.encode('ascii')
            else:
                raise ValueError("Requires unicode input, got %s" % type(value))
            c_utf8_data = utf8_data
            self._c_node = plist_new_string(c_utf8_data)

    def __repr__(self):
        s = self.get_value()
        return '<String: %s>' % s.encode('utf-8')

    def __richcmp__(self, other, op):
        cdef unicode s = self.get_value()
        if op == 0:
            return s < other
        if op == 1:
            return s <= other
        if op == 2:
            return s == other
        if op == 3:
            return s != other
        if op == 4:
            return s > other
        if op == 5:
            return s >= other

    cpdef set_value(self, object value):
        cdef:
            char* c_utf8_data = NULL
            bytes utf8_data
        if value is None:
            plist_set_string_val(self._c_node, c_utf8_data)
        else:
            if isinstance(value, unicode):
                utf8_data = value.encode('utf-8')
            elif (PY_MAJOR_VERSION < 3) and isinstance(value, str):
                value.encode('ascii') # trial decode
                utf8_data = value.encode('ascii')
            else:
                raise ValueError("Requires unicode input, got %s" % type(value))
            c_utf8_data = utf8_data
            plist_set_string_val(self._c_node, c_utf8_data)

    cpdef unicode get_value(self):
        cdef:
            char* c_value = NULL
        plist_get_string_val(self._c_node, &c_value)
        try:
            return cpython.PyUnicode_DecodeUTF8(c_value, len(c_value), 'strict')
        finally:
            libc.stdlib.free(c_value)

cdef String String_factory(plist_t c_node, bint managed=True):
    cdef String instance = String.__new__(String)
    instance._c_managed = managed
    instance._c_node = c_node
    return instance

cdef extern from "plist_util.h":
    int64_t datetime_to_timestamp(object obj)
    object timestamp_to_datetime(int64_t sec)
    int check_datetime(object obj)

cdef plist_t create_date_plist(value=None):
    cdef plist_t node = NULL
    if value is None:
        node = plist_new_unix_date(0)
    elif isinstance(value, int):
        node = plist_new_unix_date(value)
    elif isinstance(value, float):
        node = plist_new_unix_date(int(value))
    elif check_datetime(value):
        node = plist_new_unix_date(datetime_to_timestamp(value))
    return node

cdef class Date(Node):
    def __cinit__(self, object value=None, *args, **kwargs):
        self._c_node = create_date_plist(value)

    def __repr__(self):
        d = self.get_value()
        return '<Date: %s>' % d.ctime()

    def __richcmp__(self, other, op):
        d = self.get_value()
        if op == 0:
            return d < other
        if op == 1:
            return d <= other
        if op == 2:
            return d == other
        if op == 3:
            return d != other
        if op == 4:
            return d > other
        if op == 5:
            return d >= other

    cpdef object get_value(self):
        cdef int64_t secs = 0
        plist_get_unix_date_val(self._c_node, &secs)
        return timestamp_to_datetime(secs)

    cpdef set_value(self, object value):
        cdef int64_t secs = 0
        if isinstance(value, int):
            secs = value
        elif isinstance(value, float):
            secs = int(value)
        elif check_datetime(value):
            secs = datetime_to_timestamp(value)
        else:
            raise ValueError("Expected int or datetime")
        plist_set_unix_date_val(self._c_node, secs)

cdef Date Date_factory(plist_t c_node, bint managed=True):
    cdef Date instance = Date.__new__(Date)
    instance._c_managed = managed
    instance._c_node = c_node
    return instance

cdef class Data(Node):
    def __cinit__(self, object value=None, *args, **kwargs):
        if value is None:
            self._c_node = plist_new_data(NULL, 0)
        else:
            self._c_node = plist_new_data(value, len(value))

    def __repr__(self):
        d = self.get_value()
        return '<Data: %s>' % d

    def __richcmp__(self, other, op):
        cdef bytes d = self.get_value()
        if op == 0:
            return d < other
        if op == 1:
            return d <= other
        if op == 2:
            return d == other
        if op == 3:
            return d != other
        if op == 4:
            return d > other
        if op == 5:
            return d >= other

    cpdef bytes get_value(self):
        cdef:
            char* val = NULL
            uint64_t length = 0
        plist_get_data_val(self._c_node, &val, &length)

        try:
            return bytes(val[:length])
        finally:
            libc.stdlib.free(val)

    cpdef set_value(self, object value):
        cdef:
            bytes py_val = value
        plist_set_data_val(self._c_node, py_val, len(value))

cdef Data Data_factory(plist_t c_node, bint managed=True):
    cdef Data instance = Data.__new__(Data)
    instance._c_managed = managed
    instance._c_node = c_node
    return instance

cdef plist_t create_dict_plist(object value=None):
    cdef plist_t node = NULL
    cdef plist_t c_node = NULL
    node = plist_new_dict()
    if value is not None and isinstance(value, dict):
        for key, item in value.items():
            c_node = native_to_plist_t(item)
            plist_dict_set_item(node, key, c_node)
            c_node = NULL
    return node

cimport cpython

cdef class Dict(Node):
    def __cinit__(self, object value=None, *args, **kwargs):
        self._c_node = create_dict_plist(value)

    def __init__(self, value=None, *args, **kwargs):
        self._init()

    cdef void _init(self):
        cdef plist_dict_iter it = NULL
        cdef char* key = NULL
        cdef plist_t subnode = NULL

        self._map = cpython.PyDict_New()

        plist_dict_new_iter(self._c_node, &it);
        plist_dict_next_item(self._c_node, it, &key, &subnode);

        while subnode is not NULL:
            py_key = key

            if PY_MAJOR_VERSION >= 3:
                py_key = py_key.decode('utf-8')

            cpython.PyDict_SetItem(self._map, py_key, plist_t_to_node(subnode, False))
            subnode = NULL
            libc.stdlib.free(key)
            key = NULL
            plist_dict_next_item(self._c_node, it, &key, &subnode);
        libc.stdlib.free(it)

    def __dealloc__(self):
        self._map = None

    def __richcmp__(self, other, op):
        cdef dict d = self.get_value()
        if op == 0:
            return d < other
        if op == 1:
            return d <= other
        if op == 2:
            return d == other
        if op == 3:
            return d != other
        if op == 4:
            return d > other
        if op == 5:
            return d >= other

    def __len__(self):
        return cpython.PyDict_Size(self._map)

    def __repr__(self):
        return '<Dict: %s>' % self._map

    cpdef dict get_value(self):
        return dict([(key, value.get_value()) for key, value in self.items()])

    cpdef set_value(self, dict value):
        plist_free(self._c_node)
        self._map = {}
        self._c_node = NULL
        self._c_node = create_dict_plist(value)
        self._init()

    def __iter__(self):
        return self._map.__iter__()

    cpdef bint has_key(self, key):
        return self._map.has_key(key)

    cpdef object get(self, key, default=None):
        return self._map.get(key, default)

    cpdef list keys(self):
        return cpython.PyDict_Keys(self._map)

    cpdef object iterkeys(self):
        return self._map.iterkeys()

    cpdef list items(self):
        return cpython.PyDict_Items(self._map)

    cpdef object iteritems(self):
        return self._map.iteritems()

    cpdef list values(self):
        return cpython.PyDict_Values(self._map)

    cpdef object itervalues(self):
        return self._map.itervalues()

    def __getitem__(self, key):
        return self._map[key]

    def __setitem__(self, key, value):
        cdef Node n
        if isinstance(value, Node):
            n = value.copy()
        else:
            n = plist_t_to_node(native_to_plist_t(value), False)

        plist_dict_set_item(self._c_node, key, n._c_node)
        self._map[key] = n

    def __delitem__(self, key):
        cpython.PyDict_DelItem(self._map, key)
        plist_dict_remove_item(self._c_node, key.encode('utf-8'))

cdef Dict Dict_factory(plist_t c_node, bint managed=True):
    cdef Dict instance = Dict.__new__(Dict)
    instance._c_managed = managed
    instance._c_node = c_node
    instance._init()
    return instance

cdef plist_t create_array_plist(object value=None):
    cdef plist_t node = NULL
    cdef plist_t c_node = NULL
    node = plist_new_array()
    if value is not None and (isinstance(value, list) or isinstance(value, tuple)):
        for item in value:
            c_node = native_to_plist_t(item)
            plist_array_append_item(node, c_node)
            c_node = NULL
    return node

cdef class Array(Node):
    def __cinit__(self, object value=None, *args, **kwargs):
        self._c_node = create_array_plist(value)

    def __init__(self, value=None, *args, **kwargs):
        self._init()

    cdef void _init(self):
        self._array = []
        cdef uint32_t size = plist_array_get_size(self._c_node)
        cdef plist_t subnode = NULL

        for i in range(size):
            subnode = plist_array_get_item(self._c_node, i)
            self._array.append(plist_t_to_node(subnode, False))

    def __richcmp__(self, other, op):
        cdef list l = self.get_value()
        if op == 0:
            return l < other
        if op == 1:
            return l <= other
        if op == 2:
            return l == other
        if op == 3:
            return l != other
        if op == 4:
            return l > other
        if op == 5:
            return l >= other

    def __len__(self):
        return len(self._array)

    def __repr__(self):
        return '<Array: %s>' % self._array

    cpdef list get_value(self):
        return [i.get_value() for i in self]

    cpdef set_value(self, object value):
        self._array = []
        plist_free(self._c_node)
        self._c_node = NULL
        self._c_node = create_array_plist(value)
        self._init()

    def __iter__(self):
        return self._array.__iter__()

    def __getitem__(self, index):
        return self._array[index]

    def __setitem__(self, index, value):
        cdef Node n
        if isinstance(value, Node):
            n = value.copy()
        else:
            n = plist_t_to_node(native_to_plist_t(value), False)

        if index < 0:
            index = len(self) + index

        plist_array_set_item(self._c_node, n._c_node, index)
        self._array[index] = n

    def __delitem__(self, index):
        if index < 0:
            index = len(self) + index
        del self._array[index]
        plist_array_remove_item(self._c_node, index)

    cpdef append(self, object item):
        cdef Node n

        if isinstance(item, Node):
            n = item.copy()
        else:
            n = plist_t_to_node(native_to_plist_t(item), False)

        plist_array_append_item(self._c_node, n._c_node)
        self._array.append(n)

cdef Array Array_factory(plist_t c_node, bint managed=True):
    cdef Array instance = Array.__new__(Array)
    instance._c_managed = managed
    instance._c_node = c_node
    instance._init()
    return instance

cpdef object from_xml(xml):
    cdef plist_t c_node = NULL
    plist_from_xml(xml, len(xml), &c_node)
    return plist_t_to_node(c_node)

cpdef object from_bin(bytes bin):
    cdef plist_t c_node = NULL
    plist_from_bin(bin, len(bin), &c_node)
    return plist_t_to_node(c_node)

cdef plist_t native_to_plist_t(object native):
    cdef plist_t c_node
    cdef plist_t child_c_node
    cdef int32_t secs = 0
    cdef int32_t usecs = 0
    cdef Node node
    if isinstance(native, Node):
        node = native
        return plist_copy(node._c_node)
    if isinstance(native, basestring):
        return plist_new_string(native)
    if isinstance(native, bool):
        return plist_new_bool(<bint>native)
    if isinstance(native, int):
        return plist_new_uint(native)
    if isinstance(native, float):
        return plist_new_real(native)
    if isinstance(native, dict):
        return create_dict_plist(native)
    if isinstance(native, list) or isinstance(native, tuple):
        return create_array_plist(native)
    if check_datetime(native):
        return create_date_plist(native)

cdef object plist_t_to_node(plist_t c_plist, bint managed=True):
    cdef plist_type t = plist_get_node_type(c_plist)
    if t == PLIST_BOOLEAN:
        return Bool_factory(c_plist, managed)
    if t == PLIST_INT:
        return Integer_factory(c_plist, managed)
    if t == PLIST_KEY:
        return Key_factory(c_plist, managed)
    if t == PLIST_REAL:
        return Real_factory(c_plist, managed)
    if t == PLIST_STRING:
        return String_factory(c_plist, managed)
    if t == PLIST_ARRAY:
        return Array_factory(c_plist, managed)
    if t == PLIST_DICT:
        return Dict_factory(c_plist, managed)
    if t == PLIST_DATE:
        return Date_factory(c_plist, managed)
    if t == PLIST_DATA:
        return Data_factory(c_plist, managed)
    if t == PLIST_UID:
        return Uid_factory(c_plist, managed)
    if t == PLIST_NULL:
        return Null_factory(c_plist, managed)
    if t == PLIST_NONE:
        return None

# This is to match up with the new plistlib API
# http://docs.python.org/dev/library/plistlib.html
# dump() and dumps() are not yet implemented
FMT_XML = 1
FMT_BINARY = 2

cpdef object load(fp, fmt=None, use_builtin_types=True, dict_type=dict):
    is_binary = fp.read(6) == 'bplist'
    fp.seek(0)

    cdef object cb = None

    if not fmt:
        if is_binary:
            if 'b' not in fp.mode:
                raise IOError('File handle must be opened in binary (b) mode to read binary property lists')
            cb = from_bin
        else:
            cb = from_xml
    else:
        if fmt not in (FMT_XML, FMT_BINARY):
            raise ValueError('Format must be constant FMT_XML or FMT_BINARY')
        if fmt == FMT_BINARY:
            cb = from_bin
        elif fmt == FMT_XML:
            cb = from_xml

    if is_binary and fmt == FMT_XML:
        raise ValueError('Cannot parse binary property list as XML')
    elif not is_binary and fmt == FMT_BINARY:
        raise ValueError('Cannot parse XML property list as binary')

    return cb(fp.read())

cpdef object loads(data, fmt=None, use_builtin_types=True, dict_type=dict):
    is_binary = data[0:6] == 'bplist'

    cdef object cb = None

    if fmt is not None:
        if fmt not in (FMT_XML, FMT_BINARY):
            raise ValueError('Format must be constant FMT_XML or FMT_BINARY')
        if fmt == FMT_BINARY:
            cb = from_bin
        else:
            cb = from_xml
    else:
        if is_binary:
            cb = from_bin
        else:
            cb = from_xml

    if is_binary and fmt == FMT_XML:
        raise ValueError('Cannot parse binary property list as XML')
    elif not is_binary and fmt == FMT_BINARY:
        raise ValueError('Cannot parse XML property list as binary')

    return cb(data)

cpdef object dump(value, fp, fmt=FMT_XML, sort_keys=True, skipkeys=False):
    fp.write(dumps(value, fmt=fmt))

cpdef object dumps(value, fmt=FMT_XML, sort_keys=True, skipkeys=False):
    if fmt not in (FMT_XML, FMT_BINARY):
        raise ValueError('Format must be constant FMT_XML or FMT_BINARY')

    if check_datetime(value):
        node = Date(value)
    elif isinstance(value, unicode):
        node = String(value)
    elif PY_MAJOR_VERSION >= 3 and isinstance(value, bytes):
        node = Data(value)
    elif isinstance(value, str):
        # See if this is binary
        try:
            node = String(value)
        except ValueError:
            node = Data(value)
    elif isinstance(value, bool):
        node = Bool(value)
    elif isinstance(value, int):
        node = Integer(value)
    elif isinstance(value, float):
        node = Real(value)
    elif isinstance(value, dict):
        node = Dict(value)
    elif type(value) in (list, set, tuple):
        node = Array(value)
    else:
        node = value

    if fmt == FMT_XML:
        return node.to_xml().encode('utf-8')

    return node.to_bin()
