#include "optionsdlg.h"
#include "optionstab.h"
#include "common.h"
#include "psicon.h"
#include "fancylabel.h"
#include "iconwidget.h"

#include <qlayout.h>
#include <qlabel.h>
#include <qlistview.h>
#include <qwidgetstack.h>
#include <qheader.h>
#include <qobjectlist.h>
#include <qpen.h>
#include <qpainter.h>

// tabs
#include "opt_application.h"
#include "opt_appearance.h"
#include "opt_chat.h"
#include "opt_events.h"
#include "opt_status.h"
#include "opt_iconset.h"
#include "opt_groupchat.h"
#include "opt_sound.h"
#include "opt_advanced.h"

#include "opt_lookfeel.h"

//----------------------------------------------------------------------------
// FancyItem
//----------------------------------------------------------------------------

class FancyItem : public QListViewItem
{
public:
	FancyItem(QListView *, QListViewItem *after);

	void setup();
	int width(const QFontMetrics &, const QListView *lv, int c) const;
	void paintFocus(QPainter *, const QColorGroup &, const QRect &);
	void paintCell(QPainter *p, const QColorGroup &, int c, int width, int align);
};

FancyItem::FancyItem(QListView *lv, QListViewItem *after)
: QListViewItem(lv, after)
{
}

void FancyItem::setup()
{
	QListView *lv = listView();
	int ph = 0;
	for(int i = 0; i < lv->columns(); ++i) {
		if(pixmap(i))
			ph = QMAX(ph, pixmap(i)->height());
	}
	int y = QMAX(ph, lv->fontMetrics().height());
	y += 8;
	setHeight(y);
}

int FancyItem::width(const QFontMetrics &fm, const QListView *, int c) const
{
	int x = 0;
	const QPixmap *pix = pixmap(c);
	if(pix)
		x += pix->width();
	else
		x += 16;
	x += 8;
	x += fm.width(text(c));
	x += 8;
	return x;
}

void FancyItem::paintFocus(QPainter *, const QColorGroup &, const QRect &)
{
	// re-implimented to do nothing.  selection is enough of a focus
}

void FancyItem::paintCell(QPainter *p, const QColorGroup &cg, int c, int w, int)
{
	int h = height();
	QFontMetrics fm(p->font());
	if(isSelected())
		p->fillRect(0, 0, w, h-1, cg.highlight());
	else
		p->fillRect(0, 0, w, h, cg.base());

	int x = 0;
	const QPixmap *pix = pixmap(c);
	if(pix) {
		p->drawPixmap(4, (h - pix->height()) / 2, *pix);
		x += pix->width();
	}
	else
		x += 16;
	x += 8;
	int y = ((h - fm.height()) / 2) + fm.ascent();
	p->setPen(isSelected() ? cg.highlightedText() : cg.text());
	p->drawText(x, y, text(c));

	p->setPen(QPen(QColor(0xE0, 0xE0, 0xE0), 0, DotLine));
	p->drawLine(0, h-1, w-1, h-1);
}

//----------------------------------------------------------------------------
// OptionsTabBase
//----------------------------------------------------------------------------

//class OptionsTabBase : public OptionsTab
//{
//	Q_OBJECT
//public:
//	OptionsTabBase(QObject *parent, QCString id, QCString parentId, QString iconName, QString name, QString desc)
//		: OptionsTab(parent, id, parentId, name, desc, iconName)
//	{
//		w = new QWidget();
//		QGridLayout *layout = new QGridLayout(w, 0, 2, 0, 5);
//		layout->setAutoAdd(true);
//	}
//	~OptionsTabBase()
//	{
//		w->deleteLater();
//	}
//
//	QWidget *widget() { return w; }
//
//public slots:
//	void tabAdded(OptionsTab *tab);
//
//private:
//	QWidget *w;
//};
//
//void OptionsTabBase::tabAdded(OptionsTab *tab)
//{
//	//qWarning("OptionsTabBase::tabAdded(): id = %s, tab_id = %s", (const char *)id(), (const char *)tab->id());
//	QLabel *name = new QLabel(w);
//	name->setText("<b>" + tab->name() + "</b>");
//	name->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
//
//	IconLabel *desc = new IconLabel(w);
//	desc->setText(tab->desc());
//	desc->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
//}

//----------------------------------------------------------------------------
// OptionsDlg::Private
//----------------------------------------------------------------------------

class OptionsDlg::Private : public QObject
{
	Q_OBJECT
public:
	Private(OptionsDlg *dlg, PsiCon *_psi, const Options &_opt);

public slots:
	void doApply();

private slots:
	void itemSelected(QListViewItem *);
	void openTab(QString id);
	void dataChanged();
	void noDirtySlot(bool);
	void createTabs();
	void createChangedMap();

	//void addWidgetChangedSignal(QString widgetName, QCString signal);
	void connectDataChanged(QWidget *);

public:
	OptionsDlg *dlg;
	PsiCon *psi;
	Options opt;
	bool dirty, noDirty;
	QDict<QWidget> id2widget;
	QPtrList<OptionsTab> tabs;

	QMap<QString, QCString> changedMap;
};

OptionsDlg::Private::Private(OptionsDlg *d, PsiCon *_psi, const Options &_opt)
{
	dlg = d;
	psi = _psi;
	opt = _opt; // option
	noDirty = false;
	dirty = false;

	dlg->lb_pageTitle->setScaledContents(32, 32);

	dlg->lv_tabs->setSorting( -1 );
	dlg->lv_tabs->header()->hide();
	connect(dlg->lv_tabs, SIGNAL(selectionChanged(QListViewItem *)), SLOT(itemSelected(QListViewItem *)));

	createTabs();
	createChangedMap();

	// fill the QListView
	QPtrListIterator<OptionsTab> it ( tabs );
	OptionsTab *opttab;
	for ( ; it.current(); ++it) {
		opttab = it.current();
		//qWarning("Adding tab %s...", (const char *)opttab->id());
		opttab->setData(psi, dlg);
		connect(opttab, SIGNAL(dataChanged()), SLOT(dataChanged()));
		//connect(opttab, SIGNAL(addWidgetChangedSignal(QString, QCString)), SLOT(addWidgetChangedSignal(QString, QCString)));
		connect(opttab, SIGNAL(noDirty(bool)), SLOT(noDirtySlot(bool)));
		connect(opttab, SIGNAL(connectDataChanged(QWidget *)), SLOT(connectDataChanged(QWidget *)));

		// search for parent
		QListViewItem *parent = 0, *prev = 0;
		QString parentId = opttab->parentId();
		if ( !parentId.isEmpty() ) {
			QListViewItemIterator it2( dlg->lv_tabs );
			for ( ; it2.current(); ++it2) {
				//qWarning("Searching the QListView %s...", it2.current()->text(1).latin1());
				if ( it2.current()->text(1) == parentId ) {
					//qWarning("...done");
					parent = it2.current();

					// notify the parent about the child
					QPtrListIterator<OptionsTab> it3 ( tabs );
					OptionsTab *opttab2;
					for ( ; it3.current(); ++it3) {
						opttab2 = it3.current();
						//qWarning("Searching tabs %s...", (const char *)opttab2->id());
						if ( opttab2->id() == opttab->parentId() ) {
							//qWarning("...done");
							opttab2->tabAdded( opttab );
							break;
						}
					}

					parent->setOpen( true );
					break;
				}
			}
		}
		//qWarning("****************");

		// search for previous item
		QListViewItem *top;
		if ( parent )
			top = parent->firstChild();
		else
			top = dlg->lv_tabs->firstChild();
		prev = top;
		while ( prev ) {
			if ( !prev->nextSibling() )
				break;
			prev = prev->nextSibling();
		}

		if ( opttab->id().isEmpty() )
			continue;

		// create tab
		QListViewItem *item;
		//if ( parent )
		//	item = new FancyItem(parent, prev);
		//else
			item = new FancyItem(dlg->lv_tabs, prev);

		item->setText(0, opttab->tabName());
		if ( opttab->tabIcon() )
			item->setPixmap(0, opttab->tabIcon()->impix().pixmap());
		item->setText(1, opttab->id());

		// create separator
		//if ( !parent ) {
		//	new ListItemSeparator(dlg->lv_tabs, item);
		//}
	}

	// fix the width of the listview based on the largest item
	int largestWidth = 0;
	QFontMetrics fm(dlg->lv_tabs->font());
	for(QListViewItem *i = dlg->lv_tabs->firstChild(); i; i = i->nextSibling())
		largestWidth = QMAX(largestWidth, i->width(fm, dlg->lv_tabs, 0));
	dlg->lv_tabs->setFixedWidth(largestWidth + 32);

	openTab( "application" );

	dirty = false;
	dlg->pb_apply->setEnabled(false);
}

void OptionsDlg::Private::createTabs()
{
	// tabs - base
	/*tabs.append( new OptionsTabGeneral(this) );
	//tabs.append( new OptionsTabBase(this, "general",  "", "psi/psi16",	tr("General"),		tr("General preferences list")) );
	tabs.append( new OptionsTabEvents(this) );
	//tabs.append( new OptionsTabBase(this, "events",   "", "psi/system",	tr("Events"),		tr("Change the events behaviour")) );
	tabs.append( new OptionsTabPresence(this) );
	//tabs.append( new OptionsTabBase(this, "presence", "", "status/online",	tr("Presence"),		tr("Presence configuration")) );
	tabs.append( new OptionsTabLookFeel(this) );
	tabs.append( new OptionsTabIconset(this) );
	//tabs.append( new OptionsTabBase(this, "lookfeel", "", "psi/smile",	tr("Look and Feel"),	tr("Change the Psi's Look and Feel")) );
	tabs.append( new OptionsTabSound(this) );
	//tabs.append( new OptionsTabBase(this, "sound",    "", "psi/playSounds",	tr("Sound"),		tr("Configure how Psi sounds")) );
	*/

	tabs.append( new OptionsTabApplication(this) );
	tabs.append( new OptionsTabChat(this) );
	tabs.append( new OptionsTabEvents(this) );
	tabs.append( new OptionsTabStatus(this) );
	tabs.append( new OptionsTabAppearance(this) );
	//tabs.append( new OptionsTabIconsetSystem(this) );
	//tabs.append( new OptionsTabIconsetRoster(this) );
	//tabs.append( new OptionsTabIconsetEmoticons(this) );
	tabs.append( new OptionsTabGroupchat(this) );
	tabs.append( new OptionsTabSound(this) );
	tabs.append( new OptionsTabAdvanced(this) );

	tabs.append( new OptionsTabLookFeelToolbars(this) ); // it needs to be there, otherwise toolbar options would not be saved correctly in all cases

	// tabs - general
	/*tabs.append( new OptionsTabGeneralRoster(this) );
	tabs.append( new OptionsTabGeneralDocking(this) );
	tabs.append( new OptionsTabGeneralNotifications(this) );
	tabs.append( new OptionsTabGeneralGroupchat(this) );
	tabs.append( new OptionsTabGeneralMisc(this) );*/

	// tabs - events
	/*tabs.append( new OptionsTabEventsReceive(this) );
	tabs.append( new OptionsTabEventsMisc(this) );*/

	// tabs - presence
	/*tabs.append( new OptionsTabPresenceAuto(this) );
	tabs.append( new OptionsTabPresencePresets(this) );
	tabs.append( new OptionsTabPresenceMisc(this) );*/

	// tabs - look and feel
	/*tabs.append( new OptionsTabLookFeelColors(this) );
	tabs.append( new OptionsTabLookFeelFonts(this) );
	tabs.append( new OptionsTabIconsetSystem(this) );
	tabs.append( new OptionsTabIconsetEmoticons(this) );
	tabs.append( new OptionsTabIconsetRoster(this) );
	tabs.append( new OptionsTabLookFeelToolbars(this) );
	tabs.append( new OptionsTabLookFeelMisc(this) );*/

	// tabs - sound
	/*tabs.append( new OptionsTabSoundPrefs(this) );
	tabs.append( new OptionsTabSoundEvents(this) );*/
}

void OptionsDlg::Private::createChangedMap()
{
	// NOTE about commented out signals:
	//   Do NOT call addWidgetChangedSignal() for them.
	//   Instead, connect the widget's signal to your tab own dataChaged() signal
	changedMap.insert("QButton", SIGNAL(stateChanged(int)));
	changedMap.insert("QCheckBox", SIGNAL(stateChanged(int)));
	changedMap.insert("QPushButton", SIGNAL(stateChanged(int)));
	changedMap.insert("QRadioButton", SIGNAL(stateChanged(int)));
	changedMap.insert("QComboBox", SIGNAL(activated (int)));
	//changedMap.insert("QComboBox", SIGNAL(textChanged(const QString &)));
	changedMap.insert("QDateEdit", SIGNAL(valueChanged(const QDate &)));
	changedMap.insert("QDateTimeEdit", SIGNAL(valueChanged(const QDateTime &)));
	changedMap.insert("QDial", SIGNAL(valueChanged (int)));
	changedMap.insert("QLineEdit", SIGNAL(textChanged(const QString &)));
	changedMap.insert("QSlider", SIGNAL(valueChanged(int)));
	changedMap.insert("QSpinBox", SIGNAL(valueChanged(int)));
	changedMap.insert("QTimeEdit", SIGNAL(valueChanged(const QTime &)));
	changedMap.insert("QTextEdit", SIGNAL(textChanged()));
	changedMap.insert("QTextBrowser", SIGNAL(sourceChanged(const QString &)));
	changedMap.insert("QMultiLineEdit", SIGNAL(textChanged()));
	//changedMap.insert("QListBox", SIGNAL(selectionChanged()));
	//changedMap.insert("QTabWidget", SIGNAL(currentChanged(QWidget *)));
}

//void OptionsDlg::Private::addWidgetChangedSignal(QString widgetName, QCString signal)
//{
//	changedMap.insert(widgetName, signal);
//}

void OptionsDlg::Private::openTab(QString id)
{
	if ( id.isEmpty() )
		return;

	QWidget *tab = id2widget[id];
	if ( !tab ) {
		bool found = false;
		QPtrListIterator<OptionsTab> it ( tabs );
		OptionsTab *opttab;
		for ( ; it.current(); ++it) {
			opttab = it.current();

			if ( opttab->id() == id.latin1() ) {
				tab = opttab->widget(); // create the widget
				if ( !tab )
					continue;

				// TODO: how about QScrollView for large tabs?
				// idea: maybe do it only for those, whose sizeHint is bigger than ws_tabs'
				QWidget *w = new QWidget(dlg->ws_tabs, "QWidgetStack/tab");
				QVBoxLayout *vbox = new QVBoxLayout(w);

				/*FancyLabel *toplbl = new FancyLabel(w, "QWidgetStack/tab/FancyLabel");
				toplbl->setText( opttab->name() );
				toplbl->setHelp( opttab->desc() );
				toplbl->setIcon( opttab->icon() );
				vbox->addWidget( toplbl );
				vbox->addSpacing( 5 );*/

				tab->reparent(w, 0, QPoint(0, 0));
				vbox->addWidget(tab);
				if ( !opttab->stretchable() )
					vbox->addStretch();

				dlg->ws_tabs->addWidget(w);
				id2widget.insert( id, w );
				connectDataChanged( tab ); // no need to connect to dataChanged() slot by hands anymore

				bool d = dirty;

				opttab->restoreOptions( &opt ); // initialize widgets' values

				dirty = d;
				dlg->pb_apply->setEnabled( dirty );

				tab = w;
				found = true;
				break;
			}
		}

		if ( !found ) {
			qWarning("OptionsDlg::Private::itemSelected(): could not create widget for id '%s'", id.latin1());
			return;
		}
	}

	{
		QPtrListIterator<OptionsTab> it ( tabs );
		OptionsTab *opttab;
		for ( ; it.current(); ++it) {
			opttab = it.current();

			if ( opttab->id() == id.latin1() ) {
				dlg->lb_pageTitle->setText( opttab->name() );
				dlg->lb_pageTitle->setHelp( opttab->desc() );
				dlg->lb_pageTitle->setIcon( opttab->icon() );

				break;
			}
		}
	}

	dlg->ws_tabs->raiseWidget( tab );

	// and select item in lv_tabs...
	QListViewItemIterator it( dlg->lv_tabs );
	while ( it.current() ) {
		it.current()->setSelected( it.current()->text(1) == id );
		++it;
	}
}

void OptionsDlg::Private::connectDataChanged(QWidget *widget)
{
	QObjectList *l = widget->queryList( "QWidget", 0, false, true ); // search for all QWidget children of widget
	QObjectListIterator it( *l );

	for ( ; it.current(); ++it) {
		QWidget *w = (QWidget *)it.current();

		QMap<QString, QCString>::Iterator it2 = changedMap.find( w->className() );
		if ( it2 != changedMap.end() ) {
			disconnect(w, changedMap[w->className()], this, SLOT(dataChanged()));
			connect(w, changedMap[w->className()], SLOT(dataChanged()));
		}
	}

	delete l;
}

void OptionsDlg::Private::itemSelected(QListViewItem *item)
{
	if ( !item )
		return;

	openTab( item->text(1) );
}

void OptionsDlg::Private::dataChanged()
{
	if ( dirty )
		return;

	if ( !noDirty ) {
		dirty = true;
		dlg->pb_apply->setEnabled(true);
	}
}

void OptionsDlg::Private::noDirtySlot(bool d)
{
	noDirty = d;
}

void OptionsDlg::Private::doApply()
{
	if ( !dirty )
		return;

	QPtrListIterator<OptionsTab> it ( tabs );
	OptionsTab *opttab;
	for ( ; it.current(); ++it) {
		opttab = it.current();

		opttab->applyOptions( &opt );
	}

	emit dlg->applyOptions( opt );

	dirty = false;
	dlg->pb_apply->setEnabled(false);
}

//----------------------------------------------------------------------------
// OptionsDlg
//----------------------------------------------------------------------------

OptionsDlg::OptionsDlg(PsiCon *psi, const Options &opt, QWidget *parent, const char *name)
: OptionsUI(parent, name, false, WDestructiveClose)
{
	d = new Private(this, psi, opt);
	d->psi->dialogRegister(this);

	setCaption(CAP(caption()));
	resize(640, 480);
}

OptionsDlg::~OptionsDlg()
{
	d->psi->dialogUnregister(this);
	delete d;
}

void OptionsDlg::doOk()
{
	doApply();
	accept();
}

void OptionsDlg::doApply()
{
	d->doApply();
}

#include "optionsdlg.moc"
