/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



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

#include "MacabRecord.hxx"
#include "macabutilities.hxx"
#include <com/sun/star/util/DateTime.hpp>

#include <premac.h>
#include <Carbon/Carbon.h>
#include <AddressBook/ABAddressBookC.h>
#include <postmac.h>
#include <connectivity/dbconversion.hxx>

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

// -------------------------------------------------------------------------
MacabRecord::MacabRecord()
{
	size = 0;
	fields = NULL;
}

// -------------------------------------------------------------------------
MacabRecord::MacabRecord(const sal_Int32 _size)
{
	size = _size;
	fields = new macabfield *[size];
	sal_Int32 i;
	for(i = 0; i < size; i++)
		fields[i] = NULL;
}

// -------------------------------------------------------------------------
MacabRecord::~MacabRecord()
{
	if(size > 0)
	{
		int i;
		for(i = 0; i < size; i++)
		{
			delete fields[i];
			fields[i] = NULL;
		}
	}
	delete [] fields;
	fields = NULL;
}

// -------------------------------------------------------------------------
void MacabRecord::insertAtColumn (CFTypeRef _value, ABPropertyType _type, const sal_Int32 _column)
{
	if(_column < size)
	{
		if(fields[_column] == NULL)
			fields[_column] = new macabfield;

		fields[_column]->value = _value;
		if (fields[_column]->value)
			CFRetain(fields[_column]->value);
		fields[_column]->type = _type;
	}
}

// -------------------------------------------------------------------------
sal_Bool MacabRecord::contains (const macabfield *_field) const
{
	if(_field == NULL)
		return sal_False;
	else
		return contains(_field->value);
}

// -------------------------------------------------------------------------
sal_Bool MacabRecord::contains (const CFTypeRef _value) const
{
	sal_Int32 i;
	for(i = 0; i < size; i++)
	{
		if(fields[i] != NULL)
		{
			if(CFEqual(fields[i]->value, _value))
			{
				return sal_True;
			}
		}
	}

	return sal_False;
}

// -------------------------------------------------------------------------
sal_Int32 MacabRecord::getSize() const
{
	return size;
}

// -------------------------------------------------------------------------
macabfield *MacabRecord::copy(const sal_Int32 i) const
{
	/* Note: copy(i) creates a new macabfield identical to that at
	 * location i, whereas get(i) returns a pointer to the macabfield
	 * at location i.
	 */
	if(i < size)
	{
		macabfield *_copy = new macabfield;
		_copy->type = fields[i]->type;
		_copy->value = fields[i]->value;
		if (_copy->value)
			CFRetain(_copy->value);
		return _copy;
	}

	return NULL;
}

// -------------------------------------------------------------------------
macabfield *MacabRecord::get(const sal_Int32 i) const
{
	/* Note: copy(i) creates a new macabfield identical to that at
	 * location i, whereas get(i) returns a pointer to the macabfield
	 * at location i.
	 */
	if(i < size)
	{
		return fields[i];
	}

	return NULL;
}

// -------------------------------------------------------------------------
void MacabRecord::releaseFields()
{
	/* This method is, at the moment, only used in MacabHeader.cxx, but
	 * the idea is simple: if you are not destroying this object but want
	 * to clear it of its macabfields, you should release each field's
	 * value.
	 */
	sal_Int32 i;
	for(i = 0; i < size; i++)
		CFRelease(fields[i]->value);
}

// -------------------------------------------------------------------------
sal_Int32 MacabRecord::compareFields(const macabfield *_field1, const macabfield *_field2) 
{
	
	/* When comparing records, if either field is NULL (and the other is
	 * not), that field is considered "greater than" the other, so that it
	 * shows up later in the list when fields are ordered.
	 */
	if(_field1 == _field2)
		return 0;
	if(_field1 == NULL)
		return 1;
	if(_field2 == NULL)
		return -1;

	/* If they aren't the same type, for now, return the one with
	 * the smaller type ID... I don't know of a better way to compare
	 * two different data types.
	 */
	if(_field1->type != _field2->type)
		return(_field1->type - _field2->type);

	CFComparisonResult result;

	/* Carbon has a unique compare function for each data type: */
	switch(_field1->type)
	{
		case kABStringProperty:
			result = CFStringCompare(
				(CFStringRef) _field1->value,
				(CFStringRef) _field2->value,
				kCFCompareLocalized); // Specifies that the comparison should take into account differences related to locale, such as the thousands separator character.
			break;

		case kABDateProperty:
			result = CFDateCompare(
				(CFDateRef) _field1->value,
				(CFDateRef) _field2->value,
				NULL); // NULL = unused variable
			break;

		case kABIntegerProperty:
		case kABRealProperty:
			result = CFNumberCompare(
				(CFNumberRef) _field1->value,
				(CFNumberRef) _field2->value,
				NULL); // NULL = unused variable
		break;

		default:
			result = kCFCompareEqualTo; // can't compare
	}

	return (sal_Int32) result;
}

// -------------------------------------------------------------------------
/* Create a macabfield out of an OUString and type. Together with the
 * method fieldToString() (below), it is possible to switch conveniently
 * between an OUString and a macabfield (for use when creating and handling
 * SQL statement).
 */
macabfield *MacabRecord::createMacabField(const ::rtl::OUString _newFieldString, const ABPropertyType _abType)
{
	macabfield *newField = NULL;
	switch(_abType)
	{
		case kABStringProperty:
			newField = new macabfield;
			newField->value = OUStringToCFString(_newFieldString);
			newField->type = _abType;
			break;
		case kABDateProperty:
			{
				DateTime aDateTime = DBTypeConversion::toDateTime(_newFieldString);
				
				// bad format...
				if(aDateTime.Year == 0 && aDateTime.Month == 0 && aDateTime.Day == 0)
				{
				}
				else
				{
					double nTime = DBTypeConversion::toDouble(aDateTime, DBTypeConversion::getStandardDate());
					nTime -= kCFAbsoluteTimeIntervalSince1970;
					newField = new macabfield;
					newField->value = CFDateCreate(NULL, (CFAbsoluteTime) nTime);
					newField->type = _abType;
				}
			}
			break;
		case kABIntegerProperty:
			try
			{
				sal_Int64 nVal = _newFieldString.toInt64();

				newField = new macabfield;
				newField->value = CFNumberCreate(NULL,kCFNumberLongType, &nVal);
				newField->type = _abType;
			}
			// bad format...
			catch(...)
			{
			}
			break;
		case kABRealProperty:
			try
			{
				double nVal = _newFieldString.toDouble();

				newField = new macabfield;
				newField->value = CFNumberCreate(NULL,kCFNumberDoubleType, &nVal);
				newField->type = _abType;
			}
			// bad format...
			catch(...)
			{
			}
			break;
		default:
			;
	}
	return newField;
}

// -------------------------------------------------------------------------
/* Create an OUString out of a macabfield. Together with the method
 * createMacabField() (above), it is possible to switch conveniently
 * between an OUString and a macabfield (for use when creating and handling
 * SQL statement).
 */
::rtl::OUString MacabRecord::fieldToString(const macabfield *_aField)
{
	if(_aField == NULL)
		return ::rtl::OUString();

	::rtl::OUString fieldString;

	switch(_aField->type)
	{
		case kABStringProperty:
			fieldString = CFStringToOUString((CFStringRef) _aField->value);
			break;
		case kABDateProperty:
			{
				DateTime aTime = CFDateToDateTime((CFDateRef) _aField->value);
				fieldString = DBTypeConversion::toDateTimeString(aTime);
			}
			break;
		case kABIntegerProperty:
			{
				CFNumberType numberType = CFNumberGetType( (CFNumberRef) _aField->value );
				sal_Int64 nVal;
				// Should we check for the wrong type here, e.g., a float?
				sal_Bool m_bSuccess = !CFNumberGetValue((CFNumberRef) _aField->value, numberType, &nVal);
				if(m_bSuccess != sal_False)
					fieldString = ::rtl::OUString::valueOf(nVal);
			}
			break;
		case kABRealProperty:
			{
				CFNumberType numberType = CFNumberGetType( (CFNumberRef) _aField->value );
				double nVal;
				// Should we check for the wrong type here, e.g., an int?
				sal_Bool m_bSuccess = !CFNumberGetValue((CFNumberRef) _aField->value, numberType, &nVal);
				if(m_bSuccess != sal_False)
					fieldString = ::rtl::OUString::valueOf(nVal);
			}
			break;
		default:
			;
	}
	return fieldString;

}