

/*
    Copyright (C) 2005-2009  Michel de Boer <michel@twinklephone.com>

    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.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#ifdef HAVE_KDE
#include <kfiledialog.h>
#include <kiconloader.h>
#include <kmimetype.h>
#include <krun.h>
#include <kuserprofile.h>
#else
#include "utils/mime_database.h"
//Added by qt3to4:
#include <QKeyEvent>
#include <QLabel>
#include <QPixmap>
#endif

#include "gui.h"
#include "sockets/url.h"
#include <QTextDocument>
#include "audits/memman.h"
#include "util.h"
#include <QPixmap>
#include <QCursor>
#include <QFileDialog>
#include "utils/file_utils.h"
#include <QFile>
#include "sendfileform.h"
#include <QStatusBar>
#include <QFrame>
#include <QTextDocument>
#include "messageform.h"


using namespace utils;

/** Maximum width for an inline image */
#define MAX_WIDTH_IMG_INLINE 400

/** Maximum height for an inline image */
#define MAX_HEIGHT_IMG_INLINE 400

#define IMG_SCALE_FACTOR(width, height) (std::min<float>( float(MAX_WIDTH_IMG_INLINE) / (width), float(MAX_HEIGHT_IMG_INLINE) / (height) ) )

MessageForm::MessageForm(QWidget* parent)
    : QMainWindow(parent)
{
	setupUi(this);

	(void)statusBar();
	init();
}

/*
 *  Destroys the object and frees any allocated resources
 */
MessageForm::~MessageForm()
{
	destroy();
	// no need to delete child widgets, Qt does it all for us
}

/*
 *  Sets the strings of the subwidgets using the current
 *  language.
 */
void MessageForm::languageChange()
{
	retranslateUi(this);
}


void MessageForm::init()
{
    this->setAttribute(Qt::WA_DeleteOnClose);
	
	_getAddressForm = 0;
	_remotePartyComplete = false;
	
	// Add label to display size of typed message
	_msgSizeLabel = new QLabel(this);
	statusBar()->addWidget(_msgSizeLabel);
	showMessageSize();
	
	// Set toolbutton icons for disabled options.
	setDisabledIcon(addressToolButton, "kontact_contacts-disabled.png");
	
	attachmentPopupMenu = new QMenu(this);
	MEMMAN_NEW(attachmentPopupMenu);
	
    connect(attachmentPopupMenu, SIGNAL(triggered(QAction*)),
        this, SLOT(attachmentPopupActivated(QAction*)));
	
	_serviceMap = NULL;
	_saveAsDialog = NULL;
	
	_isComposingLabel = NULL;
	
	// When the user edits the message, the composition indication
	// will be set to active.
	connect(msgLineEdit, SIGNAL(textChanged(const QString &)),
		this, SLOT(setLocalComposingIndicationActive()));
}

void MessageForm::destroy()
{
	if (_getAddressForm) {
		MEMMAN_DELETE(_getAddressForm);
		delete _getAddressForm;
	}
	
	MEMMAN_DELETE(attachmentPopupMenu);
	delete attachmentPopupMenu;
	
#ifdef HAVE_KDE
	vector<KService::Ptr> *serviceMap = (vector<KService::Ptr> *)_serviceMap;
	if (serviceMap) {
		MEMMAN_DELETE(serviceMap);
		delete serviceMap;
	}
#endif
	
	if (_saveAsDialog) {
		MEMMAN_DELETE(_saveAsDialog);
		delete _saveAsDialog;
	}
	
	if (_isComposingLabel) {
		MEMMAN_DELETE(_isComposingLabel);
		delete _isComposingLabel;
	}
}

void MessageForm::closeEvent(QCloseEvent *e)
{
	MEMMAN_DELETE(this); // destructive close
	QMainWindow::closeEvent(e);
}


void MessageForm::show()
{
	if (toLineEdit->text().isEmpty()) {
		sendFileAction->setEnabled(false);
		toLineEdit->setFocus();
	} else {
		// Once a message session has been created, the
		// source and destination cannot be changed anymore.
		fromComboBox->setEnabled(false);
		toLineEdit->setEnabled(false);
		addressToolButton->setEnabled(false);
		sendFileAction->setEnabled(true);
		msgLineEdit->setFocus();
	}
	
	QMainWindow::show();
}

void MessageForm::selectUserConfig(t_user *user_config)
{
	for (int i = 0; i < fromComboBox->count(); i++) {
        if (fromComboBox->itemText(i) ==
		    user_config->get_profile_name().c_str())
		{
            fromComboBox->setCurrentIndex(i);
			break;
		}
	}
}

void MessageForm::showAddressBook()
{
	if (!_getAddressForm) {
        _getAddressForm = new GetAddressForm(this);
        _getAddressForm->setModal(true);
		MEMMAN_NEW(_getAddressForm);
	}
	
	connect(_getAddressForm, 
		SIGNAL(address(const QString &)),
		this, SLOT(selectedAddress(const QString &)));
	
	_getAddressForm->show();
}

void MessageForm::selectedAddress(const QString &address)
{
	toLineEdit->setText(address);
}

/**
  * Check if there is a valid sender and receiver. If so, then set the sender
  * and receiver addresses in the message session object.
  */
bool MessageForm::updateMessageSession()
{
	string display, dest_str;
	t_user *from_user = phone->ref_user_profile(
                fromComboBox->currentText().toStdString());
	if (!from_user) {
		// The user profile is not active anymore
		fromComboBox->setFocus();
		return false;
	}
	
    ui->expand_destination(from_user, toLineEdit->text().trimmed().toStdString(),
			       display, dest_str);
	t_url dest(dest_str);
	
	if (!dest.is_valid()) {
		toLineEdit->setFocus();
		toLineEdit->selectAll();
		return false;
	}
	
	_msgSession->set_user(from_user);
	_msgSession->set_remote_party(t_display_url(dest, display));
	
	setRemotePartyCaption();

	return true;
}

/**
  * Determine if a message can be sent, i.e. there is a valid sender and
  * receiver. If a message can be sent, then lock the sender and receiver.
  * @return True if message can be sent, false otherwise.
  */
bool MessageForm::prepareSendMessage() {
	if (toLineEdit->text().isEmpty()) {
		// No recipient selected.
		return false;
	}
	
	if (toLineEdit->isEnabled()) {
		if (!updateMessageSession()) {
			// No valid sender and receiver.
			return false;
		}

		// Once a message session has been created, the
		// source and destination cannot be changed anymore.
		fromComboBox->setEnabled(false);
		toLineEdit->setEnabled(false);
		addressToolButton->setEnabled(false);	
		
	}
	
	return true;
}

/** Send a text message */
void MessageForm::sendMessage() {
	if (msgLineEdit->text().isEmpty()) {
		// Nothing to send
		return;
	}
	
	if (!prepareSendMessage()) {
		return;
	}
	
    _msgSession->send_msg(msgLineEdit->text().toStdString(), im::TXT_PLAIN);
}

/**
  * Send a file.
  * @param filename [in] Name of file to send.
  * @param subject [in] Subject to set in the message.
  */
void MessageForm::sendFile(const QString &filename, const QString &subject) {
	if (!prepareSendMessage()) {
		return;
	}
	
	t_media media("application/octet-stream");
	
#ifdef HAVE_KDE
	// Get mime type for the attachment
	KMimeType::Ptr pMime = KMimeType::findByURL(filename);
	media = t_media(pMime->name().ascii());
#else
    string mime_type = mime_database->get_mimetype(filename.toStdString());
	if (!mime_type.empty()) {
		media = t_media(mime_type);
	}
#endif

    _msgSession->send_file(filename.toStdString(), media, subject.toStdString());
}

/**
  * Add a message to the converstation broswer.
  * @param msg [in] The message to add.
  * @param name [in] The name of the sender of the message.
  */
void MessageForm::addMessage(const im::t_msg &msg, const QString &name)
{
	QString s = "<b>";
	
	// Timestamp and name of sender
	if (msg.direction == im::MSG_DIR_IN) s += "<font color=\"blue\">";
	s += time2str(msg.timestamp, "%H:%M:%S ").c_str();
#if QT_VERSION >= 0x050000
	s += name.toHtmlEscaped();
#else
	s += Qt::escape(name);
#endif
	if (msg.direction == im::MSG_DIR_IN) s += "</font>";
	s += "</b>";
	
	// Subject
	if (!msg.subject.empty()) {
		s += "<br>";
		s += "<b>";
		s += "Subject: ";
		s += "</b>";
		s += msg.subject.c_str();
	}
	
	// Text message
	if (!msg.message.empty()) {
		s += "<br>";
		if (msg.format == im::TXT_HTML) {
			s += msg.message.c_str();
		} else {
#if QT_VERSION >= 0x050000
			s += QString::fromStdString(msg.message).toHtmlEscaped();
#else
			s += Qt::escape(msg.message.c_str());
#endif
		}
	}
	
	// Attachment
	if (msg.has_attachment) {
		s += "<br>";
		s += "<a href=\"";
		s += msg.attachment_filename.c_str();
		s += "\">";
		
		bool show_attachment_inline = false;
		bool scale_image = false;
		int scaled_width = 0, scaled_height = 0;
		
		if (msg.attachment_media.type == "image") {
			// Show image inline if possible
			QPixmap image (msg.attachment_filename.c_str());
			if (!image.isNull()) {
				show_attachment_inline = true;
				if (image.width() > MAX_WIDTH_IMG_INLINE &&
				    image.height() > MAX_HEIGHT_IMG_INLINE)
				{
					// Shrink image
					scaled_width = int(image.width() * IMG_SCALE_FACTOR(image.width(), image.height()));
					scaled_height = int(image.height() * IMG_SCALE_FACTOR(image.width(), image.height()));
					scale_image = true;
				}
			}
		}
		
		s += "<img ";
		
		if (scale_image) {
			s += "width=";
			s += QString().setNum(scaled_width);
			s += " height=";
			s += QString().setNum(scaled_height);
			s += " ";
		}
		     
		s += "src=\"";
		
		if (show_attachment_inline) {
			s += msg.attachment_filename.c_str();
		} else {
			// Show an icon representing the attachment
#ifdef HAVE_KDE		
			KIconLoader iconLoader;
			QString iconName = KMimeType::iconForURL(
					msg.attachment_filename.c_str());
			s += iconLoader.iconPath(iconName, KIcon::Desktop);
#else
			// Set icon based on main mime type
			s += "mime_";
			s += msg.attachment_media.type.c_str();
			s += ".png";
#endif
		}
		
		s += "\">";
		s+= "<br>";
		
		s += msg.attachment_save_as_name.c_str();
		
		s += "</a> ";
		
		if (scale_image) {
			s += "<br>";
			s += "<i>";
			s += tr("image size is scaled down in preview");
			s += "</i>";
		}
		
		// Store the association between the tempory file name of the
		// save attachment and the suggested save-as file name. When
		// the user clicks on the attachment, only the temporary file name
		// is available. Through this association the suggested file name
		// can be retrieved.
		_filenameMap[msg.attachment_filename] = msg.attachment_save_as_name;
	}

	conversationBrowser->append(s);
}

void MessageForm::displayError(const QString &errorMsg)
{
	QString s = "<font color =\"red\">";
	s += "<b>";
    s += tr("Delivery failure");
	s += ": </b>";
#if QT_VERSION >= 0x050000
	s += errorMsg.toHtmlEscaped();
#else
	s += Qt::escape(errorMsg);
#endif
	s += "</font>";
	
	conversationBrowser->append(s);
}

void MessageForm::displayDeliveryNotification(const QString &notification)
{
	QString s = "<font color =\"darkgreen\">";
	s += "<b>";
    s += tr("Delivery notification");
	s += ": </b>";
#if QT_VERSION >= 0x050000
	s += notification.toHtmlEscaped();
#else
	s += Qt::escape(notification);
#endif
	s += "</font>";
	
	conversationBrowser->append(s);
}

void MessageForm::setRemotePartyCaption(void) {
	if (!_msgSession) return;
	t_user *user = _msgSession->get_user();
	t_display_url remote_party = _msgSession->get_remote_party();
    setWindowTitle(ui->format_sip_address(user,
			remote_party.display, remote_party.url).c_str());
}

void MessageForm::showAttachmentPopupMenu(const QUrl &attachment) {
#ifdef HAVE_KDE
	vector<KService::Ptr> *serviceMap = (vector<KService::Ptr> *)_serviceMap;
	
	if (serviceMap) {
		MEMMAN_DELETE(serviceMap);
		delete serviceMap;
	}
	serviceMap = new vector<KService::Ptr>;
	MEMMAN_NEW(serviceMap);
	_serviceMap = (void *)serviceMap;
#endif
	
	int id = 0; // Identity of popup menu item
	
	// Store attachment. When the user selects an attachment we still
	// know which attachment was clicked.
	clickedAttachment = attachment.toLocalFile();
	
	attachmentPopupMenu->clear();
	
	QIcon saveIcon(QPixmap(":/icons/images/save_as.png"));
    attachmentPopupMenu->addAction(saveIcon, "Save as...")->setData(id++);
	
#ifdef HAVE_KDE
	// Get mime type for the attachment
	KMimeType::Ptr pMime = KMimeType::findByURL(attachment);
	
	// Get applications that can open the mime type
	KServiceTypeProfile::OfferList services = KServiceTypeProfile::offers(
			pMime->name(), "Application");
	
	KServiceTypeProfile::OfferList::ConstIterator it;
	for (it = services.begin(); it != services.end(); ++it) {
		KService::Ptr service = (*it).service();
		serviceMap->push_back(service);
		QString menuText = tr("Open with %1...").arg(service->name());
		QPixmap pixmap = service->pixmap(KIcon::Small);
		QIcon iconSet;
		iconSet.setPixmap(pixmap, QIcon::Small);
		attachmentPopupMenu->insertItem(iconSet, menuText, id++);
	}
	
	QIcon openIcon(QPixmap(":/icons/images/fileopen.png"));
	attachmentPopupMenu->insertItem(openIcon, tr("Open with..."), id++);
#endif
	
	attachmentPopupMenu->popup(QCursor::pos(), 0);
}

void MessageForm::attachmentPopupActivated(QAction* act) {
    int id = act->data().toInt();

#ifdef HAVE_KDE
	vector<KService::Ptr> *serviceMap = (vector<KService::Ptr> *)_serviceMap;
	assert(serviceMap);
#endif
	
	if (id == 0) {
#ifdef HAVE_KDE		
		KFileDialog *d = new KFileDialog(QString::null, QString::null, this, 0, true);
		MEMMAN_NEW(d);
		d->setOperationMode(KFileDialog::Saving);
		
		connect(d, SIGNAL(okClicked()), this,
			SLOT(saveAttachment()));
#else
		QFileDialog *d = new QFileDialog(this);
		MEMMAN_NEW(d);
		
		connect(d, SIGNAL(fileSelected(const QString &)), this,
			SLOT(saveAttachment()));
#endif
        d->selectFile(QString::fromStdString(_filenameMap[clickedAttachment.toStdString()]));
        d->setWindowTitle(tr("Save attachment as..."));
		
		if (_saveAsDialog) {
			MEMMAN_DELETE(_saveAsDialog);
			delete _saveAsDialog;
		}
		_saveAsDialog = d;
		
		d->show();
#ifdef HAVE_KDE
	} else if (id > serviceMap->size()) {
		KURL::List urls;
		urls << clickedAttachment;
		KRun::displayOpenWithDialog(urls, false);
	} else {
		KURL::List urls;
		urls << clickedAttachment;
		KRun::run(*serviceMap->at(id-1), urls);
#endif
	}
}

void MessageForm::saveAttachment() {
#ifdef HAVE_KDE
	KFileDialog *d = dynamic_cast<KFileDialog *>(_saveAsDialog);
#else
	QFileDialog *d = dynamic_cast<QFileDialog *>(_saveAsDialog);
#endif
    QStringList files = d->selectedFiles();
    QString filename;

    if (!files.empty())
        filename = files[0];
	
	if (QFile::exists(filename)) {
		bool overwrite = ((t_gui *)ui)->cb_ask_msg(this, 
                  tr("File already exists. Do you want to overwrite this file?").toStdString(),
				  MSG_WARNING);
		
		if (!overwrite) return;
	}
	
    if (!filecopy(clickedAttachment.toStdString(), filename.toStdString())) {
        ((t_gui *)ui)->cb_show_msg(this, tr("Failed to save attachment.").toStdString(),
					   MSG_CRITICAL);
	}
}

/** Choose a file to send */
void MessageForm::chooseFileToSend()
{
	// Indicate that a message is being composed.
	setLocalComposingIndicationActive();
	
	SendFileForm *form = new SendFileForm(this);
	MEMMAN_NEW(form);
	// Form will auto destruct itself on close.
	
	connect(form, SIGNAL(selected(const QString &, const QString &)),
		this, SLOT(sendFile(const QString &, const QString &)));
	
	form->show();
}

/** 
 * Show an is-composing indication in the status bar.
 * @param name [in] The name of the sender of the message.
 */
void MessageForm::setComposingIndication(const QString &name)
{

	if (!_isComposingLabel) {
		_isComposingLabel = new QLabel(NULL);
		MEMMAN_NEW(_isComposingLabel);
		
		_isComposingLabel->setText(tr("%1 is typing a message.").arg(name));
		_isComposingLabel->setFrameStyle(QFrame::NoFrame | QFrame::Plain);
		statusBar()->addWidget(_isComposingLabel);
	}
}

/** Clear an is-composing indication from the status bar. */
void MessageForm::clearComposingIndication()
{
	if (_isComposingLabel) {
		statusBar()->removeWidget(_isComposingLabel);
        statusBar()->clearMessage();
	
		MEMMAN_DELETE(_isComposingLabel);
		delete _isComposingLabel;
		_isComposingLabel = NULL;
	}
}

/** Set the local composition indication to active. */
void MessageForm::setLocalComposingIndicationActive()
{
	_msgSession->set_local_composing_state(im::COMPOSING_STATE_ACTIVE);
}

void MessageForm::keyPressEvent(QKeyEvent *e)
{
	switch (e->key()) {
	case Qt::Key_Return:
	case Qt::Key_Enter:
		if (sendPushButton->isEnabled()) {
			sendPushButton->animateClick();
		}
		break;
	default:
		e->ignore();
	}
}

void MessageForm::toAddressChanged(const QString &address)
{
	sendFileAction->setEnabled(!address.isEmpty());
}

/** Show the size of the typed message */
void MessageForm::showMessageSize()
{
	uint len = msgLineEdit->text().length();
	
	QString s(tr("Size"));
	s += ": ";
	s += QString().setNum(len);
	
	_msgSizeLabel->setText(s);
}
