/***************************************************************************
 *   Copyright (C) 2004-2005 by David Saxton                               *
 *   david@bluehaze.org                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/

#include "itemdocument.h"
#include "itemdocumentdata.h"
#include "core/ktlconfig.h"

#include <cmath>
#include <kdebug.h>
#include <kdialogbase.h>
#include <ktextedit.h>
#include <qbitarray.h>
#include <qlayout.h>
#include <qtimer.h>

const int minPrefixExp = -24;
const int maxPrefixExp = 24;
const int numPrefix = int((maxPrefixExp-minPrefixExp)/3)+1;
const QString SIprefix[] = {"y","z","a","f","p","n",QChar(0xB5),"m","","k","M","G","T","P","E","Z","Y"};


Item::Item( ItemDocument *itemDocument, bool newItem, const QString &id )
	: QObject(), QCanvasPolygon( itemDocument->canvas() )
{
	m_bDynamicContent = false;
	m_bIsRaised = false;
	m_bDoneCreation = false;
	p_parentItem = 0l;
	b_deleted = false;
	p_itemDocument = itemDocument;
	m_baseZ = -1;
	
	if ( QFontInfo(m_font).pixelSize() > 11 ) // It has to be > 11, not > 12, as (I think) pixelSize() rounds off the actual size
		m_font.setPixelSize(12);
	
	if (newItem)
		m_id = p_itemDocument->generateUID(id);
	
	else
	{
		m_id = id;
		p_itemDocument->registerUID(id);
	}
}


Item::~Item()
{
	p_itemDocument->requestEvent( ItemDocument::ItemDocumentEvent::ResizeCanvasToItems );
	p_itemDocument->unregisterUID( id() );
	
	QCanvasPolygon::hide();
	
	const VariantDataMap::iterator variantDataEnd = m_variantData.end();
	for ( VariantDataMap::iterator it = m_variantData.begin(); it != variantDataEnd; ++it )
		delete it.data();
	m_variantData.clear();
}


void Item::removeItem()
{
	if (b_deleted)
		return;
	b_deleted = true;
	
	hide();
	setCanvas(0l);
	emit removed(this);
	p_itemDocument->appendDeleteList(this);
}


void Item::moveBy( double dx, double dy )
{
	QCanvasPolygon::moveBy(dx,dy);
	emit movedBy( dx, dy );
}


void Item::setChanged()
{
	if (b_deleted)
		return;
	
	if (canvas())
		canvas()->setChanged(boundingRect());
}


void Item::setItemPoints( const QPointArray & pa, bool setSizeFromPoints )
{
	m_itemPoints = pa;
	if (setSizeFromPoints)
		setSize( m_itemPoints.boundingRect() );
	itemPointsChanged();
}


void Item::itemPointsChanged()
{
	setPoints(m_itemPoints);
}


void Item::setSize( QRect sizeRect, bool forceItemPoints )
{
	if ( m_sizeRect == sizeRect && !forceItemPoints )
		return;
	
	if ( !preResize(sizeRect) )
		return;
	
	canvas()->setChanged(areaPoints().boundingRect());
	m_sizeRect = sizeRect;
	if ( m_itemPoints.isEmpty() || forceItemPoints )
	{
		setItemPoints( QPointArray( m_sizeRect ), false );
	}
	canvas()->setChanged(areaPoints().boundingRect());
	postResize();
	emit resized();
}


ItemData Item::itemData() const
{
	ItemData itemData;
	
	itemData.type = m_type;
	itemData.x = x();
	itemData.y = y();
	
	if ( !parentItem() )
		itemData.z = m_baseZ;
	
	itemData.size = m_sizeRect;
	itemData.setSize = canResize();
	
	if (p_parentItem)
		itemData.parentId = p_parentItem->id();
	
	const VariantDataMap::const_iterator end = m_variantData.end();
	for ( VariantDataMap::const_iterator it = m_variantData.begin(); it != end; ++it )
	{
		switch( it.data()->type() )
		{
			case Variant::Type::String:
			case Variant::Type::FileName:
			case Variant::Type::Port:
			case Variant::Type::Pin:
			case Variant::Type::VarName:
			case Variant::Type::Combo:
			case Variant::Type::Select:
			case Variant::Type::Multiline:
			case Variant::Type::SevenSegment:
			case Variant::Type::KeyPad:
			{
				itemData.dataString[it.key()] = it.data()->value().toString();
				break;
			}
			case Variant::Type::Int:
			case Variant::Type::Double:
			{
				itemData.dataNumber[it.key()] = it.data()->value().toDouble();
				break;
			}
			case Variant::Type::Color:
			{
				itemData.dataColor[it.key()] = it.data()->value().toColor();
				break;
			}
			case Variant::Type::Bool:
			{
				itemData.dataBool[it.key()] = it.data()->value().toBool();
				break;
			}
			case Variant::Type::Raw:
			{
				itemData.dataRaw[it.key()] = it.data()->value().toBitArray();
				break;
			}
			case Variant::Type::PenStyle:
			case Variant::Type::PenCapStyle:
			{
				// These types are only created from DrawPart, and that class
				// deals with these, so we can ignore them
				break;
			}
			case Variant::Type::None:
			{
				// ? Maybe obsoleted data...
				break;
			}
		}
	}
	
	return itemData;
}


void Item::restoreFromItemData( const ItemData &itemData )
{
	move( itemData.x, itemData.y );
	if ( canResize() )
		setSize( itemData.size );
	
	Item *parentItem = p_itemDocument->itemWithID( itemData.parentId );
	if (parentItem)
		setParentItem(parentItem);
	else
		m_baseZ = itemData.z;
	
	//BEGIN Restore data
	const QStringMap::const_iterator stringEnd = itemData.dataString.end();
	for ( QStringMap::const_iterator it = itemData.dataString.begin(); it != stringEnd; ++it )
	{
		if ( hasProperty(it.key()) )
			property( it.key() )->setValue( it.data() );
	}
	
	const DoubleMap::const_iterator numberEnd = itemData.dataNumber.end();
	for ( DoubleMap::const_iterator it = itemData.dataNumber.begin(); it != numberEnd; ++it )
	{
		if ( hasProperty(it.key()) )
			property( it.key() )->setValue( it.data() );
	}
	
	const QColorMap::const_iterator colorEnd = itemData.dataColor.end();
	for ( QColorMap::const_iterator it = itemData.dataColor.begin(); it != colorEnd; ++it )
	{
		if ( hasProperty(it.key()) )
			property( it.key() )->setValue( it.data() );
	}
	
	const BoolMap::const_iterator boolEnd = itemData.dataBool.end();
	for ( BoolMap::const_iterator it = itemData.dataBool.begin(); it != boolEnd; ++it )
	{
		if ( hasProperty(it.key()) )
			property( it.key() )->setValue( QVariant( it.data(), 0 ) );
	}
	
	const QBitArrayMap::const_iterator rawEnd = itemData.dataRaw.end();
	for ( QBitArrayMap::const_iterator it = itemData.dataRaw.begin(); it != rawEnd; ++it )
	{
		if ( hasProperty(it.key()) )
			property( it.key() )->setValue( it.data() );
	}
	//END Restore Data
}


bool Item::mousePressEvent( const EventInfo &eventInfo )
{
	Q_UNUSED(eventInfo);
	return false;
}
bool Item::mouseReleaseEvent( const EventInfo &eventInfo )
{
	Q_UNUSED(eventInfo);
	return false;
}
bool Item::mouseMoveEvent( const EventInfo &eventInfo )
{
	Q_UNUSED(eventInfo);
	return false;
}
bool Item::wheelEvent( const EventInfo &eventInfo )
{
	Q_UNUSED(eventInfo);
	return false;
}
void Item::enterEvent()
{
}
void Item::leaveEvent()
{
}

bool Item::mouseDoubleClickEvent( const EventInfo &eventInfo )
{
	Q_UNUSED(eventInfo);
	
	typedef QValueList<Variant*> VarPtrLst;
	VarPtrLst list;
	const VariantDataMap::iterator variantDataEnd = m_variantData.end();
	for ( VariantDataMap::iterator it = m_variantData.begin(); it != variantDataEnd; ++it )
	{
		if ( it.data()->type() == Variant::Type::Multiline ) {
			list.append(it.data());
		}
	}
	if ( list.count() > 1 )
	{
		kdWarning() << "Item::mouseDoubleClickEvent: Can't handle more than one multiline data"<<endl;
		return false;
	}
	else if ( list.isEmpty() )
		return false;
	
	Variant *v = *(list.at(0));
	
	/// @todo Replace this with KInputDialog::getMultiLineText for KDE 3.3
// 	bool ok;
// 	QString text = KInputDialog::getMultiLineText( v->caption(), "", v->getValue(), ok );

	KDialogBase *dlg = new KDialogBase( 0l, "", true, v->editorCaption(), KDialogBase::Ok|KDialogBase::Cancel|KDialogBase::User1, KDialogBase::Ok, false, KStdGuiItem::clear() );
	QFrame *frame = dlg->makeMainWidget();
	QVBoxLayout *layout = new QVBoxLayout( frame, 0, dlg->spacingHint() );
	KTextEdit *textEdit = new KTextEdit( frame );
	textEdit->setTextFormat( PlainText );
	textEdit->setText( v->value().toString() );
	layout->addWidget( textEdit, 10 );
	textEdit->setFocus();
	connect( dlg, SIGNAL( user1Clicked() ), textEdit, SLOT( clear() ) );
	dlg->setMinimumWidth( 600 );
	if ( dlg->exec() == KDialogBase::Accepted )
	{
		v->setValue( textEdit->text() );
		dataChanged();
		p_itemDocument->setModified(true);
	}
	delete dlg;
	
	return true;
}


void Item::setSelected( bool yes )
{
	if ( isSelected() == yes ) {
		return;
	}
	QCanvasPolygon::setSelected(yes);
	yes ? (emit selected(this)) : (emit unselected(this));
}


void Item::setParentItem( Item *newParentItem )
{
// 	kdDebug() << k_funcinfo << "this = "<<this<<" newParentItem = "<<newParentItem<<endl;
	if ( newParentItem == p_parentItem )
		return;
	
	Item *oldParentItem = p_parentItem;
	
	if (oldParentItem)
	{
		disconnect( oldParentItem, SIGNAL(removed(Item*)), this, SLOT(removeItem()) );
		oldParentItem->removeChild(this);
	}
	
	if (newParentItem)
	{
		if ( newParentItem->contains(this) );
// 			kdError() << k_funcinfo << "Already a child of " << newParentItem << endl;
		else
		{
			connect( newParentItem, SIGNAL(removed(Item*)), this, SLOT(removeItem()) );
			newParentItem->addChild(this);
		}
	}
	
	p_parentItem = newParentItem;
	(void)level();
	reparented( oldParentItem, newParentItem );
	p_itemDocument->slotUpdateZOrdering();
}


int Item::level() const
{
	return p_parentItem ? p_parentItem->level()+1 : 0;
}


ItemList Item::children( bool includeGrandChildren ) const
{
	if (!includeGrandChildren)
		return m_children;
	
	ItemList children = m_children;
	ItemList::const_iterator end = m_children.end();
	for ( ItemList::const_iterator it = m_children.begin(); it != end; ++it )
	{
		if (!*it)
			continue;
		
		children += (*it)->children(true);
	}
	
	return children;
}


void Item::addChild( Item *child )
{
	if ( !child )
		return;
	
	if ( child->contains(this) )
	{
// 		kdError() << k_funcinfo << "Attempting to add a child to this item that is already a parent of this item. Incest results in stack overflow." << endl;
		return;
	}
	
	if ( contains( child, true ) )
	{
// 		kdError() << k_funcinfo << "Already have child " << child << endl;
		return;
	}
	
	m_children.append(child);
	connect( child, SIGNAL(removed(Item* )), this, SLOT(removeChild(Item* )) );
	
	child->setParentItem(this);
	childAdded(child);
	p_itemDocument->slotUpdateZOrdering();
}


void Item::removeChild( Item *child )
{
	if ( !child || !m_children.contains(child) )
		return;
	
	m_children.remove(child);
	disconnect( child, SIGNAL(removed(Item* )), this, SLOT(removeChild(Item* )) );
	
	childRemoved(child);
	p_itemDocument->slotUpdateZOrdering();
}


bool Item::contains( Item *item, bool direct ) const
{
	const ItemList::const_iterator end = m_children.end();
	for ( ItemList::const_iterator it = m_children.begin(); it != end; ++it )
	{
		if ( (Item*)*it == item || ( !direct && (*it)->contains( item, false ) ) )
			return true;
	}
	return false;
}


void Item::setRaised( bool isRaised )
{
	m_bIsRaised = isRaised;
	// We'll get called later to update our Z
}


void Item::updateZ( int baseZ )
{
	m_baseZ = baseZ;
	double z = ItemDocument::Z::Item + (ItemDocument::Z::DeltaItem)*baseZ;
	
	if ( isRaised() )
		z += ItemDocument::Z::RaisedItem - ItemDocument::Z::Item;
	
	setZ(z);
	
	const ItemList::const_iterator end = m_children.end();
	for ( ItemList::const_iterator it = m_children.begin(); it != end; ++it )
	{
		if (*it)
			(*it)->updateZ(baseZ+1);
	}
}


int Item::getNumberPre( double num )
{
	return (int)(num/getMultiplier(num));
}

QString Item::getNumberMag( double num )
{
	if ( num == 0. ) return "";
	const double exp_n = std::log10(std::abs(num));
	if ( exp_n < minPrefixExp+3 ) return SIprefix[0];
	else if ( exp_n >= maxPrefixExp ) return SIprefix[numPrefix-1];
	else return SIprefix[(int)std::floor((double)(exp_n/3))-(int)floor(double(minPrefixExp/3))];
}

double Item::getMultiplier( double num )
{
	if ( num == 0. ) return 1.;
	else return std::pow( 10, 3*std::floor(std::log10(std::abs(num))/3) );
}

double Item::getMultiplier( const QString &_mag )
{
	QString mag;
	// Allow the user to enter in "u" instead of mu, as unfortunately many keyboards don't have the mu key
	if ( _mag == "u" )
		mag = QChar(0xB5);
	else
		mag = _mag;
	
	for ( int i=0; i<numPrefix; ++i )
	{
		if ( mag == SIprefix[i] )
		{
			return std::pow( 10., (i*3)+minPrefixExp );
		}
	}
	
	// I think it is safer to return '1' if the unit is unknown
	return 1.;
// 	return pow( 10., maxPrefixExp+3. );
}



//BEGIN Data stuff
double Item::dataDouble( const QString & id ) const
{
	Variant * variant = property(id);
	return variant ? variant->value().toDouble() : 0.0;
}


int Item::dataInt( const QString & id ) const
{
	Variant * variant = property(id);
	return variant ? variant->value().toInt() : 0;
}


bool Item::dataBool( const QString & id ) const
{
	Variant * variant = property(id);
	return variant ? variant->value().toBool() : false;
}


QString Item::dataString( const QString & id ) const
{
	Variant * variant = property(id);
	return variant ? variant->value().toString() : QString::null;
}


QColor Item::dataColor( const QString & id ) const
{
	Variant * variant = property(id);
	return variant ? variant->value().toColor() : Qt::black;
}


Variant * Item::createProperty( const QString & id, Variant::Type::Value type )
{
	if ( !m_variantData.contains(id) )
	{
		m_variantData[id] = new Variant(type);
		if (m_bDoneCreation)
			connect( m_variantData[id], SIGNAL(valueChanged(QVariant,QVariant)), this, SLOT(dataChanged()) );
	}
	
	return m_variantData[id];
}


Variant * Item::property( const QString & id ) const
{
	if ( m_variantData.contains(id) )
		return m_variantData[id];
	
	kdError() << k_funcinfo << " No such property with id " << id << endl;
	return 0l;
}


bool Item::hasProperty( const QString & id ) const
{
	return m_variantData.contains(id);
}


void Item::finishedCreation( )
{
	m_bDoneCreation = true;
	const VariantDataMap::iterator end = m_variantData.end();
	for ( VariantDataMap::iterator it = m_variantData.begin(); it != end; ++it )
		connect( it.data(), SIGNAL(valueChanged(QVariant,QVariant)), this, SLOT(dataChanged()) );
	dataChanged();
}
//END Data stuff

#include "item.moc"
