/* KPilot
**
** Copyright (C) 1998-2001 by Dan Pilone
** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
** Copyright (C) 2007 by Adriaan de Groot <groot@kde.org>
**
** This is a C++ wrapper for the pilot's address database structures.
*/

/*
** This program 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 program 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 program in a file called COPYING; if not, write to
** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
** MA 02110-1301, USA.
*/

/*
** Bug reports and questions can be sent to kde-pim@kde.org
*/


#include "options.h"


#include <stdlib.h>
#include <assert.h>

#include <tqnamespace.h>
#include <tqstringlist.h>

#include "pilotAddress.h"

static const char *default_address_category_names[] = {
	"Unfiled",
	"Business",
	"Personal",
	"Quicklist",
	0L
} ;

static const char *default_address_field_labels[] = {
	"Last name",
	"First name",
	"Company",
	"Work",
	"Home",
	"Fax",
	"Other",
	"E-mail",
	"Addr(W)",
	"City",
	"State",
	"Zip Code",
	"Country",
	"Title",
	"Custom 1",
	"Custom 2",
	"Custom 3",
	"Custom 4",
	"Note",
	0L
} ;

void PilotAddressInfo::resetToDefault()
{
	FUNCTIONSETUP;
	// Reset to all 0s
	memset(&fInfo,0,sizeof(fInfo));
	// Fill up default categories
	for (unsigned int i=0; (i<4) && default_address_category_names[i]; ++i)
	{
		strncpy(fInfo.category.name[i],default_address_category_names[i],sizeof(fInfo.category.name[0]));
	}
	// Weird hack, looks like there's an extra copy of Unfiled
	strncpy(fInfo.category.name[15],default_address_category_names[0],sizeof(fInfo.category.name[0]));

	// And fill up the default labels.
	for (unsigned int i=0; (i<19) && default_address_field_labels[i]; ++i)
	{
		strncpy(fInfo.labels[i],default_address_field_labels[i],sizeof(fInfo.labels[0]));
	}
}

TQString PilotAddressInfo::phoneLabel(EPhoneType i) const
{
	if (i<=eMobile)
	{
		return Pilot::fromPilot(info()->phoneLabels[i]);
	}
	else
	{
		return TQString();
	}
}

PhoneSlot::PhoneSlot( const int v )
{
	i = entryPhone1;
	operator=(v);
}

const PhoneSlot &PhoneSlot::operator=( const int &v )
{
	if ( (entryPhone1 <= v) && (v <= entryPhone5) )
	{
		i = v;
	}
	else
	{
		i = invalid;
	}
	return *this;
}

const PhoneSlot &PhoneSlot::operator++()
{
	if ( (i!=invalid) && (i<entryPhone5) )
	{
		++i;
	}
	else
	{
		i = invalid;
	}
	return *this;
}

/* static */ const PhoneSlot PhoneSlot::begin()
{
	return PhoneSlot( entryPhone1 );
}

/* static */ const PhoneSlot PhoneSlot::end()
{
	return PhoneSlot( invalid );
}

unsigned int PhoneSlot::toOffset() const
{
	if ( isValid() )
	{
		return i-entryPhone1;
	}
	else
	{
		return 0;
	}
}

unsigned int PhoneSlot::toField() const
{
	if ( isValid() )
	{
		return i;
	}
	else
	{
		return entryPhone1;
	}
}

PhoneSlot::operator TQString() const
{
	return TQString("%1,%2").arg(toOffset()).arg(toField());
}

#define MAXFIELDS 19

PilotAddress::PilotAddress(PilotRecord *rec) :
	PilotRecordBase(rec),
	fAddressInfo()
{
	FUNCTIONSETUPL(4);
	memset(&fAddressInfo,0,sizeof(fAddressInfo));

	if (rec)
	{
		pi_buffer_t b;
		b.data = (unsigned char *) rec->data();
		b.allocated = b.used = rec->size();
		unpack_Address(&fAddressInfo, &b, address_v1);
	}
	else
	{
		fAddressInfo.phoneLabel[0] = (int) PilotAddressInfo::eWork;
		fAddressInfo.phoneLabel[1] = (int) PilotAddressInfo::eHome;
		fAddressInfo.phoneLabel[2] = (int) PilotAddressInfo::eOther;
		fAddressInfo.phoneLabel[3] = (int) PilotAddressInfo::eMobile;
		fAddressInfo.phoneLabel[4] = (int) PilotAddressInfo::eEmail;
	}
}

PilotAddress::PilotAddress(const PilotAddress & copyFrom) :
	PilotRecordBase(copyFrom),
	fAddressInfo()
{
	FUNCTIONSETUPL(4);
	_copyAddressInfo(copyFrom.fAddressInfo);
}

PilotAddress & PilotAddress::operator = (const PilotAddress & copyFrom)
{
	FUNCTIONSETUPL(4);
	PilotRecordBase::operator = (copyFrom);
	_copyAddressInfo(copyFrom.fAddressInfo);
	return *this;
}

bool PilotAddress::operator==(const PilotAddress &compareTo)
{
	FUNCTIONSETUPL(4);

	// now compare all the fields stored in the fAddressInfo.entry array of char*[19]
	for (int i=0; i<MAXFIELDS; i++) {
		// if one is NULL, and the other non-empty, they are not equal for sure
		if ( !getFieldP(i) && compareTo.getFieldP(i))
		{
			return false;
		}
		if ( getFieldP(i) && !compareTo.getFieldP(i))
		{
			return false;
		}

		// test for getField(i)!=... to prevent strcmp or NULL strings!  None or both can be zero, but not a single one.
		if ( (getFieldP(i) != compareTo.getFieldP(i)) && ( strcmp(getFieldP(i), compareTo.getFieldP(i)) ) )
		{
			return false;
		}
	}
	return true;
}


void PilotAddress::_copyAddressInfo(const struct Address &copyFrom)
{
	FUNCTIONSETUPL(4);
	fAddressInfo.showPhone = copyFrom.showPhone;

	for (int labelLp = 0; labelLp < 5; labelLp++)
	{
		fAddressInfo.phoneLabel[labelLp] =
			copyFrom.phoneLabel[labelLp];
	}

	for (unsigned int i = 0; i< MAXFIELDS; ++i)
	{
		if (copyFrom.entry[i])
		{
			fAddressInfo.entry[i] = tqstrdup(copyFrom.entry[i]);
		}
		else
		{
			fAddressInfo.entry[i] = 0L;
		}
	}
}


PilotAddress::~PilotAddress()
{
	FUNCTIONSETUPL(4);
	free_Address(&fAddressInfo);
}

TQString PilotAddress::getTextRepresentation(const PilotAddressInfo *info, TQt::TextFormat richText) const
{
	TQString text, tmp;

	TQString par = (richText==TQt::RichText) ?CSL1("<p>"): TQString();
	TQString ps = (richText==TQt::RichText) ?CSL1("</p>"):CSL1("\n");
	TQString br = (richText==TQt::RichText) ?CSL1("<br/>"):CSL1("\n");

	// title + name
	text += par;
	if (!getField(entryTitle).isEmpty())
	{
		text += rtExpand(getField(entryTitle), richText);
		text += CSL1(" ");
	}

	tmp = richText ? CSL1("<b><big>%1 %2</big></b>") : CSL1("%1 %2");
	TQString firstName = getField(entryFirstname);
	if (firstName.isEmpty())
	{
		// So replace placeholder for first name (%1) with empty
		tmp = tmp.arg(TQString());
	}
	else
	{
		tmp = tmp.arg(rtExpand(firstName,richText));
	}
	tmp=tmp.arg(rtExpand(getField(entryLastname), richText));
	text += tmp;
	text += ps;

	// company
	if (!getField(entryCompany).isEmpty())
	{
		text += par;
		text += rtExpand(getField(entryCompany), richText);
		text += ps;
	}

	// phone numbers (+ labels)
	text += par;
	for ( PhoneSlot i = PhoneSlot::begin(); i.isValid(); ++i )
	{
		if (!getField(i.toField()).isEmpty())
		{
			if (richText)
			{
				if (getShownPhone() == i)
				{
					tmp=CSL1("<small>%1: </small><b>%2</b>");
				}
				else
				{
					tmp=CSL1("<small>%1: </small>%2");
				}
			}
			else
			{
				tmp=CSL1("%1: %2");
			}
			if (info)
			{
				tmp=tmp.arg(info->phoneLabel( getPhoneType( i ) ));
			}
			else
			{
				tmp=tmp.arg(CSL1("Contact: "));
			}
			tmp=tmp.arg(rtExpand(getField(i.toField()), richText));
			text += tmp;
			text += br;
		}
	}
	text += ps;

	// address, city, state, country
	text += par;
	if (!getField(entryAddress).isEmpty())
	{
		text += rtExpand(getField(entryAddress), richText);
		text += br;
	}
	if (!getField(entryCity).isEmpty())
	{
		text += rtExpand(getField(entryCity), richText);
		text += CSL1(" ");
	}
	if (!getField(entryState).isEmpty())
	{
		text += rtExpand(getField(entryState), richText);
		text += CSL1(" ");
	}
	if (!getField(entryZip).isEmpty())
	{
		text += rtExpand(getField(entryZip), richText);
	}
	text += br;
	if (!getField(entryCountry).isEmpty())
	{
		text += rtExpand(getField(entryCountry), richText);
		text += br;
	}
	text += ps;

	// custom fields
	text += par;
	for (int i = entryCustom1; i <= entryCustom4; i++)
	{
		if (!getField(i).isEmpty())
		{
			text += rtExpand(getField(i), richText);
			text += br;
		}
	}
	text += ps;

	// category
	if (info)
	{
		TQString categoryName = info->categoryName( category() );
		if (!categoryName.isEmpty())
		{
			text += par;
			text += rtExpand(categoryName, richText);
			text += ps;
		}
	}

	// note
	if (!getField(entryNote).isEmpty())
	{
		text += richText?CSL1("<hr/>"):CSL1("-----------------------------\n");
		text += par;
		text += rtExpand(getField(entryNote), richText);
		text += ps;
	}

	return text;
}

TQStringList PilotAddress::getEmails() const
{
	TQStringList list;

	for ( PhoneSlot i = PhoneSlot::begin(); i.isValid(); ++i)
	{
		PilotAddressInfo::EPhoneType t = getPhoneType( i );
		if ( t == PilotAddressInfo::eEmail )
		{
			TQString s = getField(i.toField());
			if (!s.isEmpty())
			{
				list.append(s);
			}
		}
	}

	return list;
}

void PilotAddress::setEmails(const TQStringList &list)
{
	FUNCTIONSETUPL(4);
	TQString test;

	// clear all e-mails first
	for ( PhoneSlot i = PhoneSlot::begin(); i.isValid(); ++i )
	{
		PilotAddressInfo::EPhoneType t = getPhoneType( i );
		if (t == PilotAddressInfo::eEmail)
		{
			setField(i.toField(), TQString() );
		}
	}

	for(TQStringList::ConstIterator listIter = list.begin();
		   listIter != list.end(); ++listIter)
	{
		TQString email = *listIter;
		if (!setPhoneField(PilotAddressInfo::eEmail, email, NoFlags).isValid())
		{
			WARNINGKPILOT << "Email accounts overflowed, silently dropped." << endl;
		}
	}
}

TQString PilotAddress::getField(int field) const
{
	if ( (entryLastname <= field) && (field <= entryNote) )
	{
		return Pilot::fromPilot(fAddressInfo.entry[field]);
	}
	else
	{
		return TQString();
	}
}

PhoneSlot PilotAddress::_getNextEmptyPhoneSlot() const
{
	FUNCTIONSETUPL(4);
	for (PhoneSlot i = PhoneSlot::begin(); i.isValid(); ++i)
	{
		const char *phoneField = getFieldP(i.toField());

		if (!phoneField || !phoneField[0])
		{
			return i;
		}
	}
	return PhoneSlot();
}

PhoneSlot PilotAddress::setPhoneField(PilotAddressInfo::EPhoneType type,
	const TQString &field,
	PhoneHandlingFlags flags)
{
	FUNCTIONSETUPL(4);

	const bool overwriteExisting = (flags == Replace);
	PhoneSlot fieldSlot;
	if (overwriteExisting)
	{
		fieldSlot = _findPhoneFieldSlot(type);
	}

	if ( !fieldSlot.isValid() )
	{
		fieldSlot = _getNextEmptyPhoneSlot();
	}

	// store the overflow phone
	if ( !fieldSlot.isValid() )
	{
		DEBUGKPILOT << fname << ": Phone would overflow." << endl;
	}
	else			// phone field 1 - 5; straight forward storage
	{
		setField(fieldSlot.toField(), field);
		fAddressInfo.phoneLabel[fieldSlot.toOffset()] = (int) type;
	}
	return fieldSlot;
}

PhoneSlot PilotAddress::_findPhoneFieldSlot(PilotAddressInfo::EPhoneType t) const
{
	FUNCTIONSETUPL(4);
	for ( PhoneSlot i = PhoneSlot::begin(); i.isValid(); ++i )
	{
		if ( getPhoneType(i) == t )
		{
			return i;
		}
	}

	return PhoneSlot();
}

TQString PilotAddress::getPhoneField(PilotAddressInfo::EPhoneType type) const
{
	FUNCTIONSETUPL(4);
	PhoneSlot fieldSlot = _findPhoneFieldSlot(type);

	if (fieldSlot.isValid())
	{
		return getField(fieldSlot.toField());
	}

	return TQString();
}

PhoneSlot PilotAddress::getShownPhone() const
{
	// The slot is stored as an offset
	return PhoneSlot(entryPhone1 + fAddressInfo.showPhone);
}

const PhoneSlot &PilotAddress::setShownPhone( const PhoneSlot &v )
{
	FUNCTIONSETUPL(4);
	if (v.isValid())
	{
		fAddressInfo.showPhone = v.toOffset();
	}
	return v;
}

PhoneSlot PilotAddress::setShownPhone(PilotAddressInfo::EPhoneType type)
{
	FUNCTIONSETUPL(4);
	PhoneSlot fieldSlot = _findPhoneFieldSlot(type);

	// Did we find a slot with the requested type?
	if (!fieldSlot.isValid())
	{
		// No, so look for first non-empty phone slot
		for ( fieldSlot = PhoneSlot::begin(); fieldSlot.isValid(); ++fieldSlot )
		{
			const char *p = getFieldP(fieldSlot.toField());
			if (p && p[0])
			{
				break;
			}
		}
		// If all of them are empty, then use first slot instead
		if (!fieldSlot.isValid())
		{
			fieldSlot = PhoneSlot::begin();
		}
	}
	setShownPhone(fieldSlot);
	return fieldSlot;
}

PilotAddressInfo::EPhoneType PilotAddress::getPhoneType( const PhoneSlot &field ) const
{
	if ( field.isValid() )
	{
		return (PilotAddressInfo::EPhoneType) fAddressInfo.phoneLabel[field.toOffset()];
	}
	else
	{
		return PilotAddressInfo::eNone;
	}
}

void PilotAddress::setField(int field, const TQString &text)
{
	FUNCTIONSETUPL(4);
	// This will have either been created with unpack_Address, and/or will
	// be released with free_Address, so use malloc/free here:
	if (fAddressInfo.entry[field])
	{
		free(fAddressInfo.entry[field]);
		fAddressInfo.entry[field]=0L;
	}
	if (!text.isEmpty())
	{
		fAddressInfo.entry[field] = (char *) malloc(text.length() + 1);
		Pilot::toPilot(text, fAddressInfo.entry[field], text.length()+1);
	}
	else
	{
		fAddressInfo.entry[field] = 0L;
	}
}

PilotRecord *PilotAddress::pack() const
{
	FUNCTIONSETUPL(4);
	int i;

	pi_buffer_t *b = pi_buffer_new( sizeof(fAddressInfo) );
	i = pack_Address(const_cast<Address_t *>(&fAddressInfo), b, address_v1);
	if (i<0)
	{
		return 0L;
	}
	// pack_Address sets b->used
	return new PilotRecord( b, this );
}