//
// C++ Implementation: apollonchattab
//
// Description: 
//
//
// Author: Martin Koebele <martin@mkoebele.de>, (C) 2004
//
// Copyright: See COPYING file that comes with this distribution
//
//
#include <stdlib.h>


#include "apollonchattab.h"
#include "libkirc/kirc.h"
#include "chatline.h"

#include <qtextbrowser.h> 
#include <qpassivepopup.h>
#include <kpassivepopup.h>
#include <kiconloader.h>

//#include <qlineedit.h>
#include <qpushbutton.h>
#include <qlistview.h>
#include <qlabel.h>
#include <qsplitter.h>
#include <kinputdialog.h>


#include <klineedit.h>
#include <kdebug.h>
#include <klocale.h>

bool IrcToHtml::findTag(const QString & name)
{
	for ( QStringList::Iterator it = tags.begin(); it != tags.end(); ++it )
	{
		int pos = (*it).find(' ') ;
		if (pos == -1)
			pos = (*it).length();
		QString tagName = (*it).left(pos);
		if (tagName == name)
			return true;
	}
	return false;
}

QString IrcToHtml::closeTag(const QString & name)
{
	QString ret;
	QString openers;
	
	for ( QStringList::Iterator it = tags.fromLast(); it != tags.end() ; --it )
	{
		int pos = (*it).find(' ') ;
		if (pos == -1)
			pos = (*it).length();
		QString tagName = (*it).left(pos);
		ret += "</" + tagName + ">";
		if (tagName != name)
			openers = "<" + (*it) + ">" + openers;
		else
		{
			tags.remove(it);
			break;
		}
		
		if (it == tags.begin())
			break;
	}
	return ret + openers;
}

QString IrcToHtml::ircColorToHex(int color)
{
	int newColor = ((color - 16) % 16) + 16;
	switch (newColor)
	{
	case 1:  return "#000000";
	case 2:  return "#0000CC";
	case 3:  return "#00CC00";
	case 4:  return "#DD0000";
	case 5:  return "#AA0000";
	case 6:  return "#BB00BB";
	case 7:  return "#FFAA00";
	case 8:  return "#EEDD22";
	case 9:  return "#33DE55";
	case 10: return "#00CCCC";
	case 11: return "#33EEFF";
	case 12: return "#0000FF";
	case 13: return "#EE22EE";
	case 14: return "#777777";
	case 15: return "#999999";
	case 16: return "#CECECE";
	default: return "#000000";
	}
}

QString IrcToHtml::parse(const QString & ircText)
{
	tags.clear();
	
	QString line = ircText;
	line.replace('<', "&lt;");
	line.replace('>', "&gt;");
	
	// Simple parser for bold, colour and underline
	QString lineOut;
	QRegExp colorRE("(\\d*)(?:,(\\d*))?");
	for (int i=0 ; i<line.length() ; i++)
	{
		switch (line.at(i).latin1())
		{
		case 2: // Bold
			if (!findTag("b"))
			{
				lineOut += "<b>";
				tags.append("b");
			}
			break;
		case 3: // Color
			if (findTag("font"))
				lineOut += closeTag("font");
			colorRE.search(line.right(line.length() - i - 1));
			tags.append("font color=\"" + ircColorToHex(colorRE.cap(1).toInt()) + "\"");
			lineOut += "<font color=\"" + ircColorToHex(colorRE.cap(1).toInt()) + "\">";
			i += colorRE.cap(0).length();
			break;
		case 4: // Custom command - used for nick highlighting.
			lineOut += "<b>";
			break;
		case 15: // Reset
			for ( QStringList::Iterator it = tags.fromLast(); it != tags.end() ; --it)
			{
				int pos = (*it).find(' ') ;
				if (pos == -1)
					pos = (*it).length();
				QString tagName = (*it).left(pos);
				lineOut += "</" + tagName + ">";
				if (it == tags.begin())
					break;
			}
			tags.clear();
			break;
		case 31: // Underline
			if (!findTag("u"))
			{
				lineOut += "<u>";
				tags.append("u");
			}
			break;
		default:
			lineOut += line.at(i);
		}
	}
	return lineOut;
}




ApollonChatTab::ApollonChatTab(QWidget* parent, const char* name, WFlags fl):
ApollonChatIface(parent, name, fl),
m_connectionState(Disconnected),
m_nickName(getenv("USER")),
m_nickIndex(0),
m_showPopup(1),
m_oldLineText("")		
{
	//connect(m_messageLineEdit, SIGNAL(returnPressed()), this, SLOT(sendMessage()));
	connect(m_nickButton, SIGNAL(clicked()), this, SLOT(clickChangeNick()));	
	//m_nickButton->setMinimumHeight(m_nickButton->height());
	m_nickLabel->setText(i18n("Your nickname is currently:")+" <b>" + m_nickName + "</b>");
	//m_messageLineEdit->setFocus();
	m_topic->setMaximumSize(QSize(32767, m_messageLineEdit->sizeHint().height() + 5));
	
	KIconLoader *loader = KGlobal::iconLoader();
	QMimeSourceFactory::defaultFactory()->setPixmap("ApollonChat",loader->loadIcon("package_toys", KIcon::MainToolbar));
	
	connect(m_connectButton, SIGNAL(clicked()), this, SLOT(slotJoinChannel()));
	connect(m_messageLineEdit, SIGNAL(newMessage()), this, SLOT(sendMessage()));
	connect(m_messageLineEdit, SIGNAL(appendLine(const QString&)), this, SLOT(appendLine(const QString&)));
	m_nickListView->setColumnWidth(0, m_nickListView->width());
	initKIRC();
}

ApollonChatTab::~ApollonChatTab()
{
	kirc->quitIRC("bye");
}


void ApollonChatTab::initKIRC() {
	kirc = new KIRC("irc.freenode.net", 6667);
	
	connect(kirc, SIGNAL(incomingMotd(const QStringList &)), this, SLOT(slotConnected()));
	//connect(kirc, SIGNAL(incomingPrivMessage(const QString&, const QString&, const QString& )), this, SLOT(slotIncomingMessage(const QString&, const QString&, const QString& )));
	connect(kirc, SIGNAL(incomingMessage(const QString&, const QString&, const QString& )), this, SLOT(slotIncomingMessage(const QString&, const QString&, const QString& )));
	connect(kirc, SIGNAL(incomingNamesList(const QString&, const QStringList&)), this, SLOT(slotNickList(const QString&, const QStringList&)));
	connect(kirc, SIGNAL(incomingPartedChannel(const QString &,const QString &, const QString &)), this, SLOT(slotPartedChannel(const QString &,const QString &, const QString &)));
	//connect(kirc, SIGNAL(incomingJoinedChannel(const QString &,const QString &)), this, SLOT(slotJoinedChannel(const QString &,const QString &)));
	connect(kirc, SIGNAL(userJoinedChannel(const QString &,const QString &)), this, SLOT(slotJoinedChannel(const QString &,const QString &)));
	connect(kirc, SIGNAL(incomingNickInUse(const QString&)), this, SLOT(slotGenerateNewNick(const QString&)));
	connect(kirc, SIGNAL(incomingFailedNickOnLogin(const QString &)), this, SLOT(slotGenerateNewNick(const QString&)));
	connect(kirc, SIGNAL(incomingNickChange(const QString &, const QString &)), this, SLOT(slotNickChange(const QString &, const QString &)));
	connect(kirc, SIGNAL(incomingQuitIRC(const QString &, const QString &)), this, SLOT(slotQuitChannel(const QString &, const QString &))); 
	connect(kirc, SIGNAL(incomingTopicChange(const QString &, const QString &, const QString &)), this, SLOT(slotTopicChange(const QString &, const QString &, const QString &))); 
	connect(kirc, SIGNAL(incomingExistingTopic(const QString &, const QString &)), this, SLOT(slotExistingTopic(const QString &, const QString &)));
	
	connect(kirc, SIGNAL(successfullyChangedNick(const QString &, const QString &)), this, SLOT(slotNickChanged(const QString &, const QString &)));
}

void ApollonChatTab::slotConnected() {
	kdDebug() << k_funcinfo  << "join channel #apollon" << endl;
	kirc->joinChannel("#apollon-user", "");
	m_connectButton->setText(i18n("Disconnect"));
	m_connectButton->setEnabled(true);
	m_connectionState = Connected;
	m_connectLabel->setText(i18n("Click this button to leave the chat room"));
	m_splitter->setEnabled(true);
	m_messageLineEdit->setEnabled(true);
	m_nickLabel->setEnabled(true);
	m_nickButton->setEnabled(true);
	m_topic->setEnabled(true);
	m_messageLineEdit->setFocus();
}

void ApollonChatTab::slotJoinChannel() {
	if(m_connectionState == Disconnected) {
		kirc->connectToServer(m_nickName, "irc.freenode.net", 6667);
		m_connectLabel->setText(i18n("Connecting..."));
		m_connectButton->setEnabled(false);
		m_connectionState = Connecting;
	}
	else if (m_connectionState == Connected) {
		m_connectionState = Disconnected;
		kirc->quitIRC("Bye");
		m_nickListView->clear();
		m_connectButton->setText(i18n("Connect"));
		m_connectLabel->setText(i18n("Click this button to start chatting with other Apollon users"));
		m_splitter->setEnabled(false);
		m_messageLineEdit->setEnabled(false);
		m_nickLabel->setEnabled(false);
		m_nickButton->setEnabled(false);
		m_topic->setEnabled(false);
	}
}

static char rcolors[] = { 3, 4, 6, 8, 9, 10, 11, 12, 13 };

int ApollonChatTab::colorOf(const QString & name)
{
	int i = 0, sum = 0;

	while (name[i])
		sum += name[i++];
	sum %= sizeof (rcolors) / sizeof (char);
	return rcolors[sum];
}

void ApollonChatTab::appendLine(const QString & text)
{
	IrcToHtml parser;
	QString tmp = parser.parse(text);
	m_chatWindow->append ( tmp );
}

void ApollonChatTab::slotIncomingMessage(const QString& originating, const QString& target, const QString& message) {

	kdDebug() << k_funcinfo << "incomingMessage : originating " << originating << " target " << target  << " message: " << message << endl;
	
	QString nick = "\002" + originating;
	/*if (coloredNicks)*/
	nick = "\003" + QString::number(colorOf(originating)) + "\002" + originating;
	
	QString mess = message;
	if (message.contains(m_nickName, false))
	{
		if (!isActiveWindow())
		{
			IrcToHtml parser;
			QString shortMessage = parser.parse(message);
			if (shortMessage.length() >= 50)
				shortMessage = shortMessage.left(50) + "...";
			QString statustext = "<img source = \"ApollonChat\"><br><strong>" + i18n("Apollon Chat message:") + "</strong> <br>&lt;<b>" + originating + "</b>&gt; " + shortMessage;
			if(m_showPopup==1)
				QPassivePopup::message("<br>" + statustext + "<br>", m_systemTray);
			if(m_showPopup==2)
				KPassivePopup::message(statustext,  m_systemTray);
		}
			
		mess = "\004" + message;
	}
	
	appendLine("\0032<\017" + nick + "\017\0032>\017 " + mess);
	
}

void ApollonChatTab::slotNickList(const QString& channel, const QStringList& list) {

	kdDebug() << k_funcinfo << "incoming nicks " << list<< endl;
	m_nickList = const_cast<QStringList&>(list);
	for (QStringList::ConstIterator it = list.begin(); it != list.end(); it++){
		if (!m_nickListView->findItem((const_cast<QString&>(*it)).remove('@'),0))
			changeNickList(ADD_NICK, (*it));
	}
	
}

void ApollonChatTab::slotPartedChannel(const QString &nick,const QString &channel, const QString &reason){
	kdDebug() << k_funcinfo << nick << " left the channel" << endl;
	changeNickList(DELETE_NICK, nick);
	appendLine("\00315\002<--\017\002 " + nick + " \017" + i18n("has left the channel") + " (" + reason + ")");

}

void ApollonChatTab::slotJoinedChannel(const QString &nick,const QString &channel){
	kdDebug() << k_funcinfo << nick << " joined the channel" << endl;
	if (!m_nickListView->findItem((const_cast<QString&>(nick)).remove('@'),0)) { //avoid double entries
		changeNickList(ADD_NICK, nick);
		appendLine("\00315\002-->\017\002 " + nick + " \017" + i18n("has joined the channel"));
	}
}
		
void ApollonChatTab::sendMessage(){
	if (!m_messageLineEdit->text().isEmpty()) {
		kirc->messageContact("#apollon-user", m_messageLineEdit->text());
		appendLine("\0032<\017" + m_nickName + "\0032>\017 " + m_messageLineEdit->text());
		m_messageLineEdit->clear();
	}
}

void ApollonChatTab::clickChangeNick() {
	bool ok;
	QString new_nick = KInputDialog::getText(i18n("Change nickname"), i18n("Enter a new nickname:"), m_nickName, &ok, this);
	if (ok){
		kirc->changeNickname(new_nick);
		m_messageLineEdit->setFocus();
		
	}
}


void ApollonChatTab::slotNickChange(const QString& old_nick, const QString& newNick)
{
	changeNickList(CHANGE_NICK, old_nick, newNick);
	appendLine("\00315\002---\017\002 " + old_nick + " \017" + i18n("is now known as") + " \002" + newNick);
}

void ApollonChatTab::slotGenerateNewNick(const QString& nickInUse)
{
	
	QString nick =  const_cast<QString&>(nickInUse);
	kdDebug() << k_funcinfo << "nickname is in use, change it" << endl;
	//nickInUse might have a number at the end, extract it, increment it and add it again
	int i = nick.length() -1;
	while(nick[i].isDigit())
		i--;
	QString name = nick.left(i+1);
	int number = nick.remove(name).toInt();
	number++;
	QString s;
	m_nickName = name + s.setNum(number);
	kirc->changeNickname(m_nickName);
	m_nickLabel->setText(i18n("Your nickname is currently:")+" <b>" + m_nickName + "</b>");
	
}

void ApollonChatTab::slotQuitChannel(const QString &nick, const QString &reason) {
	kdDebug() << k_funcinfo << "somebody quit " << nick << " why? " << reason << endl;
	slotPartedChannel( (const_cast<QString&>(nick)).section('!', 0, 0), "", reason);
}

void ApollonChatTab::slotTopicChange(const QString & nick, const QString & channel, const QString & topic)
{
	IrcToHtml parser;
	m_topic->setText("<b>" + i18n("Channel topic:") + "</b> " + parser.parse(topic));
	appendLine("\00315\002---\017\002 " + nick + " \017" + i18n("has changed the topic to:") + " \"" + topic + "\"");
}

void ApollonChatTab::slotExistingTopic(const QString & channel, const QString & topic)
{
	IrcToHtml parser;
	m_topic->setText("<b>" + i18n("Channel topic:") + "</b> " + parser.parse(topic));
}


void ApollonChatTab::changeNickList(NickOp op, const QString& nick1, const QString& nick2){
	QString Nick1 = const_cast<QString&>(nick1).remove('@');
	QString Nick2;
	if(nick2)
		Nick2 = const_cast<QString&>(nick2).remove('@');
	
	switch (op)
	{
		case ADD_NICK:
			new QListViewItem(m_nickListView, Nick1);
			m_nickList << Nick1;
			m_nickList.sort();
			m_messageLineEdit->addNick(Nick1);
			break;
			
		case DELETE_NICK:
			m_nickListView->takeItem(m_nickListView->findItem(Nick1, 0));
			m_nickList.remove(Nick1);
			m_nickList.sort();
			m_messageLineEdit->removeNick(Nick1);
			break;
			
		case CHANGE_NICK:
			changeNickList(DELETE_NICK, Nick1);
			changeNickList(ADD_NICK, Nick2);
			break;
		
	}
}

void ApollonChatTab::slotNickChanged(const QString & oldNick, const QString &newNick) {
	
	kdDebug() << k_funcinfo << " nick successfully changed  from " << oldNick << " to " << newNick << endl;
	slotNickChange(oldNick, newNick);
	m_nickName = newNick;
	m_nickLabel->setText("Your nickname is currently: <b>" + m_nickName + "</b>");
}


bool ApollonChatTab::event(QEvent* e)
{
	if (e->type() == QEvent::Show)
	{
		if (m_messageLineEdit->isEnabled())
			m_messageLineEdit->setFocus();
	}
	return ApollonChatIface::event(e);
}

void ApollonChatTab::setTray(ApollonSystemTray *tray)
{
	m_systemTray = tray;
}

void ApollonChatTab::setShowPopup(int show)
{
	m_showPopup = show;
}

#include "apollonchattab.moc"

