#include <QtGui/QWidget>
#include <QtGui/QLabel>
#include <string>
#include <sstream>
#include <iostream>
#include <vector>
#include <cstdlib>
#include <cassert>

#include "softProj\ChatWidget\ChatWidget.h"
#include "softProj\ChatWidget\BuddyHolder.h"
#include "ui_ChatWidget.h"

void ChatWidget::setMyName(const std::string & name) {
	this->myName_ = name;
}

ChatWidget::ChatWidget(QWidget * parent) : QWidget(parent)
{
	ui_.setupUi(this);

	bool connect = QObject::connect(
		ui_.writingBox, SIGNAL(confirmMessageSignal(void)),
		this, SLOT(confirmMessageSlot(void))
		);

	assert(connect);

	currentConversation_ = NULL;
	
	disableWriting();
	clearConversationOutput();

	ui_.chatBuddiesTable->horizontalHeader()->setResizeMode(QHeaderView::ResizeToContents);
}

ChatWidget::~ChatWidget()
{
}

void ChatWidget::confirmMessageSlot() {
	const std::string * message = retriveMessageFromInput();

	if (message->length() > 0) {
		sendMessage(message);
	}
}

void ChatWidget::sendMessage(const std::string * message) {
	if (currentConversation_) {
		ChatMessage * chatMessage = currentConversation_->sendMessage(message);
		displayMessage(chatMessage);
		ui_.writingBox->clear();
		emit sendChatMessageSignal(chatMessage);
	}	
}

void ChatWidget::receiveMessage(ChatMessage *  message) {
	if (currentConversation_) {
		ChatBuddy * currentBuddy = currentConversation_->getToBuddy();

		if (currentBuddy->getId() == message->getBuddyId()) {
			if (isChatVisible_) {
				currentConversation_->receiveMessage(message);
				displayMessage(message);
			}
			else {
				currentConversation_->receiveUnreadMessage(message);
				emit unreadChatMessageSignal(message);
			}
		}
		else if (currentBuddy->getId() == message->getBuddyId()) {
			currentConversation_->receiveMessage(message);
			displayMessage(message);
		}
		else {
			receiveUnreadMessage(message, true);
		}
	}
	else {
		receiveUnreadMessage(message, true);
	}
}

void ChatWidget::receiveUnreadMessage(ChatMessage * message, bool shouldEmit) {		
	ChatConversation * correctConversation;

	correctConversation = findConversation(message->getBuddyId());

	if (correctConversation != NULL) {
		correctConversation->receiveUnreadMessage(message);

		ChatBuddy * correctBuddy = correctConversation->getToBuddy();

		BuddyHolder * correctBuddyWidget = findBuddyWidget(correctBuddy->getId());
		correctBuddyWidget->addUnreadMessage();

		if (shouldEmit) {
			emit unreadChatMessageSignal(message);		
		}
	}
	else {
		emit unreadChatMessageSignal(message);
		addMessageFromUnknownBuddy(message);
	}
}

void ChatWidget::addMessageFromUnknownBuddy(ChatMessage * message) {
	unreadMessages_.push_back(message);
}

BuddyHolder * ChatWidget::findBuddyWidget(int buddyId) {
	if (buddyWidgets_.find(buddyId) != buddyWidgets_.end()) {
		return buddyWidgets_[buddyId];
	}
	else {
		return NULL;
	}
}

void ChatWidget::displayMessage(ChatMessage * message) {
	std::string * formatedMessage = formatMessage(message);
	ui_.conversationBox->appendHtml(QString::fromUtf8((formatedMessage->c_str())));
}

std::string * ChatWidget::formatMessage(ChatMessage * message) {
	if (message->isIncoming()) {
		ChatBuddy * authorBuddy = findBuddy(message->getBuddyId());

		if (authorBuddy) {
			return new std::string("<strong>" + authorBuddy->getName() + " " + message->geTimeStamp() + "</strong>: " + message->getText());
		}
		else {
			std::ostringstream builder;	
			builder << "Buddy with id " << message->getBuddyId() << " not found";
			throw new std::exception(builder.str().c_str());
		}
	}
	else {
		return new std::string("<strong>" + myName_ + "</strong>: " + message->getText());
	}
}

const std::string * ChatWidget::retriveMessageFromInput() {
	QString message = ui_.writingBox->toPlainText();
	return new std::string(message.toUtf8().data());
}

void ChatWidget::showChat() {
	if (currentConversation_) {
		clearConversationOutput();
		displayConversation(currentConversation_);
		enableWriting();
	}
	else {
		clearConversationOutput();
		disableWriting();
	}

	isChatVisible_ = true;
}

void ChatWidget::hideChat() {
	isChatVisible_ = false;
}

void ChatWidget::displayConversation(ChatConversation * conversation) {

	emit readChatMessagesSignal(conversation->getUnreadMessagesCount());

	ChatBuddy * toBuddy = findBuddy(conversation->getToBuddyId());
	ui_.buddyNameLabel->setText(QString::fromUtf8(toBuddy->getName().c_str()));

	std::vector<ChatMessage *> messages = conversation->getMessages();

	for (std::vector<ChatMessage *>::const_iterator it = messages.begin(); it != messages.end(); ++it) {
		displayMessage((*it));
	}

	BuddyHolder * buddyWidget = findBuddyWidget(toBuddy->getId());
	buddyWidget->readAllMessages();
	conversation->readAllMessages();
}

void ChatWidget::clearConversationInput() {
	ui_.writingBox->clear();
}

void ChatWidget::clearConversationOutput() {
	ui_.conversationBox->clear();
	ui_.buddyNameLabel->setText(QString::fromUtf16(L"Neprobíhá žádná konverzace"));
}

void ChatWidget::saveConversationInput(ChatConversation * conversation) {
 	const std::string * input = retriveMessageFromInput();
	conversation->saveInput(input);
}

void ChatWidget::loadConversationInput(ChatConversation * conversation) {
	std::string input = conversation->loadInput();
	ui_.writingBox->appendPlainText(QString::fromUtf8(input.c_str()));
}

void ChatWidget::switchConversation(ChatBuddy * toBuddy) {
	ChatConversation * conversation = findConversation(toBuddy->getId());

	if (conversation != currentConversation_) {
		if (currentConversation_) {
			saveConversationInput(currentConversation_);
			deactivateBuddyWidget(currentConversation_->getToBuddy());
		}

		clearConversationInput();
		clearConversationOutput();

		if (conversation) {
			currentConversation_ = conversation;	
			loadConversationInput(conversation);
			activateBuddyWidget(toBuddy);
			enableWriting();			
		}

		displayConversation(currentConversation_);
	}
}

void ChatWidget::deactivateBuddyWidget(ChatBuddy * toBuddy) {
	BuddyHolder * buddyWidget = findBuddyWidget(toBuddy->getId());
	buddyWidget->setUnselected();
}

void ChatWidget::activateBuddyWidget(ChatBuddy * toBuddy) {
	BuddyHolder * buddyWidget = findBuddyWidget(toBuddy->getId());
	buddyWidget->setSelected();
}

ChatConversation * ChatWidget::findConversation(int buddyId) {
	if (conversations_.find(buddyId) != conversations_.end()) {
		return conversations_[buddyId];
	}
	else {
		return NULL;
	}
}

ChatBuddy * ChatWidget::findBuddy(int buddyId) {
	if (buddies_.find(buddyId) != buddies_.end()) {
		return buddies_[buddyId];
	}
	else {
		return NULL;
	}
}

void ChatWidget::addBuddy(ChatBuddy * buddy) {
	if (!findBuddy(buddy->getId()))
	{
		buddies_.insert(TBuddyPair(buddy->getId(), buddy));

		addConversation(buddy);
		addBuddyWidget(buddy);

		ChatConversation * conversation = findConversation(buddy->getId());

		if (conversation == NULL) {
			return;
		}

		// Add unread messages from time that user did not know this buddy

		std::list<ChatMessage*>::iterator it = unreadMessages_.begin();

		while (it != unreadMessages_.end()) {
			if ((*it)->getBuddyId() == buddy->getId()) {
				ChatMessage * message = *it;				
				it = unreadMessages_.erase(it);
				receiveUnreadMessage(message, false);
			}
			else {
				++it;
			}
		}
	}
}

void ChatWidget::addConversation(ChatBuddy * buddy) {
	ChatConversation * conversation = new ChatConversation(buddy);
	conversations_.insert(TConversationPair(buddy->getId(), conversation));	
}

void ChatWidget::addBuddyWidget(ChatBuddy * buddy) {
	int rowCount = ui_.chatBuddiesTable->rowCount();
	ui_.chatBuddiesTable->setRowCount(rowCount + 1);

	QTableWidgetItem * nameItem = new QTableWidgetItem(QString::fromUtf8(buddy->getName().c_str()));
	nameItem->setData(Qt::UserRole, QVariant::fromValue(buddy->getId()));
	ui_.chatBuddiesTable->setItem(rowCount, 0, nameItem);

	QTableWidgetItem * msgItem = new QTableWidgetItem(QString::number(0));
	ui_.chatBuddiesTable->setItem(rowCount, 1, msgItem);
	
	BuddyHolder * buddyWidget = new BuddyHolder(buddy);
	buddyWidget->setNameItem(nameItem);
	buddyWidget->setMsgItem(msgItem);	

	buddyWidgets_.insert(std::make_pair(buddy->getId(), buddyWidget));

	QObject::connect(
		ui_.chatBuddiesTable, SIGNAL(cellClicked(int, int)),
		this, SLOT(changeBuddySlot(int, int))
		);
}

void ChatWidget::changeBuddySlot(int row, int column) {

	QTableWidgetItem * clickedItem = ui_.chatBuddiesTable->item(row, 0);
	
	if (clickedItem == NULL) {
		return;
	}

	int buddyId = clickedItem->data(Qt::UserRole).toInt();

	switchConversation(buddyId);
}

void ChatWidget::enableWriting() {
	writingEnabled_ = true;
	ui_.writingBox->setReadOnly(false);
}

void ChatWidget::disableWriting() {
	writingEnabled_ = false;
	ui_.writingBox->setReadOnly(true);
}

void ChatWidget::switchConversation(int buddyId) {
	ChatBuddy * buddy = findBuddy(buddyId);

	if (buddy) {
		switchConversation(buddy);
	}
}

ChatBuddy * ChatWidget::getCurrentToBuddy() {
	return currentConversation_->getToBuddy();
}

void ChatWidget::clearBuddies() {
	for (std::map<int, ChatConversation *>::const_iterator it = conversations_.begin(); it != conversations_.end(); ++it) {
		ChatConversation * conversation = it->second;
		delete conversation;
	}

	conversations_.clear();

	for (std::map<int, BuddyHolder *>::const_iterator it = buddyWidgets_.begin(); it != buddyWidgets_.end(); ++it) {
		BuddyHolder * holder = it->second;
		delete holder;
	}

	buddyWidgets_.clear();

	int rowCount = ui_.chatBuddiesTable->rowCount();

	for (int i = rowCount; i >= 0; --i) {
		ui_.chatBuddiesTable->removeRow(i);
	}

	for (std::map<int, ChatBuddy *>::const_iterator it = buddies_.begin(); it != buddies_.end(); ++it) {
		ChatBuddy * buddy = it->second;
		delete buddy;
	}

	buddies_.clear();
}

void ChatWidget::setBuddies(std::vector<ChatBuddy *> buddies) {

	for (std::vector<ChatBuddy *>::const_iterator it = buddies.begin(); it != buddies.end(); ++it) {
		addBuddy(*it);
	}
}

ChatConversation::ChatConversation(ChatBuddy * toBuddy) {
	this->toBuddy_ = toBuddy;
	this->messagesSend_ = 0;
	this->unreadMessagesCount_ = 0;
}
	
ChatConversation::~ChatConversation() {
	// Do not delete buddy, it is shared
}

int ChatConversation::getToBuddyId() {
	return toBuddy_->getId();
}

ChatBuddy * ChatConversation::getToBuddy() {
	return toBuddy_;
}

void ChatConversation::receiveMessage(ChatMessage * message) {
	receivedMessages_.push_back(message);
	messages_.push_back(message);
}

void ChatConversation::receiveUnreadMessage(ChatMessage * message) {
	receivedMessages_.push_back(message);
	messages_.push_back(message);
	unreadMessagesCount_++;
}

void ChatConversation::saveInput(const std::string * input) {
	this->savedInput_ = std::string(input->c_str());
}

std::string ChatConversation::loadInput() {
	return savedInput_;
}

ChatMessage * ChatConversation::sendMessage(const std::string * message) {
	ChatMessage * chatMessage = ChatMessage::createOutgoingMessage(toBuddy_->getId(), std::string(message->c_str()));
	sendedMessages_.push_back(chatMessage);
	messages_.push_back(chatMessage);
	messagesSend_++;
	return chatMessage;
}

std::vector<ChatMessage *> & ChatConversation::getReceivedMessages() {
	return receivedMessages_;
}

std::vector<ChatMessage *> & ChatConversation::getMessages() {
	return messages_;
}

int ChatConversation::getUnreadMessagesCount() {
	return unreadMessagesCount_;
}

void ChatConversation::readAllMessages() {
	unreadMessagesCount_ = 0;
}

void ChatWidget::clearState() {
	clearConversationInput();
	clearConversationOutput();
	clearBuddies();

	currentConversation_ = NULL;
	conversations_.clear();
}