/*
    kopetemessage.cpp  -  Base class for Kopete messages

    Copyright (c) 2002-2003 by Martijn Klingens       <klingens@kde.org>
    Copyright (c) 2002-2006 by Olivier Goffart        <ogoffart @ kde.org>

    Kopete    (c) 2002-2005 by the Kopete developers  <kopete-devel@kde.org>

    *************************************************************************
    *                                                                       *
    * This library 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 of the License, or (at your option) any later version.      *
    *                                                                       *
    *************************************************************************
*/

#include <stdlib.h>

#include <tqcolor.h>
#include <tqbuffer.h>
#include <tqimage.h>
#include <tqstylesheet.h>
#include <tqregexp.h>
#include <tqtextcodec.h>
#include <kdebug.h>
#include <tdelocale.h>
#include <kiconloader.h>
#include <kstringhandler.h>
#include <kmdcodec.h>
#include <tqguardedptr.h>

#include "kopetemessage.h"
#include "kopetemetacontact.h"
#include "kopeteprotocol.h"
#include "kopetechatsession.h"
#include "kopeteprefs.h"
#include "kopetecontact.h"
#include "kopeteemoticons.h"


using namespace Kopete;

class Message::Private
	: public TDEShared
{
public:
	Private( const TQDateTime &timeStamp, const Contact *from, const ContactPtrList &to,
	         const TQString &subject, MessageDirection direction,
	         const TQString &requestedPlugin, MessageType type );

	TQGuardedPtr<const Contact> from;
	ContactPtrList to;
	ChatSession *manager;

	MessageDirection direction;
	MessageFormat format;
	MessageType type;
	TQString requestedPlugin;
	MessageImportance importance;
	bool bgOverride;
	bool fgOverride;
	bool rtfOverride;
	bool isRightToLeft;
	TQDateTime timeStamp;
	TQFont font;

	TQColor fgColor;
	TQColor bgColor;
	TQString body;
	TQString subject;
};

Message::Private::Private( const TQDateTime &timeStamp, const Contact *from,
		const ContactPtrList &to, const TQString &subject,
		MessageDirection direction, const TQString &requestedPlugin, MessageType type )
: from( from ), to( to ), manager( 0 ), direction( direction ), format( PlainText ), type( type ),
	requestedPlugin( requestedPlugin ), importance( (to.count() <= 1) ? Normal : Low ),
	bgOverride( false ), fgOverride( false ), rtfOverride( false ), isRightToLeft( false ),
	timeStamp( timeStamp ), body( TQString() ), subject( subject )
{
}

Message::Message()
: d( new Private( TQDateTime::currentDateTime(), 0L, TQPtrList<Contact>(), TQString(), Internal,
	TQString(), TypeNormal ) )
{
}

Message::Message( const Contact *fromKC, const TQPtrList<Contact> &toKC, const TQString &body,
		  MessageDirection direction, MessageFormat f, const TQString &requestedPlugin, MessageType type )
: d( new Private( TQDateTime::currentDateTime(), fromKC, toKC, TQString(), direction, requestedPlugin, type ) )
{
	doSetBody( body, f );
}

Message::Message( const Contact *fromKC, const Contact *toKC, const TQString &body,
		  MessageDirection direction, MessageFormat f, const TQString &requestedPlugin, MessageType type )
{
	TQPtrList<Contact> to;
	to.append(toKC);
	d = new Private( TQDateTime::currentDateTime(), fromKC, to, TQString(), direction, requestedPlugin, type );
	doSetBody( body, f );
}

Message::Message( const Contact *fromKC, const TQPtrList<Contact> &toKC, const TQString &body,
		  const TQString &subject, MessageDirection direction, MessageFormat f, const TQString &requestedPlugin, MessageType type )
    : d( new Private( TQDateTime::currentDateTime(), fromKC, toKC, subject, direction, requestedPlugin, type ) )
{
	doSetBody( body, f );
}

Message::Message( const TQDateTime &timeStamp, const Contact *fromKC, const TQPtrList<Contact> &toKC,
		  const TQString &body, MessageDirection direction, MessageFormat f, const TQString &requestedPlugin, MessageType type )
    : d( new Private( timeStamp, fromKC, toKC, TQString(), direction, requestedPlugin, type ) )
{
	doSetBody( body, f );
}


Message::Message( const TQDateTime &timeStamp, const Contact *fromKC, const TQPtrList<Contact> &toKC,
		  const TQString &body, const TQString &subject, MessageDirection direction, MessageFormat f, const TQString &requestedPlugin, MessageType type )
    : d( new Private( timeStamp, fromKC, toKC, subject, direction, requestedPlugin, type ) )
{
	doSetBody( body, f );
}

Kopete::Message::Message( const Message &other )
	: d(other.d)
{
}



Message& Message::operator=( const Message &other )
{
	d = other.d;
	return *this;
}

Message::~Message()
{
}

void Message::detach()
{
	// there is no detach in TDESharedPtr :(
	if( d.count() == 1 )
		return;

	// Warning: this only works as long as the private object doesn't contain pointers to allocated objects.
	// The from contact for example is fine, but it's a shallow copy this way.
	d = new Private(*d);
}

void Message::setBgOverride( bool enabled )
{
	detach();
	d->bgOverride = enabled;
}

void Message::setFgOverride( bool enabled )
{
	detach();
	d->fgOverride = enabled;
}

void Message::setRtfOverride( bool enabled )
{
	detach();
	d->rtfOverride = enabled;
}

void Message::setFg( const TQColor &color )
{
	detach();
	d->fgColor=color;
}

void Message::setBg( const TQColor &color )
{
	detach();
	d->bgColor=color;
}

void Message::setFont( const TQFont &font )
{
	detach();
	d->font = font;
}

void Message::doSetBody( const TQString &_body, Message::MessageFormat f )
{
	TQString body = _body;

	//TODO: move that in ChatTextEditPart::contents
	if( f == RichText )
	{
		//This is coming from the RichTextEditor component.
		//Strip off the containing HTML document
		body.replace( TQRegExp( TQString::fromLatin1(".*<body[^>]*>(.*)</body>.*") ), TQString::fromLatin1("\\1") );

		//Strip <p> tags
		body.replace( TQString::fromLatin1("<p>"), TQString() );

		//Replace </p> with a <br/>
		body.replace( TQString::fromLatin1("</p>"), TQString::fromLatin1("<br/>") );

		//Remove trailing </br>
		if ( body.endsWith( TQString::fromLatin1("<br/>") ) )
			body.truncate( body.length() - 5 );
	
		body.remove( TQString::fromLatin1("\n") );
		body.replace( TQRegExp( TQString::fromLatin1( "\\s\\s" ) ), TQString::fromLatin1( " &nbsp;" ) );
	}
	/*
	else if( f == ParsedHTML )
	{
		kdWarning( 14000 ) << k_funcinfo << "using ParsedHTML which is internal! Message: '" <<
			body << "', Backtrace: " << kdBacktrace() << endl;
	}
	*/

	d->body = body;
	d->format = f;

	// unescaping is very expensive, do it only once and cache the result
	d->isRightToLeft = ( f & RichText ? unescape( d->body ).isRightToLeft() : d->body.isRightToLeft() );
}

void Message::setBody( const TQString &body, MessageFormat f )
{
	detach();

	doSetBody( body, f );
}

bool Message::isRightToLeft() const
{
	return d->isRightToLeft;
}

void Message::setImportance(Message::MessageImportance i)
{
	detach();
	d->importance = i;
}

TQString Message::unescape( const TQString &xml )
{
	TQString data = xml;

	// Remove linebreak and multiple spaces. First return nbsp's to normal spaces :)
	data.simplifyWhiteSpace();

	int pos;
	while ( ( pos = data.find( '<' ) ) != -1 )
	{
		int endPos = data.find( '>', pos + 1 );
		if( endPos == -1 )
			break;    // No more complete elements left

		// Take the part between < and >, and extract the element name from that
		int matchWidth = endPos - pos + 1;
		TQString match = data.mid( pos + 1, matchWidth - 2 ).simplifyWhiteSpace();
		int elemEndPos = match.find( ' ' );
		TQString elem = ( elemEndPos == -1 ? match.lower() : match.left( elemEndPos ).lower() );
		if ( elem == TQString::fromLatin1( "img" ) )
		{
			// Replace smileys with their original text'
			const TQString attrTitle  = TQString::fromLatin1( "title=\"" );
			int titlePos    = match.find( attrTitle, elemEndPos );
			int titleEndPos = match.find( '"',       titlePos + attrTitle.length() );
			if( titlePos == -1 || titleEndPos == -1 )
			{
				// Not a smiley but a normal <img>
				// Don't update pos, we restart at this position :)
				data.remove( pos, matchWidth );
			}
			else
			{
				TQString orig = match.mid( titlePos + attrTitle.length(),
					titleEndPos - titlePos - attrTitle.length() );
				data.replace( pos, matchWidth, orig );
				pos += orig.length();
			}
		}
		else if ( elem == TQString::fromLatin1( "/p" ) || elem == TQString::fromLatin1( "/div" ) ||
			elem == TQString::fromLatin1( "br" ) )
		{
			// Replace paragraph, div and line breaks with a newline
			data.replace( pos, matchWidth, '\n' );
			pos++;
		}
		else
		{
			// Remove all other elements entirely
			// Don't update pos, we restart at this position :)
			data.remove( pos, matchWidth );
		}
	}

	// Replace stuff starting with '&'
	data.replace( TQString::fromLatin1( "&gt;" ), TQString::fromLatin1( ">" ) );
	data.replace( TQString::fromLatin1( "&lt;" ), TQString::fromLatin1( "<" ) );
	data.replace( TQString::fromLatin1( "&quot;" ), TQString::fromLatin1( "\"" ) );
	data.replace( TQString::fromLatin1( "&nbsp;" ), TQString::fromLatin1( " " ) );
	data.replace( TQString::fromLatin1( "&amp;" ), TQString::fromLatin1( "&" ) );
	data.replace( TQString::fromLatin1( "&#160;" ), TQString::fromLatin1( " " ) );  //this one is used in jabber:  note, we should escape all &#xx;

	return data;
}

TQString Message::escape( const TQString &text )
{
	TQString html = TQStyleSheet::escape( text );
 	//Replace carriage returns inside the text
	html.replace( TQString::fromLatin1( "\n" ), TQString::fromLatin1( "<br />" ) );
	//Replace a tab with 4 spaces
	html.replace( TQString::fromLatin1( "\t" ), TQString::fromLatin1( "&nbsp;&nbsp;&nbsp;&nbsp;" ) );

	//Replace multiple spaces with &nbsp;
	//do not replace every space so we break the linebreak
	html.replace( TQRegExp( TQString::fromLatin1( "\\s\\s" ) ), TQString::fromLatin1( "&nbsp; " ) );

	return html;
}



TQString Message::plainBody() const
{
	TQString body=d->body;
	if( d->format & RichText )
	{
		body = unescape( body );
	}
	return body;
}

TQString Message::escapedBody() const
{
	TQString escapedBody=d->body;
//	kdDebug(14000) << k_funcinfo << escapedBody << " " << d->rtfOverride << endl;

	if( d->format & PlainText )
	{
		escapedBody=escape( escapedBody );
	}
	else if( d->format & RichText && d->rtfOverride)
	{
		//remove the rich text
		escapedBody = escape (unescape( escapedBody ) );
	}

	return escapedBody;
}

TQString Message::parsedBody() const
{
	//kdDebug(14000) << k_funcinfo << "messageformat: " << d->format << endl;

	if( d->format == ParsedHTML )
	{
		return d->body;
	}
	else
	{
		return Kopete::Emoticons::parseEmoticons(parseLinks(escapedBody(), RichText));
	}
}

static TQString makeRegExp( const char *pattern )
{
	const TQString urlChar = TQString::fromLatin1( "\\+\\-\\w\\./#@&;:=\\?~%_,\\!\\$\\*\\(\\)" );
	const TQString boundaryStart = TQString::fromLatin1( "(^|[^%1])(" ).arg( urlChar );
	const TQString boundaryEnd = TQString::fromLatin1( ")([^%1]|$)" ).arg( urlChar );

	return boundaryStart + TQString::fromLatin1(pattern) + boundaryEnd;
}

TQString Message::parseLinks( const TQString &message, MessageFormat format )
{
	if ( format == ParsedHTML )
		return message;

	if ( format & RichText )
	{
		// < in HTML *always* means start-of-tag
		TQStringList entries = TQStringList::split( TQChar('<'), message, true );

		TQStringList::Iterator it = entries.begin();

		// first one is different: it doesn't start with an HTML tag.
		if ( it != entries.end() )
		{
			*it = parseLinks( *it, PlainText );
			++it;
		}

		for ( ; it != entries.end(); ++it )
		{
			TQString curr = *it;
			// > in HTML means start-of-tag if and only if it's the first one after a <
			int tagclose = curr.find( TQChar('>') );
			// no >: the HTML is broken, but we can cope
			if ( tagclose == -1 )
				continue;
			TQString tag = curr.left( tagclose + 1 );
			TQString body = curr.mid( tagclose + 1 );
			*it = tag + parseLinks( body, PlainText );
		}
		return entries.join(TQString::fromLatin1("<"));
	}

	TQString result = message;

	// common subpatterns - may not contain matching parens!
	const TQString name = TQString::fromLatin1( "[\\w\\+\\-=_\\.]+" );
	const TQString userAndPassword = TQString::fromLatin1( "(?:%1(?::%1)?\\@)" ).arg( name );
	const TQString urlChar = TQString::fromLatin1( "\\+\\-\\w\\./#@&;:=\\?~%_,\\!\\$\\*\\(\\)" );
	const TQString urlSection = TQString::fromLatin1( "[%1]+" ).arg( urlChar );
	const TQString domain = TQString::fromLatin1( "[\\-\\w_]+(?:\\.[\\-\\w_]+)+" );

	//Replace http/https/ftp links:
	// Replace (stuff)://[user:password@](linkstuff) with a link
	result.replace(
		TQRegExp( makeRegExp("\\w+://%1?\\w%2").arg( userAndPassword, urlSection ) ),
		TQString::fromLatin1("\\1<a href=\"\\2\" title=\"\\2\">\\2</a>\\3" ) );

	// Replace www.X.Y(linkstuff) with a http: link
	result.replace(
		TQRegExp( makeRegExp("%1?www\\.%2%3").arg( userAndPassword, domain, urlSection ) ),
		TQString::fromLatin1("\\1<a href=\"http://\\2\" title=\"http://\\2\">\\2</a>\\3" ) );

	//Replace Email Links
	// Replace user@domain with a mailto: link
	result.replace(
		TQRegExp( makeRegExp("%1@%2").arg( name, domain ) ),
		TQString::fromLatin1("\\1<a href=\"mailto:\\2\" title=\"mailto:\\2\">\\2</a>\\3") );

	//Workaround for Bug 85061: Highlighted URLs adds a ' ' after the URL itself
	// the trailing  &nbsp; is included in the url.
	result.replace( TQRegExp( TQString::fromLatin1("(<a href=\"[^\"]+)(&nbsp;)(\")")  ) , TQString::fromLatin1("\\1\\3") );

	return result;
}



TQDateTime Message::timestamp() const
{
	return d->timeStamp;
}

const Contact *Message::from() const
{
	return d->from;
}

TQPtrList<Contact> Message::to() const
{
	return d->to;
}

Message::MessageType Message::type() const
{
	return d->type;
}

TQString Message::requestedPlugin() const
{
	return d->requestedPlugin;
}

TQColor Message::fg() const
{
	return d->fgColor;
}

TQColor Message::bg() const
{
	return d->bgColor;
}

TQFont Message::font() const
{
	//TQDomElement bodyNode = d->xmlDoc.elementsByTagName( TQString::fromLatin1("body") ).item(0).toElement();
	//return TQFont( bodyNode.attribute( TQString::fromLatin1("font") ), bodyNode.attribute( TQString::fromLatin1("fontsize") ).toInt() );
	return d->font;
}

TQString Message::subject() const
{
	return d->subject;
}

Message::MessageFormat Message::format() const
{
	return d->format;
}

Message::MessageDirection Message::direction() const
{
	return d->direction;
}

Message::MessageImportance Message::importance() const
{
	return d->importance;
}

ChatSession *Message::manager() const
{
	return d->manager;
}

void Message::setManager(ChatSession *kmm)
{
	detach();
	d->manager=kmm;
}

TQString Message::getHtmlStyleAttribute() const
{
	TQString styleAttribute;
	
	styleAttribute = TQString::fromUtf8("style=\"");

	// Affect foreground(color) and background color to message.
	if( !d->fgOverride && d->fgColor.isValid() )
	{
		styleAttribute += TQString::fromUtf8("color: %1; ").arg(d->fgColor.name());
	}
	if( !d->bgOverride && d->bgColor.isValid() )
	{
		styleAttribute += TQString::fromUtf8("background-color: %1; ").arg(d->bgColor.name());
	}
	
	// Affect font parameters.
	if( !d->rtfOverride && d->font!=TQFont() )
	{
		TQString fontstr;
		if(!d->font.family().isNull())
			fontstr+=TQString::fromLatin1("font-family: ")+d->font.family()+TQString::fromLatin1("; ");
		if(d->font.italic())
			fontstr+=TQString::fromLatin1("font-style: italic; ");
		if(d->font.strikeOut())
			fontstr+=TQString::fromLatin1("text-decoration: line-through; ");
		if(d->font.underline())
			fontstr+=TQString::fromLatin1("text-decoration: underline; ");
		if(d->font.bold())
			fontstr+=TQString::fromLatin1("font-weight: bold;");

		styleAttribute += fontstr;
	}

	styleAttribute += TQString::fromUtf8("\"");

	return styleAttribute;
}

// KDE4: Move that to a utils class/namespace
TQString Message::decodeString( const TQCString &message, const TQTextCodec *providedCodec, bool *success )
{
	/*
	Note to everyone. This function is not the most efficient, that is for sure.
	However, it *is* the only way we can be guarenteed that a given string is
	decoded properly.
	*/

	if( success )
		*success = true;

	// Avoid heavy codec tests on empty message.
	if( message.isEmpty() )
            return TQString::fromAscii( message );

	//Check first 128 chars
	int charsToCheck = message.length();
	charsToCheck = 128 > charsToCheck ? charsToCheck : 128;

	//They are providing a possible codec. Check if it is valid
	if( providedCodec && providedCodec->heuristicContentMatch( message, charsToCheck ) >= 0 )
	{
		//All chars decodable.
		return providedCodec->toUnicode( message );
	}

	//Check if it is UTF
	if( KStringHandler::isUtf8(message) )
	{
		//We have a UTF string almost for sure. At least we know it will be decoded.
		return TQString::fromUtf8( message );
	}

	//Try codecForContent - exact match
	TQTextCodec *testCodec = TQTextCodec::codecForContent(message, charsToCheck);
	if( testCodec && testCodec->heuristicContentMatch( message, charsToCheck ) >= 0 )
	{
		//All chars decodable.
		return testCodec->toUnicode( message );
	}

	kdWarning(14000) << k_funcinfo << "Unable to decode string using provided codec(s), taking best guesses!" << endl;
	if( success )
		*success = false;

	//We don't have any clues here.

	//Try local codec
	testCodec = TQTextCodec::codecForLocale();
	if( testCodec && testCodec->heuristicContentMatch( message, charsToCheck ) >= 0 )
	{
		//All chars decodable.
		kdDebug(14000) << k_funcinfo << "Using locale's codec" << endl;
		return testCodec->toUnicode( message );
	}

	//Try latin1 codec
	testCodec = TQTextCodec::codecForMib(4);
	if( testCodec && testCodec->heuristicContentMatch( message, charsToCheck ) >= 0 )
	{
		//All chars decodable.
		kdDebug(14000) << k_funcinfo << "Using latin1" << endl;
		return testCodec->toUnicode( message );
	}

	kdDebug(14000) << k_funcinfo << "Using latin1 and cleaning string" << endl;
	//No codec decoded. Just decode latin1, and clean out any junk.
	TQString result = TQString::fromLatin1( message );
	const uint length = message.length();
	for( uint i = 0; i < length; ++i )
	{
		if( !result[i].isPrint() )
			result[i] = '?';
	}

	return result;
}
