/* This file is part of the KDE project
   Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
   Copyright (C) 2004 Alexander Dymo <cloudtemple@mskat.net>
   Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
*/

#include "property.h"
#include "customproperty.h"
#include "set.h"
#include "factory.h"

#include <kdebug.h>

#include <tqobject.h>
#include <tqptrdict.h>
#include <tqasciidict.h>
#include <tqguardedptr.h>

namespace KoProperty {

const Property Property::null;

//! @internal
class PropertyPrivate
{
	public:
		PropertyPrivate()
		: caption(0), listData(0), changed(false), storable(true), 
		 readOnly(false), visible(true),
		 autosync(-1), custom(0), useCustomProperty(true),
		 sets(0), parent(0), children(0), relatedProperties(0),
		 sortingKey(0)
		{
		}

		inline void setCaptionForDisplaying(const TQString& captionForDisplaying)
		{
			delete caption;
			if (captionForDisplaying.simplifyWhiteSpace()!=captionForDisplaying)
				caption = new TQString(captionForDisplaying.simplifyWhiteSpace());
			else
				caption = 0;
			this->captionForDisplaying = captionForDisplaying;
		}

		~PropertyPrivate()
		{
			delete caption;
			caption = 0;
			delete listData;
			delete children;
			delete relatedProperties;
			delete custom;
			delete sets;
		}

	int type;
	TQCString name;
	TQString captionForDisplaying;
	TQString* caption;
	TQString description;
	TQVariant value;
	TQVariant oldValue;
	/*! The string-to-value correspondence list of the property.*/
	Property::ListData* listData;
//	TQStringVariantMap *valueList;
	TQString icon;

	bool changed : 1;
	bool storable : 1;
	bool readOnly : 1;
	bool visible : 1;
	int autosync;
	TQMap<TQCString, TQVariant> options;

	CustomProperty *custom;
	//! Flag used to allow CustomProperty to use setValue()
	bool useCustomProperty;

	//! Used when a single set is assigned for the property
	TQGuardedPtr<Set> set;
	//! Used when multiple sets are assigned for the property
	TQPtrDict< TQGuardedPtr<Set> > *sets;
//	TQValueList<Set*>  sets;

	Property  *parent;
	TQValueList<Property*>  *children;
	//! list of properties with the same name (when intersecting buffers)
	TQValueList<Property*>  *relatedProperties;

	int sortingKey;
};
}

using namespace KoProperty;

/////////////////////////////////////////////////////////////////

Property::ListData::ListData(const TQStringList& keys_, const TQStringList& names_)
 : names(names_)
// , fixed(true)
{
	setKeysAsStringList(keys_);
}

Property::ListData::ListData(const TQValueList<TQVariant> keys_, const TQStringList& names_)
 : keys(keys_), names(names_)
// , fixed(true)
{
}

Property::ListData::ListData()
// : fixed(true)
{
}

Property::ListData::~ListData()
{
}

void Property::ListData::setKeysAsStringList(const TQStringList& list)
{
	keys.clear();
	for (TQStringList::ConstIterator it = list.constBegin(); it!=list.constEnd(); ++it) {
		keys.append(*it);
	}
}

TQStringList Property::ListData::keysAsStringList() const
{
	TQStringList result;
	for (TQValueList<TQVariant>::ConstIterator it = keys.constBegin(); it!=keys.constEnd(); ++it) {
		result.append((*it).toString());
	}
	return result;
}

/////////////////////////////////////////////////////////////////

/*
KOPROPERTY_EXPORT TQStringVariantMap
KoProperty::createValueListFromStringLists(const TQStringList &keys, const TQStringList &values)
{
	TQStringVariantMap map;
	if(keys.count() != values.count())
		return map;

	TQStringList::ConstIterator valueIt = values.begin();
	TQStringList::ConstIterator endIt = keys.constEnd();
	for(TQStringList::ConstIterator it = keys.begin(); it != endIt; ++it, ++valueIt)
		map.insert( *it, *valueIt);

	return map;
}
*/


Property::Property(const TQCString &name, const TQVariant &value,
	const TQString &caption, const TQString &description,
	int type, Property* parent)
 : d( new PropertyPrivate() )
{
	d->name = name;
	d->setCaptionForDisplaying(caption);
	d->description = description;

	if(type == Auto)
		d->type = value.type();
	else
		d->type = type;

	d->custom = FactoryManager::self()->createCustomProperty(this);

	if (parent)
		parent->addChild(this);
	setValue(value, false);
}

Property::Property(const TQCString &name, const TQStringList &keys, const TQStringList &strings,
	const TQVariant &value, const TQString &caption, const TQString &description, 
	int type, Property* parent)
 : d( new PropertyPrivate() )
{
	d->name = name;
	d->setCaptionForDisplaying(caption);
	d->description = description;
	d->type = type;
	setListData(keys, strings);

	d->custom = FactoryManager::self()->createCustomProperty(this);

	if (parent)
		parent->addChild(this);
	setValue(value, false);
}

Property::Property(const TQCString &name, ListData* listData, 
	const TQVariant &value, const TQString &caption, const TQString &description, 
	int type, Property* parent)
 : d( new PropertyPrivate() )
{
	d->name = name;
	d->setCaptionForDisplaying(caption);
	d->description = description;
	d->type = type;
	d->listData = listData;

	d->custom = FactoryManager::self()->createCustomProperty(this);

	if (parent)
		parent->addChild(this);
	setValue(value, false);
}

Property::Property()
 : d( new PropertyPrivate() )
{
}

Property::Property(const Property &prop)
 : d( new PropertyPrivate() )
{
	*this = prop;
}

Property::~Property()
{
	delete d;
	d = 0;
}

TQCString
Property::name() const
{
	return d->name;
}

void
Property::setName(const TQCString &name)
{
	d->name = name;
}

TQString
Property::caption() const
{
	return d->caption ? *d->caption : d->captionForDisplaying;
}

TQString
Property::captionForDisplaying() const
{
	return d->captionForDisplaying;
}

void
Property::setCaption(const TQString &caption)
{
	d->setCaptionForDisplaying(caption);
}

TQString
Property::description() const
{
	return d->description;
}

void
Property::setDescription(const TQString &desc)
{
	d->description = desc;
}

int
Property::type() const
{
	return d->type;
}

void
Property::setType(int type)
{
	d->type = type;
}

TQString
Property::icon() const
{
	return d->icon;
}

void
Property::setIcon(const TQString &icon)
{
	d->icon = icon;
}

TQVariant
Property::value() const
{
	if(d->custom && d->custom->handleValue())
		return d->custom->value();
	return d->value;
}

TQVariant
Property::oldValue() const
{
/*	if(d->oldValue.isNull())
		return value();
	else*/
		return d->oldValue;
}

void
Property::setValue(const TQVariant &value, bool rememberOldValue, bool useCustomProperty)
{
	if (d->name.isEmpty()) {
		kopropertywarn << "Property::setValue(): COULD NOT SET value to a null property" << endl;
		return;
	}
	TQVariant currentValue = this->value();
	const TQVariant::Type t = currentValue.type();
	const TQVariant::Type newt = value.type();
//	kopropertydbg << d->name << " : setValue('" << value.toString() << "' type=" << type() << ")" << endl;
	if (t != newt && !currentValue.isNull() && !value.isNull()
		 && !( (t==TQVariant::Int && newt==TQVariant::UInt)
			   || (t==TQVariant::UInt && newt==TQVariant::Int)
			   || (t==TQVariant::CString && newt==TQVariant::String)
			   || (t==TQVariant::String && newt==TQVariant::CString)
			   || (t==TQVariant::ULongLong && newt==TQVariant::LongLong)
			   || (t==TQVariant::LongLong && newt==TQVariant::ULongLong)
		 )) {
		kopropertywarn << "Property::setValue(): INCOMPATIBLE TYPES! old=" << currentValue 
			<< " new=" << value << endl;
	}

	//1. Check if the value should be changed
	bool ch;
	if (t == TQVariant::DateTime
		|| t == TQVariant::Time) {
		//for date and datetime types: compare with strings, because there
		//can be miliseconds difference
		ch = (currentValue.toString() != value.toString());
	}
	else if (t == TQVariant::String || t==TQVariant::CString) {
		//property is changed for string type,
		//if one of value is empty and other isn't..
		ch = ( (currentValue.toString().isEmpty() != value.toString().isEmpty())
		//..or both are not empty and values differ
			|| (!currentValue.toString().isEmpty() && !value.toString().isEmpty() && currentValue != value) );
	}
	else if (t == TQVariant::Invalid && newt == TQVariant::Invalid)
		ch = false;
	else
		ch = (currentValue != value);

	if (!ch)
		return;

	//2. Then change it, and store old value if necessary
	if(rememberOldValue) {
		if(!d->changed)
			d->oldValue = currentValue;
		d->changed = true;
	}
	else {
		d->oldValue = TQVariant(); // clear old value
		d->changed = false;
	}
	TQVariant prevValue;
	if(d->custom && useCustomProperty) {
		d->custom->setValue(value, rememberOldValue);
		prevValue = d->custom->value();
	}
	else
		prevValue = currentValue;

	if (!d->custom || !useCustomProperty || !d->custom->handleValue())
		d->value = value;

	emitPropertyChanged(); // called as last step in this method!
}

void
Property::resetValue()
{
	d->changed = false;
	bool cleared = false;
	if (d->set)
		d->set->informAboutClearing(cleared); //inform me about possibly clearing the property sets
	setValue(oldValue(), false);
	if (cleared)
		return; //property set has been cleared: no further actions make sense as 'this' is dead

	// maybe parent  prop is also unchanged now
	if(d->parent && d->parent->value() == d->parent->oldValue())
		d->parent->d->changed = false;

	if (d->sets) {
		for (TQPtrDictIterator< TQGuardedPtr<Set> > it(*d->sets); it.current(); ++it) {
			if (it.current()) //may be destroyed in the meantime
				emit (*it.current())->propertyReset(**it.current(), *this);
		}
	}
	else if (d->set) {
		emit d->set->propertyReset(*d->set, *this);
	}
}

//const TQStringVariantMap*
Property::ListData*
Property::listData() const
{
	return d->listData;
}

void
Property::setListData(ListData* list) //const TQStringVariantMap &list)
{
//	if(!d->valueList)
//		d->valueList = new TQStringVariantMap();
	if (list == d->listData)
		return;
	delete d->listData;
	d->listData = list;
}

void
Property::setListData(const TQStringList &keys, const TQStringList &names)
{
	ListData* list = new ListData(keys, names);
	setListData(list);

//	if(!d->valueList)
//		d->valueList = new TQStringVariantMap();
//	*(d->valueList) = createValueListFromStringLists(keys, values);
}

////////////////////////////////////////////////////////////////

bool
Property::isNull() const
{
	return d->name.isEmpty();
}

bool
Property::isModified() const
{
	return d->changed;
}

void
Property::clearModifiedFlag()
{
	d->changed = false;
}

bool
Property::isReadOnly() const
{
	return d->readOnly;
}

void
Property::setReadOnly(bool readOnly)
{
	d->readOnly = readOnly;
}

bool
Property::isVisible() const
{
	return d->visible;
}

void
Property::setVisible(bool visible)
{
	d->visible = visible;
}

int
Property::autoSync() const
{
	return d->autosync;
}

void
Property::setAutoSync(int sync)
{
	d->autosync = sync;
}

bool
Property::isStorable() const
{
	return d->storable;
}

void
Property::setStorable(bool storable)
{
	d->storable = storable;
}

void
Property::setOption(const char* name, const TQVariant& val)
{
	d->options[name] = val;
}

TQVariant
Property::option(const char* name) const
{
	if (d->options.contains(name))
		return d->options[name];
	return TQVariant();
}

bool
Property::hasOptions() const
{
	return !d->options.isEmpty();
}

/////////////////////////////////////////////////////////////////

Property::operator bool () const
{
	return !isNull();
}

const Property&
Property::operator= (const TQVariant& val)
{
	setValue(val);
	return *this;
}

const Property&
Property::operator= (const Property &property)
{
	if(&property == this)
		return *this;

	if(d->listData) {
		delete d->listData;
		d->listData = 0;
	}
	if(d->children) {
		delete d->children;
		d->children = 0;
	}
	if(d->relatedProperties) {
		delete d->relatedProperties;
		d->relatedProperties = 0;
	}
	if(d->custom) {
		delete d->custom;
		d->custom = 0;
	}

	d->name = property.d->name;
	d->setCaptionForDisplaying(property.captionForDisplaying());
	d->description = property.d->description;
	d->type = property.d->type;

	d->icon = property.d->icon;
	d->autosync = property.d->autosync;
	d->visible = property.d->visible;
	d->storable = property.d->storable;
	d->readOnly = property.d->readOnly;
	d->options = property.d->options;

	if(property.d->listData) {
		d->listData = new ListData(*property.d->listData); //TQStringVariantMap(*(property.d->valueList));
	}
	if(property.d->custom) {
		d->custom = FactoryManager::self()->createCustomProperty(this);
		// updates all children value, using CustomProperty
		setValue(property.value());
	}
	else {
		d->value = property.d->value;
		if(property.d->children) {
			// no CustomProperty (should never happen), simply copy all children
			d->children = new TQValueList<Property*>();
			TQValueList<Property*>::ConstIterator endIt = property.d->children->constEnd();
			for(TQValueList<Property*>::ConstIterator it = property.d->children->constBegin(); it != endIt; ++it) {
				Property *child = new Property( *(*it) );
				addChild(child);
			}
		}
	}

	if(property.d->relatedProperties) {
		d->relatedProperties = new TQValueList<Property*>( *(property.d->relatedProperties));
	}

	// update these later because they may have been changed when creating children
	d->oldValue = property.d->oldValue;
	d->changed = property.d->changed;
	d->sortingKey = property.d->sortingKey;

	return *this;
}

bool
Property::operator ==(const Property &prop) const
{
	return ((d->name == prop.d->name) && (value() == prop.value()));
}

/////////////////////////////////////////////////////////////////

const TQValueList<Property*>*
Property::children() const
{
	return d->children;
}

Property*
Property::child(const TQCString &name)
{
	TQValueList<Property*>::ConstIterator endIt = d->children->constEnd();
	for(TQValueList<Property*>::ConstIterator it = d->children->constBegin(); it != endIt; ++it) {
		if((*it)->name() == name)
			return *it;
	}
	return 0;
}

Property*
Property::parent() const
{
	return d->parent;
}

void
Property::addChild(Property *prop)
{
	if (!prop)
		return;

	if(!d->children || tqFind( d->children->begin(), d->children->end(), prop) == d->children->end()) { // not in our list
		if(!d->children)
			d->children = new TQValueList<Property*>();
		d->children->append(prop);
		prop->setSortingKey(d->children->count());
		prop->d->parent = this;
	}
	else {
		kopropertywarn << "Property::addChild(): property \"" << name() 
			<< "\": child property \"" << prop->name() << "\" already added" << endl;
		return;
	}
}

void
Property::addSet(Set *set)
{
	if (!set)
		return;

	if (!d->set) {//simple case
		d->set = set;
		return;
	}
	if ((Set*)d->set==set)
		return;
	TQGuardedPtr<Set> *pset = d->sets ? d->sets->find(set) : 0;
	if (pset && (Set*)*pset == set)
		return;
	if (!d->sets) {
		d->sets = new TQPtrDict< TQGuardedPtr<Set> >( 101 );
		d->sets->setAutoDelete(true);
	}

	d->sets->replace(set, new TQGuardedPtr<Set>( set ));

//	TQValueList<Set*>::iterator it = tqFind( d->sets.begin(), d->sets.end(), set);
//	if(it == d->sets.end()) // not in our list
//		d->sets.append(set);
}

const TQValueList<Property*>*
Property::related() const
{
	return d->relatedProperties;
}

void
Property::addRelatedProperty(Property *property)
{
	if(!d->relatedProperties)
		d->relatedProperties = new TQValueList<Property*>();

	TQValueList<Property*>::iterator it = tqFind( d->relatedProperties->begin(), d->relatedProperties->end(), property);
	if(it == d->relatedProperties->end()) // not in our list
		d->relatedProperties->append(property);
}

CustomProperty*
Property::customProperty() const
{
	return d->custom;
}

void
Property::setCustomProperty(CustomProperty *prop)
{
	d->custom = prop;
}

int Property::sortingKey() const
{
	return d->sortingKey;
}

void Property::setSortingKey(int key)
{
	d->sortingKey = key;
}

void Property::emitPropertyChanged()
{
	if (d->sets) {
		for (TQPtrDictIterator< TQGuardedPtr<Set> > it(*d->sets); it.current(); ++it) {
			if (it.current()) {//may be destroyed in the meantime
				emit (*it.current())->propertyChangedInternal(**it.current(), *this);
				emit (*it.current())->propertyChanged(**it.current(), *this);
			}
		}
	}
	else if (d->set) {
		//if the slot connect with that signal may call set->clear() - that's
		//the case e.g. at kexi/plugins/{macros|scripting}/* -  this Property
		//may got destroyed ( see Set::removeProperty(Property*) ) while we are
		//still on it. So, if we try to access ourself/this once the signal
		//got emitted we may end in a very hard to reproduce crash. So, the
		//emit should happen as last step in this method!
		emit d->set->propertyChangedInternal(*d->set, *this);
		emit d->set->propertyChanged(*d->set, *this);
	}
}

/////////////////////////////////////////////////////////////////

void
Property::debug()
{
	TQString dbg = "Property( name='" + TQString(d->name) + "' desc='" + d->description
		+ "' val=" + (value().isValid() ? value().toString() : "<INVALID>");
	if (!d->oldValue.isValid())
		dbg += (", oldVal='" + d->oldValue.toString() + '\'');
	dbg += (TQString(d->changed ? " " : " un") + "changed");
	dbg += (d->visible ? " visible" : " hidden");
	dbg+=" )";

	kopropertydbg << dbg << endl;
}
