/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org 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 version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_connectivity.hxx"

#include "MacabRecords.hxx"
#include "MacabRecord.hxx"
#include "MacabHeader.hxx"
#include "macabutilities.hxx"

#include <premac.h>
#include <Carbon/Carbon.h>
#include <AddressBook/ABAddressBookC.h>
#include <postmac.h>
#include <com/sun/star/util/DateTime.hpp>

using namespace connectivity::macab;
using namespace com::sun::star::util;

// -------------------------------------------------------------------------
MacabRecords::MacabRecords(const ABAddressBookRef _addressBook, MacabHeader *_header, MacabRecord **_records, sal_Int32 _numRecords)
{
    /* Variables passed in... */
    header = _header;
    recordsSize = _numRecords;
    currentRecord = _numRecords;
    records = _records;
    addressBook = _addressBook;

    /* Default variables... */
    recordType = kABPersonRecordType;

    /* Variables constructed... */
    bootstrap_CF_types();
    bootstrap_requiredProperties();
}

// -------------------------------------------------------------------------
/* Creates a MacabRecords from another: copies the length, name, and
 * address book of the original, but the header or the records themselves.
 * The idea is that the only reason to copy a MacabRecords is to create
 * a filtered version of it, which can have the same length (to avoid
 * resizing) and will work from the same base addressbook, but might have
 * entirey different values and even (possibly in the future) a different
 * header.
 */
MacabRecords::MacabRecords(const MacabRecords *_copy)
{
    /* Variables passed in... */
    recordsSize = _copy->recordsSize;
    addressBook = _copy->addressBook;
    m_sName = _copy->m_sName;

    /* Default variables... */
    currentRecord = 0;
    header = NULL;
    records = new MacabRecord *[recordsSize];
    recordType = kABPersonRecordType;

    /* Variables constructed... */
    bootstrap_CF_types();
    bootstrap_requiredProperties();
}

// -------------------------------------------------------------------------
MacabRecords::MacabRecords(const ABAddressBookRef _addressBook)
{
    /* Variables passed in... */
    addressBook = _addressBook;

    /* Default variables... */
    recordsSize = 0;
    currentRecord = 0;
    records = NULL;
    header = NULL;
    recordType = kABPersonRecordType;

    /* Variables constructed... */
    bootstrap_CF_types();
    bootstrap_requiredProperties();
}

// -------------------------------------------------------------------------
void MacabRecords::initialize()
{

    /* Make sure everything is NULL before initializing. (We usually just
     * initialize after we use the constructor that takes only a
     * MacabAddressBook, so these variables will most likely already be
     * NULL.
     */
    if(records != NULL)
    {
        sal_Int32 i;

        for(i = 0; i < recordsSize; i++)
            delete records[i];

        delete [] records;
    }

    if(header != NULL)
        delete header;

    /* We can handle both default record Address Book record types in
     * this method, though only kABPersonRecordType is ever used.
     */
    CFArrayRef allRecords;
    if(CFStringCompare(recordType, kABPersonRecordType, 0) == kCFCompareEqualTo)
        allRecords = ABCopyArrayOfAllPeople(addressBook);
    else
        allRecords = ABCopyArrayOfAllGroups(addressBook);

    ABRecordRef record;
    sal_Int32 i;
    recordsSize = (sal_Int32) CFArrayGetCount(allRecords);
    records = new MacabRecord *[recordsSize];

    /* First, we create the header... */
    header = createHeaderForRecordType(allRecords, recordType);

    /* Then, we create each of the records... */
    for(i = 0; i < recordsSize; i++)
    {
        record = (ABRecordRef) CFArrayGetValueAtIndex(allRecords, i);
        records[i] = createMacabRecord(record, header, recordType);
    }
    currentRecord = recordsSize;
    
    CFRelease(allRecords);
}

// -------------------------------------------------------------------------
MacabRecords::~MacabRecords()
{
}

// -------------------------------------------------------------------------
void MacabRecords::setHeader(MacabHeader *_header)
{
    if(header != NULL)
        delete header;
    header = _header;
}

// -------------------------------------------------------------------------
MacabHeader *MacabRecords::getHeader() const
{
    return header;
}

// -------------------------------------------------------------------------
/* Inserts a MacabRecord at a given location. If there is already a
 * MacabRecord at that location, return it.
 */
MacabRecord *MacabRecords::insertRecord(MacabRecord *_newRecord, const sal_Int32 _location)
{
    MacabRecord *oldRecord;

    /* If the location is greater than the current allocated size of this
     * MacabRecords, allocate more space.
     */
    if(_location >= recordsSize)
    {
        sal_Int32 i;
        MacabRecord **newRecordsArray = new MacabRecord *[_location+1];
        for(i = 0; i < recordsSize; i++)
        {
            newRecordsArray[i] = records[i];
        }
        delete [] records;
        records = newRecordsArray;
    }

    /* Remember: currentRecord refers to one above the highest existing
     * record (i.e., it refers to where to place the next record if a
     * location is not given).
     */
    if(_location >= currentRecord)
        currentRecord = _location+1;
    
    oldRecord = records[_location];
    records[_location] = _newRecord;
    return oldRecord;
}

// -------------------------------------------------------------------------
/* Insert a record at the next available place. */
void MacabRecords::insertRecord(MacabRecord *_newRecord)
{
    insertRecord(_newRecord, currentRecord);
}

// -------------------------------------------------------------------------
MacabRecord *MacabRecords::getRecord(const sal_Int32 _location) const
{
    if(_location >= recordsSize)
        return NULL;
    return records[_location];
}

// -------------------------------------------------------------------------
macabfield *MacabRecords::getField(const sal_Int32 _recordNumber, const sal_Int32 _columnNumber) const
{
    if(_recordNumber >= recordsSize)
        return NULL;

    MacabRecord *record = records[_recordNumber];

    if(_columnNumber < 0 || _columnNumber >= record->getSize())
        return NULL;

    return record->get(_columnNumber);
}

// -------------------------------------------------------------------------
macabfield *MacabRecords::getField(const sal_Int32 _recordNumber, const ::rtl::OUString _columnName) const
{
    if(header != NULL)
    {
        sal_Int32 columnNumber = header->getColumnNumber(_columnName);
        if(columnNumber == -1)
            return NULL;

        return getField(_recordNumber, columnNumber);
    }
    else
    {
        // error: shouldn't access field with null header!
        return NULL;
    }
}

// -------------------------------------------------------------------------
sal_Int32 MacabRecords::getFieldNumber(const ::rtl::OUString _columnName) const
{
    if(header != NULL)
        return header->getColumnNumber(_columnName);
    else
        // error: shouldn't access field with null header!
        return -1;
}

// -------------------------------------------------------------------------
/* Create the lcl_CFTypes array -- we need this because there is no
 * way to get the ABType of an object from the object itself, and the
 * function ABTypeOfProperty can't handle multiple levels of data
 * (e.g., it can tell us that "address" is of type
 * kABDictionaryProperty, but it cannot tell us that all of the keys
 * and values in the dictionary have type kABStringProperty. On the
 * other hand, we _can_ get the CFType out of any object.
 * Unfortunately, all information about CFTypeIDs comes with the
 * warning that they change between releases, so we build them
 * ourselves here. (The one that we can't build is for multivalues,
 * e.g., kABMultiStringProperty. All of these appear to have the
 * same type: 1, but there is no function that I've found to give
 * us that dynamically in case that number ever changes.
 */
void MacabRecords::bootstrap_CF_types()
{
    lcl_CFTypesLength = 6;
    lcl_CFTypes = new lcl_CFType[lcl_CFTypesLength];

    lcl_CFTypes[0].cf = CFNumberGetTypeID();
    lcl_CFTypes[0].ab = kABIntegerProperty;

    lcl_CFTypes[1].cf = CFStringGetTypeID();
    lcl_CFTypes[1].ab = kABStringProperty;

    lcl_CFTypes[2].cf = CFDateGetTypeID();
    lcl_CFTypes[2].ab = kABDateProperty;

    lcl_CFTypes[3].cf = CFArrayGetTypeID();
    lcl_CFTypes[3].ab = kABArrayProperty;

    lcl_CFTypes[4].cf = CFDictionaryGetTypeID();
    lcl_CFTypes[4].ab = kABDictionaryProperty;

    lcl_CFTypes[5].cf = CFDataGetTypeID();
    lcl_CFTypes[5].ab = kABDataProperty;
}

// -------------------------------------------------------------------------
/* This is based on the possible fields required in the mail merge template
 * in sw. If the fields possible there change, it would be optimal to
 * change these fields as well.
 */
void MacabRecords::bootstrap_requiredProperties()
{
    numRequiredProperties = 7;
    requiredProperties = new CFStringRef[numRequiredProperties];
    requiredProperties[0] = kABTitleProperty;
    requiredProperties[1] = kABFirstNameProperty;
    requiredProperties[2] = kABLastNameProperty;
    requiredProperties[3] = kABOrganizationProperty;
    requiredProperties[4] = kABAddressProperty;
    requiredProperties[5] = kABPhoneProperty;
    requiredProperties[6] = kABEmailProperty;
}

// -------------------------------------------------------------------------
/* Create the header for a given record type and a given array of records.
 * Because the array of records and the record type are given, if you want
 * to, you can run this method on the members of a group, or on any other
 * filtered list of people and get a header relevant to them (e.g., if
 * they only have home addresses, the work address fields won't show up).
 */
MacabHeader *MacabRecords::createHeaderForRecordType(const CFArrayRef _records, const CFStringRef _recordType) const
{
    /* We have two types of properties for a given record type, nonrequired
     * and required. Required properties are ones that will show up whether
     * or not they are empty. Nonrequired properties will only show up if
     * at least one record in the set has that property filled. The reason
     * is that some properties, like the kABTitleProperty are required by
     * the mail merge wizard (in module sw) but are by default not shown in
     * the Mac OS X address book, so they would be weeded out at this stage
     * and not shown if they were not required.
     *
     * Note: with the addition of required properties, I am not sure that
     * this method still works for kABGroupRecordType (since the required
     * properites are all for kABPersonRecordType).
     *
     * Note: required properties are constructed in the method
     * bootstrap_requiredProperties() (above).
     */
    CFArrayRef allProperties = ABCopyArrayOfPropertiesForRecordType(addressBook, _recordType);
    CFStringRef *nonRequiredProperties;
    sal_Int32 numRecords = (sal_Int32) CFArrayGetCount(_records);
    sal_Int32 numProperties = (sal_Int32) CFArrayGetCount(allProperties); 
    sal_Int32 numNonRequiredProperties = numProperties - numRequiredProperties;

    /* While searching through the properties for required properties, these
     * sal_Bools will keep track of what we have found.
     */
    sal_Bool bFoundProperty;
    sal_Bool bFoundRequiredProperties[numRequiredProperties];


    /* We have three MacabHeaders: headerDataForProperty is where we
     * store the result of createHeaderForProperty(), which return a
     * MacabHeader for a single property. lcl_header is where we store
     * the MacabHeader that we are constructing. And, nonRequiredHeader
     * is where we construct the MacabHeader for non-required properties,
     * so that we can sort them before adding them to lcl_header.
     */
    MacabHeader *headerDataForProperty;
    MacabHeader *lcl_header = new MacabHeader();
    MacabHeader *nonRequiredHeader = new MacabHeader();

    /* Other variables... */
    sal_Int32 i, j, k;
    ABRecordRef record;
    CFStringRef property;


    /* Allocate and initialize... */
    nonRequiredProperties = new CFStringRef[numNonRequiredProperties];
    k = 0;
    for(i = 0; i < numRequiredProperties; i++)
        bFoundRequiredProperties[i] = sal_False;

    /* Determine the non-required properties... */
    for(i = 0; i < numProperties; i++)
    {
        property = (CFStringRef) CFArrayGetValueAtIndex(allProperties, i);
        bFoundProperty = sal_False;
        for(j = 0; j < numRequiredProperties; j++)
        {
            if(CFEqual(property, requiredProperties[j]))
            {
                bFoundProperty = sal_True;
                bFoundRequiredProperties[j] = sal_True;
                break;
            }
        }

        if(bFoundProperty == sal_False)
        {
            /* If we have found too many non-required properties */
            if(k == numNonRequiredProperties)
            {
                k++; // so that the OSL_ENSURE below fails
                break;
            }
            nonRequiredProperties[k] = property;
            k++;
        }
    }

    // Somehow, we got too many or too few non-requird properties...
    // Most likely, one of the required properties no longer exists, which
    // we also test later.
    OSL_ENSURE(k == numNonRequiredProperties, "MacabRecords::createHeaderForRecordType: Found an unexpected number of non-required properties");

    /* Fill the header with required properties first... */
    for(i = 0; i < numRequiredProperties; i++)
    {
        if(bFoundRequiredProperties[i] == sal_True)
        {
            /* The order of these matters (we want all address properties
             * before any phone properties, or else things will look weird),
             * so we get all possibilitities for each property, going through
             * each record, and then go onto the next property.
             * (Note: the reason that we have to go through all records
             * in the first place is that properties like address, phone, and
             * e-mail are multi-value properties with an unknown number of
             * values. A user could specify thirteen different kinds of
             * e-mail addresses for one of her or his contacts, and we need to
             * get all of them.
             */
            for(j = 0; j < numRecords; j++)
            {
                record = (ABRecordRef) CFArrayGetValueAtIndex(_records, j);
                headerDataForProperty = createHeaderForProperty(record,requiredProperties[i],_recordType,sal_True);
                if(headerDataForProperty != NULL)
                {
                    (*lcl_header) += headerDataForProperty;
                    delete headerDataForProperty;
                }
            }
        }
        else
        {
            // Couldn't find a required property...
            OSL_ENSURE(false, ::rtl::OString("MacabRecords::createHeaderForRecordType: could not find required property: ") +
                        ::rtl::OUStringToOString(CFStringToOUString(requiredProperties[i]), RTL_TEXTENCODING_ASCII_US));
        }
    }

    /* And now, non-required properties... */
    for(i = 0; i < numRecords; i++)
    {
        record = (ABRecordRef) CFArrayGetValueAtIndex(_records, i);

        for(j = 0; j < numNonRequiredProperties; j++)
        {
            property = nonRequiredProperties[j];
            headerDataForProperty = createHeaderForProperty(record,property,_recordType,sal_False);
            if(headerDataForProperty != NULL)
            {
                (*nonRequiredHeader) += headerDataForProperty;
                delete headerDataForProperty;
            }
        }

    }
    nonRequiredHeader->sortRecord();

    (*lcl_header) += nonRequiredHeader;
    delete nonRequiredHeader;

    CFRelease(allProperties);
    delete [] nonRequiredProperties;

    return lcl_header;
}

// -------------------------------------------------------------------------
/* Create a header for a single property. Basically, this method gets
 * the property's value and type and then calls another method of
 * the same name to do the dirty work.
 */
MacabHeader *MacabRecords::createHeaderForProperty(const ABRecordRef _record, const CFStringRef _propertyName, const CFStringRef _recordType, const sal_Bool _isPropertyRequired) const
{
    // local variables
    CFStringRef localizedPropertyName;
    CFTypeRef propertyValue;
    ABPropertyType propertyType;
    MacabHeader *result;

    /* Get the property's value */
    propertyValue = ABRecordCopyValue(_record,_propertyName);
    if(propertyValue == NULL && _isPropertyRequired == sal_False)
        return NULL;

    propertyType = ABTypeOfProperty(addressBook, _recordType, _propertyName);
    localizedPropertyName = ABCopyLocalizedPropertyOrLabel(_propertyName);

    result = createHeaderForProperty(propertyType, propertyValue, localizedPropertyName);

    if(propertyValue != NULL)
        CFRelease(propertyValue);

    return result;
}

// -------------------------------------------------------------------------
/* Create a header for a single property. This method is recursive
 * because a single property might contain several sub-properties that
 * we also want to treat singly.
 */
MacabHeader *MacabRecords::createHeaderForProperty(const ABPropertyType _propertyType, const CFTypeRef _propertyValue, const CFStringRef _propertyName) const
{
    macabfield **headerNames = NULL;
    sal_Int32 length = 0;

    switch(_propertyType)
    {
        /* Scalars */
        case kABStringProperty:
        case kABRealProperty:
        case kABIntegerProperty:
        case kABDateProperty:
            length = 1;
            headerNames = new macabfield *[1];
            headerNames[0] = new macabfield;
            headerNames[0]->value = _propertyName;
            headerNames[0]->type = _propertyType;
            break;

        /* Multi-scalars */
        case kABMultiIntegerProperty:
        case kABMultiDateProperty:
        case kABMultiStringProperty:
        case kABMultiRealProperty:
        case kABMultiDataProperty:
            /* For non-scalars, we can only get more information if the property
             * actually exists.
             */
            if(_propertyValue != NULL)
            {
            sal_Int32 i;
            
            sal_Int32 multiLength = ABMultiValueCount((ABMutableMultiValueRef) _propertyValue);
            CFStringRef multiLabel, localizedMultiLabel;
            ::rtl::OUString multiLabelString;
            ::rtl::OUString multiPropertyString;
            ::rtl::OUString headerNameString;
            ABPropertyType multiType = (ABPropertyType) (ABMultiValuePropertyType((ABMutableMultiValueRef) _propertyValue) - 0x100);

            length = multiLength;
            headerNames = new macabfield *[multiLength];
            multiPropertyString = CFStringToOUString(_propertyName);

            /* Go through each element, and - since each element is a scalar -
             * just create a new macabfield for it.
             */
            for(i = 0; i < multiLength; i++)
            {
                multiLabel = ABMultiValueCopyLabelAtIndex((ABMutableMultiValueRef) _propertyValue, i);
                localizedMultiLabel = ABCopyLocalizedPropertyOrLabel(multiLabel);
                multiLabelString = CFStringToOUString(localizedMultiLabel);
                CFRelease(multiLabel);
                CFRelease(localizedMultiLabel);
                headerNameString = multiPropertyString + ::rtl::OUString::createFromAscii(": ") + fixLabel(multiLabelString);
                headerNames[i] = new macabfield;
                headerNames[i]->value = OUStringToCFString(headerNameString);
                headerNames[i]->type = multiType;
            }
            }
            break;

        /* Multi-array or dictionary */
        case kABMultiArrayProperty:
        case kABMultiDictionaryProperty:
            /* For non-scalars, we can only get more information if the property
             * actually exists.
             */
            if(_propertyValue != NULL)
            {
                sal_Int32 i,j,k;

                // Total number of multi-array or multi-dictionary elements.
                sal_Int32 multiLengthFirstLevel = ABMultiValueCount((ABMutableMultiValueRef) _propertyValue);

                /* Total length, including the length of each element (e.g., if
                 * this multi-dictionary contains three dictionaries, and each
                 * dictionary has four elements, this variable will be twelve,
                 * whereas multiLengthFirstLevel will be three.
                 */
                sal_Int32 multiLengthSecondLevel = 0;

                CFStringRef multiLabel, localizedMultiLabel;
                CFTypeRef multiValue;
                ::rtl::OUString multiLabelString;
                ::rtl::OUString multiPropertyString;
                MacabHeader **multiHeaders = new MacabHeader *[multiLengthFirstLevel];
                ABPropertyType multiType = (ABPropertyType) (ABMultiValuePropertyType((ABMutableMultiValueRef) _propertyValue) - 0x100);

                multiPropertyString = CFStringToOUString(_propertyName);

                /* Go through each element - since each element can really
                 * contain anything, we run this method again on each element
                 * and store the resulting MacabHeader (in the multiHeaders
                 * array). Then, all we'll have to do is combine the MacabHeaders
                 * into a single one.
                 */
                for(i = 0; i < multiLengthFirstLevel; i++)
                {
                    /* label */
                    multiLabel = ABMultiValueCopyLabelAtIndex((ABMutableMultiValueRef) _propertyValue, i);
                    multiValue = ABMultiValueCopyValueAtIndex((ABMutableMultiValueRef) _propertyValue, i);
                    if(multiValue && multiLabel)
                    {
                        localizedMultiLabel = ABCopyLocalizedPropertyOrLabel(multiLabel);
                        multiLabelString = multiPropertyString + ::rtl::OUString::createFromAscii(": ") + fixLabel(CFStringToOUString(localizedMultiLabel));
                        CFRelease(multiLabel);
                        CFRelease(localizedMultiLabel);
                        multiLabel = OUStringToCFString(multiLabelString);
                        multiHeaders[i] = createHeaderForProperty(multiType, multiValue, multiLabel);
                        if (!multiHeaders[i])
                            multiHeaders[i] = new MacabHeader();
                        multiLengthSecondLevel += multiHeaders[i]->getSize();
                    }
                    else
                    {
                        multiHeaders[i] = new MacabHeader();
                    }
                    if(multiValue)
                        CFRelease(multiValue);
                }

                /* We now have enough information to create our final MacabHeader.
                 * We go through each field of each header and add it to the
                 * headerNames array (which is what is used below to construct
                 * the MacabHeader we return).
                 */
                length = multiLengthSecondLevel;
                headerNames = new macabfield *[multiLengthSecondLevel];

                for(i = 0, j = 0, k = 0; i < multiLengthSecondLevel; i++,k++)
                {
                    while(multiHeaders[j]->getSize() == k)
                    {
                        j++;
                        k = 0;
                    }
                    
                    headerNames[i] = multiHeaders[j]->copy(k);
                }
                for(i = 0; i < multiLengthFirstLevel; i++)
                    delete multiHeaders[i];

                delete [] multiHeaders;
            }
            break;
            
        /* Dictionary */
        case kABDictionaryProperty:
            /* For non-scalars, we can only get more information if the property
             * actually exists.
             */
            if(_propertyValue != NULL)
            {
            /* Assume all keys are strings */
            sal_Int32 numRecords = (sal_Int32) CFDictionaryGetCount((CFDictionaryRef) _propertyValue);

            /* The only method for getting info out of a CFDictionary, of both
             * keys and values, is to all of them all at once, so these
             * variables will hold them.
             */
            CFStringRef *dictKeys;
            CFTypeRef *dictValues;

            sal_Int32 i,j,k;
            ::rtl::OUString dictKeyString, propertyNameString;
            ABPropertyType dictType;
            MacabHeader **dictHeaders = new MacabHeader *[numRecords];
            ::rtl::OUString dictLabelString;
            CFStringRef dictLabel, localizedDictKey;

            /* Get the keys and values */
            dictKeys = (CFStringRef *) malloc(sizeof(CFStringRef)*numRecords);
            dictValues = (CFTypeRef *) malloc(sizeof(CFTypeRef)*numRecords);
            CFDictionaryGetKeysAndValues((CFDictionaryRef) _propertyValue, (const void **) dictKeys, (const void **) dictValues);

            propertyNameString = CFStringToOUString(_propertyName);

            length = 0;
            /* Go through each element - assuming that the key is a string but
             * that the value could be anything. Since the value could be
             * anything, we can't assume that it is scalar (it could even be
             * another dictionary), so we attempt to get its type using
             * the method getABTypeFromCFType and then run this method
             * recursively on that element, storing the MacabHeader that
             * results. Then, we just combine all of the MacabHeaders into
             * one.
             */
            for(i = 0; i < numRecords; i++)
            {
                dictType = (ABPropertyType) getABTypeFromCFType( CFGetTypeID(dictValues[i]) );
                localizedDictKey = ABCopyLocalizedPropertyOrLabel(dictKeys[i]);
                dictKeyString = CFStringToOUString(localizedDictKey);
                dictLabelString = propertyNameString + ::rtl::OUString::createFromAscii(": ") + fixLabel(dictKeyString);
                dictLabel = OUStringToCFString(dictLabelString);
                dictHeaders[i] = createHeaderForProperty(dictType, dictValues[i], dictLabel);
                if (!dictHeaders[i])
                    dictHeaders[i] = new MacabHeader();
                length += dictHeaders[i]->getSize();
                CFRelease(dictLabel);
                CFRelease(localizedDictKey);
            }

            /* Combine all of the macabfields in each MacabHeader into the
             * headerNames array, which (at the end of this method) is used
             * to create the MacabHeader that is returned.
             */
            headerNames = new macabfield *[length];
            for(i = 0, j = 0, k = 0; i < length; i++,k++)
            {
                while(dictHeaders[j]->getSize() == k)
                {
                    j++;
                    k = 0;
                }
                
                headerNames[i] = dictHeaders[j]->copy(k);
            }

            for(i = 0; i < numRecords; i++)
                delete dictHeaders[i];

            delete [] dictHeaders;
            free(dictKeys);
            free(dictValues);
            }
            break;

        /* Array */
        case kABArrayProperty:
            /* For non-scalars, we can only get more information if the property
             * actually exists.
             */
            if(_propertyValue != NULL)
            {
                sal_Int32 arrLength = (sal_Int32) CFArrayGetCount( (CFArrayRef) _propertyValue);
                sal_Int32 i,j,k;
                CFTypeRef arrValue;
                ABPropertyType arrType;
                MacabHeader **arrHeaders = new MacabHeader *[arrLength];
                ::rtl::OUString propertyNameString = CFStringToOUString(_propertyName);
                ::rtl::OUString arrLabelString;
                CFStringRef arrLabel;

                length = 0;
                /* Go through each element - since the elements here do not have
                 * unique keys like the ones in dictionaries, we create a unique
                 * key out of the id of the element in the array (the first
                 * element gets a 0 plopped onto the end of it, the second a 1...
                 * As with dictionaries, the elements could be anything, including
                 * another array, so we have to run this method recursively on
                 * each element, storing the resulting MacabHeader into an array,
                 * which we then combine into one MacabHeader that is returned.
                 */
                for(i = 0; i < arrLength; i++)
                {
                    arrValue = (CFTypeRef) CFArrayGetValueAtIndex( (CFArrayRef) _propertyValue, i);
                    arrType = (ABPropertyType) getABTypeFromCFType( CFGetTypeID(arrValue) );
                    arrLabelString = propertyNameString + ::rtl::OUString::valueOf(i);
                    arrLabel = OUStringToCFString(arrLabelString);
                    arrHeaders[i] = createHeaderForProperty(arrType, arrValue, arrLabel);
                    if (!arrHeaders[i])
                        arrHeaders[i] = new MacabHeader();
                    length += arrHeaders[i]->getSize();
                    CFRelease(arrLabel);
                }

                headerNames = new macabfield *[length];
                for(i = 0, j = 0, k = 0; i < length; i++,k++)
                {
                    while(arrHeaders[j]->getSize() == k)
                    {
                        j++;
                        k = 0;
                    }
                    
                    headerNames[i] = arrHeaders[j]->copy(k);
                }
                for(i = 0; i < arrLength; i++)
                    delete arrHeaders[i];

                delete [] arrHeaders;
            }
            break;

            default:
                break;

    }

    /* If we succeeded at adding elements to the headerNames array, then
     * length will no longer be 0. If it is, create a new MacabHeader
     * out of the headerNames (after weeding out duplicate headers), and
     * then return the result. If the length is still 0, return NULL: we
     * failed to create a MacabHeader out of this property.
     */
    if(length != 0)
    {
        manageDuplicateHeaders(headerNames, length);
        MacabHeader *headerResult = new MacabHeader(length, headerNames);
        delete [] headerNames;
        return headerResult;
    }
    else
        return NULL;
}

// -------------------------------------------------------------------------
void MacabRecords::manageDuplicateHeaders(macabfield **_headerNames, const sal_Int32 _length) const
{
    /* If we have two cases of, say, phone: home, this makes it:
     * phone: home (1)
     * phone: home (2)
     */
    sal_Int32 i, j;
    sal_Int32 count;
    for(i = _length-1; i >= 0; i--)
    {
        count = 1;
        for( j = i-1; j >= 0; j--)
        {
            if(CFEqual(_headerNames[i]->value, _headerNames[j]->value))
            {
                count++;
            }
        }

        // duplicate!
        if(count != 1)
        {
            // There is probably a better way to do this...
            ::rtl::OUString newName = CFStringToOUString((CFStringRef) _headerNames[i]->value);
            CFRelease(_headerNames[i]->value);
            newName += ::rtl::OUString::createFromAscii(" (") + ::rtl::OUString::valueOf(count) + ::rtl::OUString::createFromAscii(")");
            _headerNames[i]->value = OUStringToCFString(newName);
        }
    }
}

// -------------------------------------------------------------------------
/* Create a MacabRecord out of an ABRecord, using a given MacabHeader and
 * the record's type. We go through each property for this record type
 * then process it much like we processed the header (above), with two
 * exceptions: if we come upon something not in the header, we ignore it
 * (it's something we don't want to add), and once we find a corresponding
 * location in the header, we store the property and the property type in
 * a macabfield. (For the header, we stored the property type and the name
 * of the property as a CFString.)
 */
MacabRecord *MacabRecords::createMacabRecord(const ABRecordRef _abrecord, const MacabHeader *_header, const CFStringRef _recordType) const
{
    /* The new record that we will create... */
    MacabRecord *macabRecord = new MacabRecord(_header->getSize());

    CFArrayRef recordProperties = ABCopyArrayOfPropertiesForRecordType(addressBook, _recordType);
    sal_Int32 numProperties = (sal_Int32) CFArrayGetCount(recordProperties); 

    sal_Int32 i;

    CFTypeRef propertyValue;
    ABPropertyType propertyType;

    CFStringRef propertyName, localizedPropertyName;
    ::rtl::OUString propertyNameString;
    for(i = 0; i < numProperties; i++)
    {
        propertyName = (CFStringRef) CFArrayGetValueAtIndex(recordProperties, i);
        localizedPropertyName = ABCopyLocalizedPropertyOrLabel(propertyName);
        propertyNameString = CFStringToOUString(localizedPropertyName);
        CFRelease(localizedPropertyName);

        /* Get the property's value */
        propertyValue = ABRecordCopyValue(_abrecord,propertyName);
        if(propertyValue != NULL)
        {
            propertyType = ABTypeOfProperty(addressBook, _recordType, propertyName);
            if(propertyType != kABErrorInProperty)
                insertPropertyIntoMacabRecord(propertyType, macabRecord, _header, propertyNameString, propertyValue);

            CFRelease(propertyValue);
        }
    }
    CFRelease(recordProperties);
    return macabRecord;
}

// -------------------------------------------------------------------------
/* Inserts a given property into a MacabRecord. This method calls another
 * method by the same name after getting the property type (it only
 * receives the property value). It is called when we aren't given the
 * property's type already.
 */
void MacabRecords::insertPropertyIntoMacabRecord(MacabRecord *_abrecord, const MacabHeader *_header, const ::rtl::OUString _propertyName, const CFTypeRef _propertyValue) const
{
    CFTypeID cf_type = CFGetTypeID(_propertyValue);
    ABPropertyType ab_type = getABTypeFromCFType( cf_type );

    if(ab_type != kABErrorInProperty)
        insertPropertyIntoMacabRecord(ab_type, _abrecord, _header, _propertyName, _propertyValue);
}

// -------------------------------------------------------------------------
/* Inserts a given property into a MacabRecord. This method is recursive
 * because properties can contain many sub-properties.
 */
void MacabRecords::insertPropertyIntoMacabRecord(const ABPropertyType _propertyType, MacabRecord *_abrecord, const MacabHeader *_header, const ::rtl::OUString _propertyName, const CFTypeRef _propertyValue) const
{
    /* If there is no value, return */
    if(_propertyValue == NULL)
        return;

    /* The main switch statement */
    switch(_propertyType)
    {
        /* Scalars */
        case kABStringProperty:
        case kABRealProperty:
        case kABIntegerProperty:
        case kABDateProperty:
        {
            /* Only scalars actually insert a property into the MacabRecord.
             * In all other cases, this method is called recursively until a
             * scalar type, an error, or an unknown type are found.
             * Because of that, the following checks only occur for this type.
             * We store whether we have successfully placed this property
             * into the MacabRecord (or whether an unrecoverable error occured).
             * Then, we try over and over again to place the property into the
             * record. There are three possible results:
             * 1) Success!
             * 2) There is already a property stored at the column of this name,
             * in which case we have a duplicate header (see the method
             * manageDuplicateHeaders()). If that is the case, we add an ID
             * to the end of the column name in the same format as we do in
             * manageDuplicateHeaders() and try again.
             * 3) No column of this name exists in the header. In this case,
             * there is nothing we can do: we have failed to place this
             * property into the record.
             */
            sal_Bool bPlaced = sal_False;
            ::rtl::OUString columnName = ::rtl::OUString(_propertyName);
            sal_Int32 i = 1;
            
            // A big safeguard to prevent two fields from having the same name.
            while(bPlaced != sal_True)
            {
                sal_Int32 columnNumber = _header->getColumnNumber(columnName);
                bPlaced = sal_True;
                if(columnNumber != -1)
                {
                    // collision! A property already exists here!
                    if(_abrecord->get(columnNumber) != NULL)
                    {
                        bPlaced = sal_False;
                        i++;
                        columnName = ::rtl::OUString(_propertyName) + ::rtl::OUString::createFromAscii(" (") + ::rtl::OUString::valueOf(i) + ::rtl::OUString::createFromAscii(")");
                    }

                    // success!
                    else
                    {
                        _abrecord->insertAtColumn(_propertyValue, _propertyType, columnNumber);
                    }
                }
            }
        }
        break;

        /* Array */
        case kABArrayProperty:
            {
                /* An array is basically just a list of anything, so all we do
                 * is go through the array, and rerun this method recursively
                 * on each element.
                 */
                sal_Int32 arrLength = (sal_Int32) CFArrayGetCount( (CFArrayRef) _propertyValue);
                sal_Int32 i;
                const void *arrValue;
                ::rtl::OUString newPropertyName;

                /* Going through each element... */
                for(i = 0; i < arrLength; i++)
                {
                    arrValue = CFArrayGetValueAtIndex( (CFArrayRef) _propertyValue, i);
                    newPropertyName = _propertyName + ::rtl::OUString::valueOf(i);
                    insertPropertyIntoMacabRecord(_abrecord, _header, newPropertyName, arrValue);
                    CFRelease(arrValue);
                }

            }
            break;

        /* Dictionary */
        case kABDictionaryProperty:
            {
                /* A dictionary is basically a hashmap. Technically, it can
                 * hold any object as a key and any object as a value.
                 * For our case, we assume that the key is a string (so that
                 * we can use the key to get the column name and match it against
                 * the header), but we don't assume anything about the value, so
                 * we run this method recursively (or, rather, we run the version
                 * of this method for when we don't know the object's type) until
                 * we hit a scalar value.
                 */

                sal_Int32 numRecords = (sal_Int32) CFDictionaryGetCount((CFDictionaryRef) _propertyValue);
                ::rtl::OUString dictKeyString;
                sal_Int32 i;
                ::rtl::OUString newPropertyName;

                /* Unfortunately, the only way to get both keys and values out
                 * of a dictionary in Carbon is to get them all at once, so we
                 * do that.
                 */
                CFStringRef *dictKeys;
                CFStringRef localizedDictKey;
                CFTypeRef *dictValues;
                dictKeys = (CFStringRef *) malloc(sizeof(CFStringRef)*numRecords);
                dictValues = (CFTypeRef *) malloc(sizeof(CFTypeRef)*numRecords);
                CFDictionaryGetKeysAndValues((CFDictionaryRef) _propertyValue, (const void **) dictKeys, (const void **) dictValues);

                /* Going through each element... */
                for(i = 0; i < numRecords; i++)
                {
                    localizedDictKey = ABCopyLocalizedPropertyOrLabel(dictKeys[i]);
                    dictKeyString = CFStringToOUString(localizedDictKey);
                    CFRelease(localizedDictKey);
                    newPropertyName = _propertyName + ::rtl::OUString::createFromAscii(": ") + fixLabel(dictKeyString);
                    insertPropertyIntoMacabRecord(_abrecord, _header, newPropertyName, dictValues[i]);
                }

                free(dictKeys);
                free(dictValues);
            }
            break;

        /* Multivalue */
        case kABMultiIntegerProperty:
        case kABMultiDateProperty:
        case kABMultiStringProperty:
        case kABMultiRealProperty:
        case kABMultiDataProperty:
        case kABMultiDictionaryProperty:
        case kABMultiArrayProperty:
            {
                /* All scalar multivalues are handled in the same way. Each element
                 * is a label and a value. All labels are strings
                 * (kABStringProperty), and all values have the same type
                 * (which is the type of the multivalue minus 255, or as
                 * Carbon's list of property types has it, minus 0x100.
                 * We just get the correct type, then go through each element
                 * and get the label and value and print them in a list.
                 */

                sal_Int32 i;
                sal_Int32 multiLength = ABMultiValueCount((ABMutableMultiValueRef) _propertyValue);
                CFStringRef multiLabel, localizedMultiLabel;
                CFTypeRef multiValue;
                ::rtl::OUString multiLabelString, newPropertyName;
                ABPropertyType multiType = (ABPropertyType) (ABMultiValuePropertyType((ABMutableMultiValueRef) _propertyValue) - 0x100);

                /* Go through each element... */
                for(i = 0; i < multiLength; i++)
                {
                    /* Label and value */
                    multiLabel = ABMultiValueCopyLabelAtIndex((ABMutableMultiValueRef) _propertyValue, i);
                    multiValue = ABMultiValueCopyValueAtIndex((ABMutableMultiValueRef) _propertyValue, i);

                    localizedMultiLabel = ABCopyLocalizedPropertyOrLabel(multiLabel);
                    multiLabelString = CFStringToOUString(localizedMultiLabel);
                    newPropertyName = _propertyName + ::rtl::OUString::createFromAscii(": ") + fixLabel(multiLabelString);
                    insertPropertyIntoMacabRecord(multiType, _abrecord, _header, newPropertyName, multiValue);

                    /* free our variables */
                    CFRelease(multiLabel);
                    CFRelease(localizedMultiLabel);
                    CFRelease(multiValue);
                }
            }
            break;

        /* Unhandled types */
        case kABErrorInProperty:
        case kABDataProperty:
        default:
            /* An error, as far as I have seen, only shows up as a type
             * returned by a function for dictionaries when the dictionary
             * holds many types of values. Since we do not use that function,
             * it shouldn't come up. I have yet to see the kABDataProperty,
             * and I am not sure how to represent it as a string anyway,
             * since it appears to just be a bunch of bytes. Assumably, if
             * these bytes made up a string, the type would be
             * kABStringProperty. I think that this is used when we are not
             * sure what the type is (e.g., it could be a string or a number).
             * That being the case, I still don't know how to represent it.
             * And, default should never come up, since we've exhausted all
             * of the possible types for ABPropertyType, but... just in case.
             */
            break;
    }

}

// -------------------------------------------------------------------------
ABPropertyType MacabRecords::getABTypeFromCFType(const CFTypeID cf_type ) const
{
    sal_Int32 i;
    for(i = 0; i < lcl_CFTypesLength; i++)
    {
        /* A match! */
        if(lcl_CFTypes[i].cf == (sal_Int32) cf_type)
        {
            return (ABPropertyType) lcl_CFTypes[i].ab;
        }
    }
    return kABErrorInProperty;
}

// -------------------------------------------------------------------------
sal_Int32 MacabRecords::size() const
{
    return currentRecord;
}

// -------------------------------------------------------------------------
MacabRecords *MacabRecords::begin()
{
    return this;
}

// -------------------------------------------------------------------------
MacabRecords::iterator::iterator ()
{
}

// -------------------------------------------------------------------------
MacabRecords::iterator::~iterator ()
{
}

// -------------------------------------------------------------------------
void MacabRecords::iterator::operator= (MacabRecords *_records)
{
    id = 0;
    records = _records;
}

// -------------------------------------------------------------------------
void MacabRecords::iterator::operator++ ()
{
    id++;
}

// -------------------------------------------------------------------------
sal_Bool MacabRecords::iterator::operator!= (const sal_Int32 i) const
{
    return(id != i);
}

// -------------------------------------------------------------------------
sal_Bool MacabRecords::iterator::operator== (const sal_Int32 i) const
{
    return(id == i);
}

// -------------------------------------------------------------------------
MacabRecord *MacabRecords::iterator::operator* () const
{
    return records->getRecord(id);
}

// -------------------------------------------------------------------------
sal_Int32 MacabRecords::end() const
{
    return currentRecord;
}

// -------------------------------------------------------------------------
void MacabRecords::swap(const sal_Int32 _id1, const sal_Int32 _id2)
{
    MacabRecord *swapRecord = records[_id1];

    records[_id1] = records[_id2];
    records[_id2] = swapRecord;
}

// -------------------------------------------------------------------------
void MacabRecords::setName(const ::rtl::OUString _sName)
{
    m_sName = _sName;
}

// -------------------------------------------------------------------------
::rtl::OUString MacabRecords::getName() const
{
    return m_sName;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
