/*
 *  Python extension for reading a Mac OS X plist file.
 *  Copyright (C) 2004  Andreas Junghans <aj@lucid-cake.net>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "cfdate.h"
#include "util.h"
#include "globals.h"


// calculates the len of a 0-terminated
// unicode string
int unilen(UniChar* unicodeString)
{
    int i;
    for (i = 0;;++i)
    {
        if (unicodeString[i] == 0)
        {
            return i;
        }
    }
}


// converts a CFString to a python unicode string
PyObject* PyUnicodeFromCFString(CFStringRef string)
{
    int			stringLength;
    UniChar*	buffer;
    PyObject*	pyRetVal;
    
    stringLength = (int) CFStringGetLength(string);
    buffer = PyMem_Malloc(stringLength*sizeof(UniChar));
    if (buffer == NULL)
    {
        return NULL;
    }
    CFStringGetCharacters(string, CFRangeMake(0, stringLength), buffer);
    pyRetVal = PyUnicode_FromUnicode(buffer, stringLength);
    PyMem_Free(buffer);

    return pyRetVal;
}


// converts a python string (either unicode or ASCII)
// to a CFString
CFStringRef CFStringFromPyString(PyObject* pyString)
{
    CFStringRef		retVal = NULL;
    CFIndex			length;
    
    if (PyUnicode_Check(pyString))
    {
        length = (CFIndex) PyUnicode_GET_SIZE(pyString);
        retVal = CFStringCreateWithCharacters(NULL, PyUnicode_AS_UNICODE(pyString), length);
        if (retVal == NULL)
        {
            PyErr_SetString(PyExc_ValueError, "error converting unicode string to CFString");
        }
    }
    else if (PyString_Check(pyString))
    {
        length = (CFIndex) PyString_GET_SIZE(pyString);
        retVal = CFStringCreateWithBytes(NULL, PyString_AS_STRING(pyString), length, kCFStringEncodingASCII, false);
        if (retVal == NULL)
        {
            PyErr_SetString(PyExc_ValueError, "error converting string to CFString");
        }
    }
    else
    {
        PyErr_SetString(PyExc_TypeError, "expected a unicode object or a string to convert to a CFString");
    }
    return retVal;
}


// converts a CFDate to a python object
PyObject* PyDateFromCFDate(CFDateRef date)
{
    return (PyObject*) cfdate_create(date);
}


// "converts" a python date to a CFDate
CFDateRef CFDateFromPyDate(PyObject* pyDate)
{
    if (!PyObject_IsInstance(pyDate, (PyObject*) &cfdate_type))
    {
        PyErr_SetString(PyExc_TypeError, "expected a CFDate object");
        return NULL;
    }
    
    return CFRetain(((CFDateObject*) pyDate)->_theDate);
}


// converts a CFNumber to a python number
PyObject* PyNumberFromCFNumber(CFNumberRef numberValue)
{
    PyObject*		pyRetVal = NULL;
    CFNumberType	numberType;
    char			numberChar;
    short int		numberShortInt;
    int				numberInt;
    long int		numberLongInt;
    long long		numberLongLong;
    float			numberFloat;
    double			numberDouble;
    CFIndex			numberByteSize;
    Boolean			numberIsFloat;

    numberByteSize = CFNumberGetByteSize(numberValue);
    numberType = CFNumberGetType(numberValue);
    numberIsFloat = CFNumberIsFloatType(numberValue);
    if (numberIsFloat)
    {
        if (numberByteSize == sizeof(float))
        {
            CFNumberGetValue(numberValue, numberType, &numberFloat);
            pyRetVal = PyFloat_FromDouble(numberFloat);
        }
        else if (numberByteSize == sizeof(double))
        {
            CFNumberGetValue(numberValue, numberType, &numberDouble);
            pyRetVal = PyFloat_FromDouble(numberDouble);
        }
        else
        {
            PyErr_SetString(PyExc_ValueError, "cannot convert floating point property value");
        }
    }
    else
    {
        if (numberByteSize == sizeof(char))
        {
            CFNumberGetValue(numberValue, numberType, &numberChar);
            pyRetVal = PyInt_FromLong(numberChar);
        }
        else if (numberByteSize == sizeof(short int))
        {
            CFNumberGetValue(numberValue, numberType, &numberShortInt);
            pyRetVal = PyInt_FromLong(numberShortInt);
        }
        else if (numberByteSize == sizeof(int))
        {
            CFNumberGetValue(numberValue, numberType, &numberInt);
            pyRetVal = PyInt_FromLong(numberInt);
        }
        else if (numberByteSize == sizeof(long int))
        {
            CFNumberGetValue(numberValue, numberType, &numberLongInt);
            pyRetVal = PyInt_FromLong(numberLongInt);
        }
        else if (numberByteSize == sizeof(long long))
        {
            CFNumberGetValue(numberValue, numberType, &numberLongLong);
            pyRetVal = PyLong_FromLongLong(numberLongLong);
        }
        else
        {
            PyErr_SetString(PyExc_ValueError, "cannot convert integer property value");
        }
    }

    return pyRetVal;
}


// converts a python number to a CFNumber
CFNumberRef CFNumberFromPyNumber(PyObject* pyNumber)
{
    if (PyFloat_Check(pyNumber))
    {
        double value = PyFloat_AS_DOUBLE(pyNumber);
        return CFNumberCreate(NULL, kCFNumberDoubleType, &value);
    }
    if (PyInt_Check(pyNumber))
    {
        long value = PyInt_AS_LONG(pyNumber);
        return CFNumberCreate(NULL, kCFNumberLongType, &value);
    }
    if (PyLong_Check(pyNumber))
    {
        long long value = PyLong_AsLongLong(pyNumber);
        return CFNumberCreate(NULL, kCFNumberLongLongType, &value);
    }
    PyErr_SetString(PyExc_TypeError, "expected a numeric type");
    return NULL;
}


// converts a CFArray to a python list
PyObject* PyListFromCFArray(CFArrayRef arrayValue) {
    CFTypeRef		arrayElement;
    PyObject*		pyRetVal;
    PyObject*		pyContainedValue;
    int				arrayElementCount;
    int				i;
    
    arrayElementCount = (int) CFArrayGetCount(arrayValue);
    pyRetVal = PyList_New(arrayElementCount);
    if (pyRetVal != NULL)
    {
        for (i = 0; i < arrayElementCount; ++i)
        {
            arrayElement = CFArrayGetValueAtIndex(arrayValue, i);
            pyContainedValue = convertToPyObject(arrayElement);
            if (pyContainedValue == NULL)
            {
                Py_DECREF(pyRetVal);
                pyRetVal = NULL;
                break;
            }
            PyList_SET_ITEM(pyRetVal, i, pyContainedValue);
                // use the version without any error checking;
                // this "steals" our reference
        }
    }
    
    return pyRetVal;
}


// converter function for properties in an array
/*static PyObject* propertyConverter(ABPropertyType* param, const void* arrayElement)
{
    return convertToPyObject(*param, arrayElement);
}*/


// converts a CFArray containing properties to a python list
/*PyObject* PyListFromPropertyArray(CFArrayRef arrayValue)
{
    ABPropertyType propertyType = kABErrorInProperty;
    return PyListFromCFArray(arrayValue, (ArrayElementToPyObjectConverter) propertyConverter,
                             &propertyType);
}*/


// converts a python list to a CFArray
// (also converts all the objects contained in the list)
/*CFMutableArrayRef CFArrayFromPyList(PyObject* pyList)
{
    CFMutableArrayRef	retVal = NULL;
    CFTypeRef			containedObject = NULL;
    int					numEntries;
    int					i;
    
    if (!PyList_Check(pyList))
    {
        PyErr_SetString(PyExc_TypeError, "expected a list to convert to a CFArray");
        return NULL;
    }
    numEntries = PyList_GET_SIZE(pyList);
    retVal = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
        // use 0 for the capacity in case the caller wants to add further values
    if (!retVal)
    {
        PyErr_SetString(AddressBookError, "could not create a mutable array");
    }
    else
    {
        for (i = 0; i < numEntries; ++i)
        {
            containedObject = convertToCFType(kABErrorInProperty, PyList_GET_ITEM(pyList, i));
            if (containedObject == NULL)
            {
                CFRelease(retVal);
                retVal = NULL;
                break;
            }
            CFArrayAppendValue(retVal, containedObject);
            CFRelease(containedObject);
        }
    }
    
    return retVal;
}*/


// converts a CFDictionary to a python dictionary
PyObject* PyDictFromCFDict(CFDictionaryRef dictValue)
{
    const void**    dictKeys;
    const void**    dictValues;
    CFTypeRef		dictKeyOrValue;
    PyObject*		pyContainedKey;
    PyObject*		pyContainedValue = NULL;
    PyObject*		pyRetVal = NULL;
    int				dictElementCount;
    int				check;
    int				i;
    
    dictElementCount = (int) CFDictionaryGetCount(dictValue);
    dictKeys = PyMem_Malloc(dictElementCount*sizeof(const void*));
    if (dictKeys != NULL)
    {
        dictValues = PyMem_Malloc(dictElementCount*sizeof(const void*));
        if (dictValues == NULL)
        {
            PyMem_Free(dictKeys);
        }
        else
        {
            pyRetVal = PyDict_New();
            if (pyRetVal != NULL)
            {
                CFDictionaryGetKeysAndValues(dictValue, dictKeys, dictValues);
                for (i = 0; i < dictElementCount; ++i)
                {
                    dictKeyOrValue = (CFTypeRef) dictKeys[i];
                    pyContainedKey = convertToPyObject(dictKeyOrValue);
                    if (pyContainedKey != NULL)
                    {
                        dictKeyOrValue = (CFTypeRef) dictValues[i];
                        pyContainedValue = convertToPyObject(dictKeyOrValue);
                        if (pyContainedValue == NULL)
                        {
                            Py_DECREF(pyContainedKey);
                            pyContainedKey = NULL;
                        }
                    }
                    if (pyContainedKey == NULL)
                    {
                        Py_DECREF(pyRetVal);
                        pyRetVal = NULL;
                        break;
                    }
                    check = PyDict_SetItem(pyRetVal, pyContainedKey, pyContainedValue);
                    Py_DECREF(pyContainedKey);
                    Py_DECREF(pyContainedValue);
                    if (check)
                    {
                        Py_DECREF(pyRetVal);
                        pyRetVal = NULL;
                        break;
                    }
                }	// for (i = 0; i < dictElementCount; ++i)
            }	// if (pyRetVal != NULL)
            PyMem_Free(dictValues);
            PyMem_Free(dictKeys);
        }	// if (dictValues == NULL) else
    }	// if (dictKeys != NULL)
    
    return pyRetVal;
}


// converts a python dictionary to a CFDictionary
// (also converts all the objects contained in the dictionary)
/*CFMutableDictionaryRef CFDictFromPyDict(PyObject* pyDict)
{
    CFMutableDictionaryRef	retVal = NULL;
    CFTypeRef				containedKey = NULL;
    CFTypeRef				containedValue = NULL;
    PyObject*				pyKey;
    PyObject*				pyValue;
    int						pos = 0;
    
    if (!PyDict_Check(pyDict))
    {
        PyErr_SetString(PyExc_TypeError, "expected a dictionary");
        return NULL;
    }
    retVal = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        // use 0 for the capacity so the caller can append more values
    if (retVal == NULL)
    {
        PyErr_SetString(AddressBookError, "could not create mutable dictionary");
    }
    else
    {
        while (PyDict_Next(pyDict, &pos, &pyKey, &pyValue))
        {
            containedKey = convertToCFType(kABErrorInProperty, pyKey);
            if (containedKey == NULL)
            {
                CFRelease(retVal);
                retVal = NULL;
                break;
            }
            containedValue = convertToCFType(kABErrorInProperty, pyValue);
            if (containedValue == NULL)
            {
                CFRelease(containedKey);
                CFRelease(retVal);
                retVal = NULL;
                break;
            }
            CFDictionaryAddValue(retVal, containedKey, containedValue);
            CFRelease(containedKey);
            CFRelease(containedValue);
        }
    }
    
    return retVal;
}*/


// Converts a CFData value into a python string
PyObject* PyStringFromCFData(CFDataRef dataValue)
{
    CFIndex			dataLength;
    const UInt8*	dataPointer;
    
    dataLength = CFDataGetLength(dataValue);
    dataPointer = CFDataGetBytePtr(dataValue);
    
    return PyString_FromStringAndSize(dataPointer, dataLength);
}


// Converts a python string into a CFData object.
CFMutableDataRef CFDataFromPyString(PyObject* pyString)
{
    CFMutableDataRef	retVal = NULL;
    CFIndex				numBytes;
    
    if (!PyString_Check(pyString))
    {
        PyErr_SetString(PyExc_TypeError, "expected a string to convert to a CFData");
        return NULL;
    }
    numBytes = (CFIndex) PyString_GET_SIZE(pyString);
    retVal = CFDataCreateMutable(NULL, numBytes);
    if (retVal == NULL)
    {
        PyErr_SetString(PyExc_MemoryError, "cannot create mutable data object");
    }
    else
    {
        if (numBytes > 0)
        {
            CFDataAppendBytes(retVal, PyString_AS_STRING(pyString), numBytes);
        }
    }
    
    return retVal;
}


// examines a CF type and returns an appropriate
// ABPropertyType (necessary for conversion of
// array elements and similar things)
/*ABPropertyType getPropertyType(CFTypeRef typeRef)
{
    if (typeRef)
    {
        CFTypeID typeID = CFGetTypeID(typeRef);
        if (typeID == CFStringGetTypeID())
        {
            return kABStringProperty;
        }
        if (typeID == CFNumberGetTypeID())
        {
            if (CFNumberIsFloatType((CFNumberRef) typeRef))
            {
                return kABRealProperty;
            }
            else
            {
                return kABIntegerProperty;
            }
        }
        if (typeID == CFDateGetTypeID())
        {
            return kABDateProperty;
        }
        if (typeID == CFArrayGetTypeID())
        {
            return kABArrayProperty;
        }
        if (typeID == CFDictionaryGetTypeID())
        {
            return kABDictionaryProperty;
        }
        if (typeID == CFDataGetTypeID())
        {
            return kABDataProperty;
        }
    }	// if (typeRef)
    
    // Note: There seems to be no way to identify a multi property
    // (but multi inside something else is not allowed anyway in the address book)
    return kABErrorInProperty;
}*/


// Examines a python object and returns an appropriate
// ABPropertyType (necessary for conversion of
// array elements and similar things). The best thing is
// to convert according to the property type of a property
// that should be set. However, there are cases (e.g. dictionaries)
// where it's not possible to determine the exact expected type.
// If this is the case, getPythonPropertyType() is handy to do
// a conversion that is at least remotely sensible.
/*ABPropertyType getPythonPropertyType(PyObject* pyObject)
{
    if (pyObject != Py_None && pyObject != NULL)
    {
        if (PyUnicode_Check(pyObject) || PyString_Check(pyObject))
        {
            return kABStringProperty;
        }
        // Note that a data property cannot be detected (since it's also
        // represented as a string). For the built-in address book properties,
        // this is not a problem since none of the dictionaries there
        // contain a CFData.
        if (PyFloat_Check(pyObject))
        {
            return kABRealProperty;
        }
        if (PyInt_Check(pyObject) || PyLong_Check(pyObject))
        {
            return kABIntegerProperty;
        }
        if (PyList_Check(pyObject))
        {
            return kABArrayProperty;
        }
        if (PyDict_Check(pyObject))
        {
            return kABDictionaryProperty;
        }
        if (PyObject_IsInstance(pyObject, (PyObject*) &abmultivalue_type))
        {
            return kABMultiValueMask;
        }
    }	// if (pyObject != Py_None && pyObject != NULL)
    
    return kABErrorInProperty;
}*/


// Converts a CF object to a python object.
PyObject* convertToPyObject(CFTypeRef cfObject)
{
    // if the property value is NULL, return Py_None
    if (cfObject == NULL)
    {
        Py_INCREF(Py_None);
        return Py_None;
    }

    // create an appropriate python object
    CFTypeID typeID = CFGetTypeID(cfObject);
    if (typeID == CFStringGetTypeID()) {
        return PyUnicodeFromCFString((CFStringRef) cfObject);
    }
    if (typeID == CFDateGetTypeID()) {
        return PyDateFromCFDate((CFDateRef) cfObject);
    }
    if (typeID == CFNumberGetTypeID()) {
        return PyNumberFromCFNumber((CFNumberRef) cfObject);
    }
    if (typeID == CFArrayGetTypeID()) {
        return PyListFromCFArray((CFArrayRef) cfObject);
    }
    if (typeID == CFDictionaryGetTypeID()) {
        return PyDictFromCFDict((CFDictionaryRef) cfObject);
    }
    if (typeID == CFDataGetTypeID()) {
        return PyStringFromCFData(cfObject);
    }
    if (typeID == CFBooleanGetTypeID()) {
        long number;
        CFNumberGetValue(cfObject, kCFNumberLongType, &number);
        return PyInt_FromLong(number);
    }
    PyErr_SetString(PyExc_TypeError, "unknown object type");
    return NULL;
}


// Converts a python property value to a CFType
/*CFTypeRef convertToCFType(ABPropertyType propertyType, PyObject* propertyValue)
{
    // raise an exception if the property value is Py_None
    // (returning NULL is reserved for error indication)
    if (propertyValue == Py_None)
    {
        PyErr_SetString(PyExc_TypeError, "cannot convert None to a Core Foundation object");
        return NULL;
    }
    
    // if no sensible property is given, try to determine one
    if (propertyType == kABErrorInProperty)
    {
        propertyType = getPythonPropertyType(propertyValue);
    }

    // create an appropriate CF object
    if (propertyType == kABStringProperty)
    {
        return CFStringFromPyString(propertyValue);
    }
    if (propertyType == kABDateProperty)
    {
        return CFDateFromPyDate(propertyValue);
    }
    if (propertyType == kABIntegerProperty ||
        propertyType == kABRealProperty)
    {
        return CFNumberFromPyNumber(propertyValue);
    }
    if (propertyType == kABArrayProperty)
    {
        return CFArrayFromPyList(propertyValue);
    }
    if (propertyType == kABDictionaryProperty)
    {
        return CFDictFromPyDict(propertyValue);
    }
    if (propertyType == kABDataProperty)
    {
        return CFDataFromPyString(propertyValue);
    }
    if (propertyType & kABMultiValueMask)
    {
        return ABMultiValueFromPyMultiValue(propertyValue);
    }
    PyErr_SetString(PyExc_TypeError, "unknown property type");
    return NULL;
}*/
