/*

Facebook plugin for Miranda Instant Messenger
_____________________________________________

Copyright � 2009-11 Michal Zelinka, 2011-15 Robert P�sel

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, see <http://www.gnu.org/licenses/>.

*/

#include "stdafx.h"

int facebook_json_parser::parse_buddy_list(std::string *data, List::List< facebook_user >* buddy_list)
{
	facebook_user* current = NULL;
	std::string jsonData = data->substr(9);

	JSONROOT root(jsonData.c_str());
	if (root == NULL)
		return EXIT_FAILURE;

	JSONNODE *payload = json_get(root, "payload");
	if (payload == NULL) {
		return EXIT_FAILURE;
	}

	JSONNODE *list = json_get(payload, "buddy_list");
	if (list == NULL) {
		return EXIT_FAILURE;
	}

	// Set all contacts in map to offline
	for (List::Item< facebook_user >* i = buddy_list->begin(); i != NULL; i = i->next) {
		i->data->status_id = ID_STATUS_OFFLINE;
	}

	// Load last active times
	JSONNODE *lastActive = json_get(list, "last_active_times");
	if (lastActive != NULL) {
		for (unsigned int i = 0; i < json_size(lastActive); i++) {
			JSONNODE *it = json_at(lastActive, i);
			const char *id = json_name(it);

			current = buddy_list->find(id);
			if (current == NULL) {
				buddy_list->insert(std::make_pair(id, new facebook_user()));
				current = buddy_list->find(id);
				current->user_id = id;
			}

			current->last_active = json_as_int(it);
		}
	}

	// Find mobile friends
	JSONNODE *mobileFriends = json_get(list, "mobile_friends");
	if (mobileFriends != NULL) {
		for (unsigned int i = 0; i < json_size(mobileFriends); i++) {
			JSONNODE *it = json_at(mobileFriends, i);
			std::string id = json_as_pstring(it);

			current = buddy_list->find(id);
			if (current == NULL) {
				buddy_list->insert(std::make_pair(id, new facebook_user()));
				current = buddy_list->find(id);
				current->user_id = id;
			}

			current->status_id = ID_STATUS_OFFLINE;
			current->client = CLIENT_MOBILE;
		}
	}

	// Find now available contacts
	JSONNODE *nowAvailable = json_get(list, "nowAvailableList");
	if (nowAvailable != NULL) {
		for (unsigned int i = 0; i < json_size(nowAvailable); i++) {
			JSONNODE *it = json_at(nowAvailable, i);
			const char *id = json_name(it);

			current = buddy_list->find(id);
			if (current == NULL) {
				buddy_list->insert(std::make_pair(id, new facebook_user()));
				current = buddy_list->find(id);
				current->user_id = id;
			}

			current->status_id = ID_STATUS_ONLINE;

			JSONNODE *p = json_get(it, "p");
			if (p != NULL) {
				JSONNODE *status = json_get(p, "status"); // this seems to be "active" everytime
				JSONNODE *webStatus = json_get(p, "webStatus"); // "active", "idle" or "offline"
				JSONNODE *fbAppStatus = json_get(p, "fbAppStatus"); // "offline" or "active"
				JSONNODE *messengerStatus = json_get(p, "messengerStatus"); // "offline" or "active"
				JSONNODE *otherStatus = json_get(p, "otherStatus"); // "offline" or "active" - this seems to be "active" when webStatus is "idle" or "active" only

				// this may never happen
				if (json_as_pstring(status) != "active")
					current->status_id = ID_STATUS_OFFLINE;

				bool b;

				// "webStatus" and "otherStatus" are marked as "WEB" on FB website
				if ((b = (json_as_pstring(webStatus) == "active")) || json_as_pstring(otherStatus) == "active") {
					current->status_id = ID_STATUS_ONLINE;
					current->client = b ? CLIENT_WEB : CLIENT_OTHER;
				}

				// "fbAppStatus" and "messengerStatus" are marked as "MOBILE" on FB website
				if ((b = (json_as_pstring(fbAppStatus) == "active")) || json_as_pstring(messengerStatus) == "active") {
					current->status_id = ID_STATUS_ONTHEPHONE;
					current->client = b ? CLIENT_APP : CLIENT_MESSENGER;
				}

				// this is not marked anyhow on website (yet?)
				current->idle = json_as_pstring(webStatus) == "idle"
					|| json_as_pstring(otherStatus) == "idle"
					|| json_as_pstring(fbAppStatus) == "idle"
					|| json_as_pstring(messengerStatus) == "idle";
			}
		}
	}

	// Get aditional informations about contacts (if available)
	JSONNODE *userInfos = json_get(list, "userInfos");
	if (userInfos != NULL) {
		for (unsigned int i = 0; i < json_size(userInfos); i++) {
			JSONNODE *it = json_at(userInfos, i);
			const char *id = json_name(it);

			current = buddy_list->find(id);
			if (current == NULL)
				continue;

			JSONNODE *name = json_get(it, "name");
			JSONNODE *thumbSrc = json_get(it, "thumbSrc");

			if (name != NULL)
				current->real_name = utils::text::slashu_to_utf8(json_as_pstring(name));
			if (thumbSrc != NULL)
				current->image_url = utils::text::slashu_to_utf8(json_as_pstring(thumbSrc));
		}
	}

	return EXIT_SUCCESS;
}

void parseUser(JSONNODE *it, facebook_user *fbu)
{
	fbu->user_id = json_name(it);

	JSONNODE *id = json_get(it, "id");
	if (id == NULL || json_as_pstring(id) == "0" || json_as_pstring(id).empty()) {
		// this user has deleted account or is just unavailable for us (e.g., ignore list) -> don't read dummy name and avatar and rather ignore that completely
		return;
	}

	JSONNODE *name = json_get(it, "name");
	JSONNODE *thumbSrc = json_get(it, "thumbSrc");
	JSONNODE *gender = json_get(it, "gender");
	JSONNODE *vanity = json_get(it, "vanity"); // username
	//JSONNODE *uri = json_get(it, "uri"); // profile url
	//JSONNODE *is_friend = json_get(it, "is_friend"); // e.g. "True"
	//JSONNODE *type = json_get(it, "type"); // e.g. "friend" (classic contact) or "user" (disabled/deleted account)


	if (name)
		fbu->real_name = utils::text::slashu_to_utf8(json_as_pstring(name));
	if (thumbSrc)
		fbu->image_url = utils::text::slashu_to_utf8(json_as_pstring(thumbSrc));
	if (vanity)
		fbu->username = utils::text::slashu_to_utf8(json_as_pstring(vanity));

	if (gender)
		switch (json_as_int(gender)) {
		case 1: // female
			fbu->gender = 70;
			break;
		case 2: // male
			fbu->gender = 77;
			break;
			// case 7: not available female?
	}
}

int facebook_json_parser::parse_friends(std::string *data, std::map< std::string, facebook_user* >* friends)
{
	std::string jsonData = data->substr(9);

	JSONROOT root(jsonData.c_str());
	if (root == NULL)
		return EXIT_FAILURE;

	JSONNODE *payload = json_get(root, "payload");
	if (payload == NULL) {
		return EXIT_FAILURE;
	}

	for (unsigned int i = 0; i < json_size(payload); i++) {
		JSONNODE *it = json_at(payload, i);

		facebook_user *fbu = new facebook_user();
		parseUser(it, fbu);

		friends->insert(std::make_pair(fbu->user_id, fbu));
	}

	return EXIT_SUCCESS;
}


int facebook_json_parser::parse_notifications(std::string *data, std::map< std::string, facebook_notification* > *notifications)
{
	std::string jsonData = data->substr(9);

	JSONROOT root(jsonData.c_str());
	if (root == NULL)
		return EXIT_FAILURE;

	JSONNODE *payload = json_get(root, "payload");
	if (payload == NULL) {
		return EXIT_FAILURE;
	}

	JSONNODE *list = json_get(payload, "notifications");
	if (list == NULL) {
		return EXIT_FAILURE;
	}

	// Create notifications chatroom (if it doesn't exists), because we will be writing to it new notifications here
	proto->PrepareNotificationsChatRoom();

	for (unsigned int i = 0; i < json_size(list); i++) {
		JSONNODE *it = json_at(list, i);
		const char *id = json_name(it);

		JSONNODE *markup = json_get(it, "markup");
		JSONNODE *unread = json_get(it, "unread");
		JSONNODE *time = json_get(it, "time");

		// Ignore empty and old notifications
		if (markup == NULL || unread == NULL || time == NULL || json_as_int(unread) == 0)
			continue;

		std::string text = utils::text::html_entities_decode(utils::text::slashu_to_utf8(json_as_pstring(markup)));

		facebook_notification* notification = new facebook_notification();

		notification->id = id;
		notification->link = utils::text::source_get_value(&text, 3, "<a ", "href=\"", "\"");
		notification->text = utils::text::remove_html(utils::text::source_get_value(&text, 1, "<abbr"));
		notification->time = utils::time::from_string(json_as_pstring(time));

		// Write notification to chatroom
		proto->UpdateNotificationsChatRoom(notification);

		// If it's unseen, remember it, otherwise forget it
		if (notifications->find(notification->id) == notifications->end())
			notifications->insert(std::make_pair(notification->id, notification));
		else
			delete notification;
	}

	return EXIT_SUCCESS;
}

bool ignore_duplicits(FacebookProto *proto, const std::string &mid, const std::string &)
{
	ScopedLock s(proto->facy.send_message_lock_);

	std::map<std::string, int>::iterator it = proto->facy.messages_ignore.find(mid);
	if (it != proto->facy.messages_ignore.end()) {
		proto->debugLogA("??? Ignoring duplicit/sent message ID: %s", mid.c_str());
		it->second++; // increase counter (for deleting it later)
		return true;
	}

	// remember this id to ignore duplicits
	proto->facy.messages_ignore.insert(std::make_pair(mid, 1));
	return false;
}

void parseAttachments(FacebookProto *proto, std::string *message_text, JSONNODE *it, const std::string &thread_id, std::string other_user_fbid)
{
	// Process attachements and stickers
	JSONNODE *has_attachment = json_get(it, "has_attachment");
	if (has_attachment != NULL && json_as_bool(has_attachment)) {
		// Append attachements
		std::string type;
		std::string attachments_text;
		JSONNODE *attachments = json_get(it, "attachments");
		for (unsigned int j = 0; j < json_size(attachments); j++) {
			JSONNODE *itAttachment = json_at(attachments, j);
			JSONNODE *attach_type = json_get(itAttachment, "attach_type"); // "sticker", "photo", "file"
			if (attach_type != NULL) {
				// Get attachment type - "file" has priority over other types
				if (type.empty() || type != "file")
					type = json_as_pstring(attach_type);
			}
			JSONNODE *name = json_get(itAttachment, "name");
			JSONNODE *url = json_get(itAttachment, "url");
			if (url != NULL) {
				std::string link = json_as_pstring(url);

				if (link.find("/ajax/mercury/attachments/photo/view/") != std::string::npos)
					link = utils::url::decode(utils::text::source_get_value(&link, 2, "?uri=", "&")); // fix photo url
				else if (link.find("/") == 0)
					link = HTTP_PROTO_SECURE FACEBOOK_SERVER_REGULAR + link; // make absolute url

				if (!link.empty()) {
					std::string filename;
					if (name != NULL)
						filename = json_as_pstring(name);
					if (filename == "null")
						filename.clear();

					attachments_text += "\n" + (!filename.empty() ? "<" + filename + "> " : "") + link + "\n";

					// Stickers as smileys
					if (type == "sticker" && proto->getByte(FACEBOOK_KEY_CUSTOM_SMILEYS, DEFAULT_CUSTOM_SMILEYS)) {
						JSONNODE *metadata = json_get(itAttachment, "metadata");
						if (metadata != NULL) {
							JSONNODE *stickerId_ = json_get(metadata, "stickerID");
							if (stickerId_ != NULL) {
								std::string sticker = "[[sticker:" + json_as_pstring(stickerId_) + "]]";
								attachments_text += sticker;

								if (other_user_fbid.empty() && !thread_id.empty()) {
									other_user_fbid = proto->ThreadIDToContactID(thread_id);
								}

								// FIXME: rewrite smileyadd to use custom smileys per protocol and not per contact and then remove this ugliness
								if (!other_user_fbid.empty()) {
									MCONTACT hContact = proto->ContactIDToHContact(other_user_fbid);
									proto->StickerAsSmiley(sticker, link, hContact);
								}
							}
						}
					}
				}
			}
		}

		// TODO: have this as extra event, not replace or append message content
		if (!message_text->empty())
			*message_text += "\n\n";

		if (!attachments_text.empty()) {
			// we can't use this as offline messages doesn't have it
			/* JSONNODE *admin_snippet = json_get(it, "admin_snippet");
			if (admin_snippet != NULL) {
			*message_text += json_as_pstring(admin_snippet);
			} */

			std::tstring newText;
			if (type == "sticker")
				newText = TranslateT("a sticker");
			else if (type == "file")
				newText = (json_size(attachments) > 1) ? TranslateT("files") : TranslateT("a file");
			else if (type == "photo")
				newText = (json_size(attachments) > 1) ? TranslateT("photos") : TranslateT("a photo");
			else
				newText = _A2T(type.c_str());

			TCHAR title[200];
			mir_sntprintf(title, SIZEOF(title), TranslateT("User sent %s:"), newText.c_str());

			*message_text += ptrA(mir_utf8encodeT(title));
			*message_text += attachments_text;
		}
		else {
			// TODO: better support for these attachments (parse it from "m_messaging" instead of "messaging"
			*message_text += ptrA(mir_utf8encodeT(TranslateT("User sent an unsupported attachment. Open your browser to see it.")));
		}
	}
}

int facebook_json_parser::parse_messages(std::string *data, std::vector< facebook_message* >* messages, std::map< std::string, facebook_notification* >* notifications, bool inboxOnly)
{
	// remove old received messages from map		
	for (std::map<std::string, int>::iterator it = proto->facy.messages_ignore.begin(); it != proto->facy.messages_ignore.end();) {
		if (it->second > FACEBOOK_IGNORE_COUNTER_LIMIT) {
			it = proto->facy.messages_ignore.erase(it);
		}
		else {
			it->second++; // increase counter on each request
			++it;
		}
	}

	std::string jsonData = data->substr(9);

	JSONROOT root(jsonData.c_str());
	if (root == NULL)
		return EXIT_FAILURE;

	JSONNODE *ms = json_get(root, "ms");
	if (ms == NULL) {
		return EXIT_FAILURE;
	}

	for (unsigned int i = 0; i < json_size(ms); i++) {
		JSONNODE *it = json_at(ms, i);

		JSONNODE *type = json_get(it, "type");
		if (type == NULL)
			continue;

		std::string t = json_as_pstring(type);
		if (t == "messaging") {
			// various messaging stuff (received and sent messages, getting seen info)

			JSONNODE *type = json_get(it, "event");
			if (type == NULL)
				continue;

			std::string t = json_as_pstring(type);

			if (t == "read_receipt") {
				// user read message
				JSONNODE *reader_ = json_get(it, "reader");
				JSONNODE *time_ = json_get(it, "time");

				if (reader_ == NULL || time_ == NULL)
					continue;

				time_t timestamp = utils::time::from_string(json_as_pstring(time_));
				MCONTACT hContact = NULL;
				std::tstring reader;

				std::string readerId = json_as_pstring(reader_);
				JSONNODE *threadid = json_get(it, "tid");
				if (threadid == NULL) {
					// classic contact
					hContact = proto->ContactIDToHContact(readerId);
				} else {
					// multi user chat
					if (!proto->m_enableChat)
						continue;

					std::tstring tid = ptrT(json_as_string(threadid));

					std::map<std::tstring, facebook_chatroom*>::iterator it = proto->facy.chat_rooms.find(tid);
					if (it != proto->facy.chat_rooms.end()) {
						facebook_chatroom *chatroom = it->second;
						std::map<std::string, std::string> participants = chatroom->participants;

						std::map<std::string, std::string>::const_iterator participant = participants.find(readerId);
						if (participant == participants.end()) {
							// TODO: load name of this participant
							std::string name = readerId;
							participants.insert(std::make_pair(readerId, name));
							proto->AddChatContact(tid.c_str(), readerId.c_str(), name.c_str());
						}

						participant = participants.find(readerId);
						if (participant != participants.end()) {
							// TODO: remember just reader ids to avoid eventual duplication of names
							reader = _A2T(participant->second.c_str(), CP_UTF8);
							hContact = proto->ChatIDToHContact(tid);
						}
					}
				}

				if (hContact)
					proto->facy.insert_reader(hContact, timestamp, reader);
			}
			else if (t == "deliver") {
				// inbox message (multiuser or direct)

				JSONNODE *msg = json_get(it, "message");
				JSONNODE *folder = json_get(it, "folder");

				if (inboxOnly && json_as_pstring(folder) != "inbox")
					continue;

				JSONNODE *sender_fbid = json_get(msg, "sender_fbid");
				JSONNODE *sender_name = json_get(msg, "sender_name");
				JSONNODE *body = json_get(msg, "body");

				// looks like there is either "tid" or "other_user_fbid" - or "tid" is removed forever?
				JSONNODE *tid = json_get(msg, "tid");
				JSONNODE *other_user_id_ = json_get(msg, "other_user_fbid");

				JSONNODE *mid = json_get(msg, "mid");
				JSONNODE *timestamp = json_get(msg, "timestamp");
				JSONNODE *filtered = json_get(it, "is_filtered_content");

				if (sender_fbid == NULL || sender_name == NULL || body == NULL || mid == NULL || timestamp == NULL)
					continue;

				std::string id = json_as_pstring(sender_fbid);
				std::string message_id = json_as_pstring(mid);
				std::string message_text = json_as_pstring(body);

				std::string thread_id = (tid != NULL ? json_as_pstring(tid) : "");
				std::string other_user_id = (other_user_id_ != NULL ? json_as_pstring(other_user_id_) : "");

				// Process attachements and stickers
				parseAttachments(proto, &message_text, msg, thread_id, other_user_id);

				// Ignore duplicits or messages sent from miranda
				if (body == NULL || ignore_duplicits(proto, message_id, message_text))
					continue;

				if (json_as_bool(filtered) && message_text.empty())
					message_text = Translate("This message is no longer available, because it was marked as abusive or spam.");

				message_text = utils::text::trim(utils::text::slashu_to_utf8(message_text), true);
				if (message_text.empty())
					continue;

				facebook_message* message = new facebook_message();
				message->isUnread = true;
				message->isIncoming = (id != proto->facy.self_.user_id);
				message->message_text = message_text;
				message->time = utils::time::from_string(json_as_pstring(timestamp));
				message->user_id = id;
				message->message_id = message_id;
				message->sender_name = utils::text::slashu_to_utf8(json_as_pstring(sender_name)); // TODO: or if not incomming use my own name from facy.self_ ?
				message->thread_id = json_as_pstring(tid); // TODO: or if not incomming use my own id from facy.self_ ?

				JSONNODE *gthreadinfo = json_get(msg, "group_thread_info");
				message->isChat = (gthreadinfo != NULL && json_as_pstring(gthreadinfo) != "null");

				if (!message->isChat && !message->isIncoming) {
					message->sender_name.clear();
					message->user_id = !other_user_id.empty() ? other_user_id : proto->ThreadIDToContactID(message->thread_id); // TODO: Check if we have contact with this user_id in friendlist and otherwise do something different?
				}

				messages->push_back(message);
			}
		}
		else if (t == "notification_json") {
			// event notification
			JSONNODE *nodes = json_get(it, "nodes");

			// Create notifications chatroom (if it doesn't exists), because we will be writing to it new notifications here
			proto->PrepareNotificationsChatRoom();

			for (unsigned int j = 0; j < json_size(nodes); j++) {
				JSONNODE *itNodes = json_at(nodes, j);

				//JSONNODE *text = json_get(itNodes, "title/text"); // use this when popups will be ready to allow changes of their content
				JSONNODE *text_ = json_get(itNodes, "unaggregatedTitle"); // notifications one by one, not grouped
				if (text_ == NULL)
					continue;
				JSONNODE *text = json_get(text_, "text");
				JSONNODE *url = json_get(itNodes, "url");
				JSONNODE *alert_id = json_get(itNodes, "alert_id");

				JSONNODE *time_ = json_get(itNodes, "timestamp");
				if (time_ == NULL)
					continue;
				JSONNODE *time = json_get(time_, "time");
				if (time == NULL || text == NULL || url == NULL || alert_id == NULL)
					continue;

				time_t timestamp = utils::time::from_string(json_as_pstring(time));
				if (timestamp > proto->facy.last_notification_time_) {
					// Only new notifications
					proto->facy.last_notification_time_ = timestamp;

					facebook_notification* notification = new facebook_notification();
					notification->text = utils::text::slashu_to_utf8(json_as_pstring(text));
					notification->link = json_as_pstring(url);
					notification->id = json_as_pstring(alert_id);
					notification->time = timestamp;

					std::string::size_type pos = notification->id.find(":");
					if (pos != std::string::npos)
						notification->id = notification->id.substr(pos + 1);

					// Write notification to chatroom
					proto->UpdateNotificationsChatRoom(notification);

					// If it's unseen, remember it, otherwise forget it (here it will always be unseen)
					if (notifications->find(notification->id) == notifications->end() && !notification->seen)
						notifications->insert(std::make_pair(notification->id, notification));
					else
						delete notification;
				}
			}
		}
		else if (t == "m_notification") {
			JSONNODE *data = json_get(it, "data");
			if (data == NULL)
				continue;

			JSONNODE *appId_ = json_get(data, "app_id");
			JSONNODE *type_ = json_get(data, "type");

			if ((appId_ && json_as_pstring(appId_) == "2356318349") || (type_ && json_as_pstring(type_) == "friend_confirmed")) {
				// Friendship notifications

				JSONNODE *body_ = json_get(data, "body");
				JSONNODE *html_ = (body_ ? json_get(body_, "__html") : NULL);

				JSONNODE *href_ = json_get(data, "href");
				JSONNODE *unread_ = json_get(data, "unread");
				JSONNODE *alertId_ = json_get(data, "alert_id");

				if (html_ == NULL || href_ == NULL || unread_ == NULL || json_as_int(unread_) == 0)
					continue;
				
				std::string text = utils::text::remove_html(utils::text::slashu_to_utf8(json_as_pstring(html_)));
				std::string url = json_as_pstring(href_);
				std::string alert_id = (alertId_ ? json_as_pstring(alertId_) : "");

				proto->NotifyEvent(proto->m_tszUserName, ptrT(mir_utf8decodeT(text.c_str())), NULL, FACEBOOK_EVENT_FRIENDSHIP, &url, alert_id.empty() ? NULL : &alert_id);
			}
		}
		else if (t == "jewel_requests_add") {
			// New friendship request, load them all with real names (because there is only user_id in "from" field)
			proto->ForkThread(&FacebookProto::ProcessFriendRequests, NULL);
		}
		else if (t == "typ") {
			// chat typing notification

			JSONNODE *from = json_get(it, "from");
			if (from == NULL)
				continue;

			facebook_user fbu;
			fbu.user_id = json_as_pstring(from);

			MCONTACT hContact = proto->AddToContactList(&fbu, CONTACT_FRIEND); // only friends are able to send typing notifications

			JSONNODE *st = json_get(it, "st");
			if (json_as_int(st) == 1)
				proto->StartTyping(hContact);
			else
				proto->StopTyping(hContact);
		}
		else if (t == "ttyp") {
			// multi chat typing notification
			if (!proto->m_enableChat)
				continue;

			JSONNODE *from_ = json_get(it, "from");
			JSONNODE *thread_ = json_get(it, "thread");
			JSONNODE *st_ = json_get(it, "st");

			if (from_ == NULL || thread_ == NULL || st_ == NULL)
				continue;

			std::tstring tid = ptrT(json_as_string(thread_));
			std::string from_id = json_as_pstring(from_);

			std::map<std::tstring, facebook_chatroom*>::iterator it = proto->facy.chat_rooms.find(tid);
			if (it != proto->facy.chat_rooms.end()) {
				facebook_chatroom *chatroom = it->second;
				std::map<std::string, std::string> participants = chatroom->participants;

				std::map<std::string, std::string>::const_iterator participant = participants.find(from_id);
				if (participant == participants.end()) {
					// TODO: load name of this participant
					std::string name = from_id;
					proto->AddChatContact(tid.c_str(), from_id.c_str(), name.c_str());
				}

				participant = participants.find(from_id);
				if (participant != participants.end()) {
					MCONTACT hChatContact = proto->ChatIDToHContact(tid);
					ptrT name(mir_utf8decodeT(participant->second.c_str()));

					if (json_as_int(st_) == 1) {
						StatusTextData st = { 0 };
						st.cbSize = sizeof(st);

						mir_sntprintf(st.tszText, SIZEOF(st.tszText), TranslateT("%s is typing a message..."), name);

						CallService(MS_MSG_SETSTATUSTEXT, (WPARAM)hChatContact, (LPARAM)&st);
					}
					else {
						CallService(MS_MSG_SETSTATUSTEXT, (WPARAM)hChatContact, NULL);
					}

					// TODO: support proper MS_PROTO_CONTACTISTYPING service for chatrooms (when it will be implemented)
				}
			}
		}
		else if (t == "privacy_changed") {
			// settings changed

			JSONNODE *event_type = json_get(it, "event");
			JSONNODE *event_data = json_get(it, "data");

			if (event_type == NULL || event_data == NULL)
				continue;

			std::string t = json_as_pstring(event_type);
			if (t == "visibility_update") {
				// change of chat status
				JSONNODE *visibility = json_get(event_data, "visibility");

				bool isVisible = (visibility != NULL) && json_as_bool(visibility);
				proto->debugLogA("    Requested chat switch to %s", isVisible ? "Online" : "Offline");
				proto->SetStatus(isVisible ? ID_STATUS_ONLINE : ID_STATUS_INVISIBLE);
			}
		}
		else if (t == "buddylist_overlay") {
			// we opened/closed chat window - pretty useless info for us
			continue;
		} else if (t == "ticker_update:home") {
			JSONNODE *actor_ = json_get(it, "actor");
			JSONNODE *story_ = json_get(it, "story_xhp");

			std::string text = json_as_pstring(story_);
			text = utils::text::html_entities_decode(utils::text::slashu_to_utf8(text));

			std::string url = utils::text::source_get_value(&text, 3, "\"tickerStoryLink\"", "href=\"", "\"");
			std::string story_type = utils::text::source_get_value2(&text, "\"type\":\"", "\"");
			std::string story_class = utils::text::source_get_value2(&text, "\"entstory_class\":\"", "\"");

			text = utils::text::trim(utils::text::remove_html(text));

			std::string userId = (actor_ != NULL ? json_as_pstring(actor_) : "");

			MCONTACT hContact = proto->ContactIDToHContact(userId);

			proto->debugLogA("+++ Got ticker type='%s' class='%s'", story_type.c_str(), story_class.c_str());

			if (!text.empty())
				proto->NotifyEvent(proto->m_tszUserName, ptrT(mir_utf8decodeT(text.c_str())), hContact, FACEBOOK_EVENT_TICKER, &url);
		}
		else if (t == "mercury") {
			// rename multi user chat, video call, ...

			JSONNODE *actions_ = json_get(it, "actions");
			if (actions_ == NULL)
				continue;

			for (unsigned int i = 0; i < json_size(actions_); i++) {
				JSONNODE *action_ = json_at(actions_, i);

				JSONNODE *thread_id_ = json_get(action_, "thread_id");
				JSONNODE *log_body_ = json_get(action_, "log_message_body");
				JSONNODE *log_data_ = json_get(action_, "log_message_data");
				JSONNODE *log_type_ = json_get(action_, "log_message_type");
				if (!log_data_ || !log_body_ || !thread_id_ || !log_type_)
					continue;

				std::tstring thread_id = ptrT(json_as_string(thread_id_));
				std::string type = json_as_pstring(log_type_);
				std::string message_text = json_as_pstring(log_body_);

				if (type == "log:video-call") {
					JSONNODE *other_user_fbid_ = json_get(action_, "other_user_fbid");
					std::string id = json_as_pstring(other_user_fbid_);

					JSONNODE *timestamp = json_get(action_, "timestamp");
					JSONNODE *mid_ = json_get(action_, "message_id");

					std::string message_id = json_as_pstring(mid_);

					facebook_message* message = new facebook_message();
					message->isChat = false;
					message->isUnread = true;
					message->isIncoming = (id != proto->facy.self_.user_id);
					message->message_text = message_text;
					message->time = utils::time::from_string(json_as_pstring(timestamp));
					message->user_id = id;
					message->message_id = message_id;
					message->sender_name.clear();
					message->thread_id = json_as_pstring(thread_id_);
					message->type = CALL;

					messages->push_back(message);
				}
				else {
					// TODO: check for other types, now we expect this is rename chat

					if (!proto->m_enableChat)
						continue;

					JSONNODE *name_ = json_get(log_data_, "name");
					std::string name = json_as_pstring(name_);

					// proto->RenameChat(thread_id.c_str(), name.c_str()); // this don't work, why?
					proto->setStringUtf(proto->ChatIDToHContact(thread_id), FACEBOOK_KEY_NICK, name.c_str());

					proto->UpdateChat(thread_id.c_str(), NULL, NULL, message_text.c_str());
				}
			}
		}
		else if (t == "notifications_read" || t == "notifications_seen") {
			ScopedLock s(proto->facy.notifications_lock_);

			JSONNODE *alerts = json_get(it, "alert_ids");

			for (unsigned int j = 0; j < json_size(alerts); j++) {
				JSONNODE *itAlerts = json_at(alerts, j);
				std::string id = json_as_pstring(itAlerts);

				std::map<std::string, facebook_notification*>::iterator it = notifications->find(id);
				if (it != notifications->end()) {
					if (it->second->hWndPopup != NULL)
						PUDeletePopup(it->second->hWndPopup); // close popup

					delete it->second;
					notifications->erase(it);
				}
			}
		}
		else
			continue;
	}

	return EXIT_SUCCESS;
}

int facebook_json_parser::parse_unread_threads(std::string *data, std::vector< std::string >* threads, bool inboxOnly)
{
	std::string jsonData = data->substr(9);

	JSONROOT root(jsonData.c_str());
	if (root == NULL)
		return EXIT_FAILURE;

	JSONNODE *payload = json_get(root, "payload");
	if (payload == NULL) {
		return EXIT_FAILURE;
	}

	JSONNODE *unread_threads = json_get(payload, "unread_thread_ids");
	if (unread_threads == NULL) {
		return EXIT_FAILURE;
	}

	for (unsigned int i = 0; i < json_size(unread_threads); i++) {
		JSONNODE *it = json_at(unread_threads, i);

		JSONNODE *folder = json_get(it, "folder");
		JSONNODE *thread_ids = json_get(it, "thread_ids");

		if (inboxOnly && json_as_pstring(folder) != "inbox")
			continue;

		for (unsigned int j = 0; j < json_size(thread_ids); j++) {
			JSONNODE *id = json_at(thread_ids, j);
			threads->push_back(json_as_pstring(id));
		}
	}

	return EXIT_SUCCESS;
}

int facebook_json_parser::parse_thread_messages(std::string *data, std::vector< facebook_message* >* messages, std::map< std::string, facebook_chatroom* >* chatrooms, bool unreadOnly, bool inboxOnly)
{
	std::string jsonData = data->substr(9);

	JSONROOT root(jsonData.c_str());
	if (root == NULL)
		return EXIT_FAILURE;

	JSONNODE *payload = json_get(root, "payload");
	if (payload == NULL) {
		return EXIT_FAILURE;
	}

	JSONNODE *actions = json_get(payload, "actions");
	JSONNODE *threads = json_get(payload, "threads");
	if (actions == NULL || threads == NULL) {
		return EXIT_FAILURE;
	}

	JSONNODE *roger = json_get(payload, "roger");
	if (roger != NULL) {
		for (unsigned int i = 0; i < json_size(roger); i++) {
			JSONNODE *it = json_at(roger, i);
			std::tstring id = _A2T(json_name(it));

			// Ignore "wrong" (duplicit) identifiers - these that doesn't begin with "id."
			if (id.substr(0, 3) == _T("id.")) {
				facebook_chatroom *room = new facebook_chatroom(id);
				chatrooms->insert(std::make_pair((char*)_T2A(room->thread_id.c_str()), room));
			}
		}
	}

	std::map<std::string, std::string> thread_ids;
	for (unsigned int i = 0; i < json_size(threads); i++) {
		JSONNODE *it = json_at(threads, i);
		JSONNODE *is_canonical_user = json_get(it, "is_canonical_user");
		JSONNODE *canonical = json_get(it, "canonical_fbid");
		JSONNODE *thread_id = json_get(it, "thread_id");
		JSONNODE *name = json_get(it, "name");
		//JSONNODE *message_count = json_get(it, "message_count");
		//JSONNODE *unread_count = json_get(it, "unread_count"); // TODO: use it to check against number of loaded messages... but how?
		JSONNODE *folder = json_get(it, "folder");

		if (canonical == NULL || thread_id == NULL)
			continue;

		std::string id = json_as_pstring(canonical);
		std::string tid = json_as_pstring(thread_id);

		std::map<std::string, facebook_chatroom*>::iterator iter = chatrooms->find(tid);
		if (iter != chatrooms->end()) {
			if (json_as_bool(is_canonical_user)) {
				chatrooms->erase(iter); // this is not chatroom
			}
			else {
				iter->second->chat_name = ptrT(json_as_string(name)); // TODO: create name from users if there is no name...

				JSONNODE *participants = json_get(it, "participants");
				for (unsigned int j = 0; j < json_size(participants); j++) {
					JSONNODE *jt = json_at(it, j);
					std::string user_id = json_name(jt);
					iter->second->participants.insert(std::make_pair(user_id, user_id)); // TODO: get name somehow
				}
			}
		}

		iter = chatrooms->find(id);
		if (iter != chatrooms->end()) {
			chatrooms->erase(iter); // this is not chatroom
		}

		if (inboxOnly && json_as_pstring(folder) != "inbox")
			continue;

		if (id == "null")
			continue;

		thread_ids.insert(std::make_pair(tid, id));
	}

	for (unsigned int i = 0; i < json_size(actions); i++) {
		JSONNODE *it = json_at(actions, i);

		JSONNODE *author = json_get(it, "author");
		JSONNODE *author_email = json_get(it, "author_email");
		JSONNODE *body = json_get(it, "body");
		JSONNODE *tid = json_get(it, "thread_id");
		JSONNODE *mid = json_get(it, "message_id");
		JSONNODE *timestamp = json_get(it, "timestamp");
		JSONNODE *filtered = json_get(it, "is_filtered_content");
		JSONNODE *folder = json_get(it, "folder");
		JSONNODE *is_unread = json_get(it, "is_unread");

		if (author == NULL || body == NULL || mid == NULL || tid == NULL || timestamp == NULL)
			continue;

		if (inboxOnly && json_as_pstring(folder) != "inbox")
			continue;

		std::string thread_id = json_as_pstring(tid);
		std::string message_id = json_as_pstring(mid);
		std::string message_text = json_as_pstring(body);
		std::string author_id = json_as_pstring(author);
		std::string::size_type pos = author_id.find(":");
		if (pos != std::string::npos)
			author_id = author_id.substr(pos + 1);

		// Process attachements and stickers
		parseAttachments(proto, &message_text, it, thread_id, ""); // FIXME: is here supported other_user_fbid ?

		if (json_as_bool(filtered) && message_text.empty())
			message_text = Translate("This message is no longer available, because it was marked as abusive or spam.");

		message_text = utils::text::trim(utils::text::slashu_to_utf8(message_text), true);
		if (message_text.empty())
			continue;

		bool isUnread = (is_unread != NULL && json_as_bool(is_unread));

		// Ignore read messages if we want only unread messages
		if (unreadOnly && !isUnread)
			continue;

		facebook_message* message = new facebook_message();
		message->message_text = message_text;
		if (author_email != NULL)
			message->sender_name = json_as_pstring(author_email);
		message->time = utils::time::from_string(json_as_pstring(timestamp));
		message->thread_id = thread_id;
		message->message_id = message_id;
		message->isIncoming = (author_id != proto->facy.self_.user_id);
		message->isUnread = isUnread;

		std::map<std::string, facebook_chatroom*>::iterator iter = chatrooms->find(thread_id);
		if (iter != chatrooms->end()) {
			// this is chatroom message
			message->isChat = true;
			message->user_id = author_id;
		}
		else {
			// this is standard message
			message->isChat = false;
			std::map<std::string, std::string>::iterator iter = thread_ids.find(thread_id);
			if (iter != thread_ids.end()) {
				message->user_id = iter->second; // TODO: Check if we have contact with this ID in friendlist and otherwise do something different?
			} else {
				delete message;
				continue;
			}
		}

		messages->push_back(message);
	}

	return EXIT_SUCCESS;
}

int facebook_json_parser::parse_thread_info(std::string *data, std::string* user_id)
{
	std::string jsonData = data->substr(9);

	JSONROOT root(jsonData.c_str());
	if (root == NULL)
		return EXIT_FAILURE;

	JSONNODE *payload = json_get(root, "payload");
	if (payload == NULL) {
		return EXIT_FAILURE;
	}

	JSONNODE *threads = json_get(payload, "threads");
	if (threads == NULL) {
		return EXIT_FAILURE;
	}

	//std::map<std::string, std::string> thread_ids;
	for (unsigned int i = 0; i < json_size(threads); i++) {
		JSONNODE *it = json_at(threads, i);
		JSONNODE *canonical = json_get(it, "canonical_fbid");
		JSONNODE *thread_id = json_get(it, "thread_id");
		//JSONNODE *message_count = json_get(it, "message_count"); // TODO: this could be useful for loading history from server

		if (canonical == NULL || thread_id == NULL)
			continue;

		std::string id = json_as_pstring(canonical);
		if (id == "null")
			continue;

		*user_id = id;
	}

	return EXIT_SUCCESS;
}


int facebook_json_parser::parse_user_info(std::string *data, facebook_user* fbu)
{
	std::string jsonData = data->substr(9);

	JSONROOT root(jsonData.c_str());
	if (root == NULL)
		return EXIT_FAILURE;

	JSONNODE *payload = json_get(root, "payload");
	if (payload == NULL) {
		return EXIT_FAILURE;
	}

	JSONNODE *profiles = json_get(payload, "profiles");
	if (profiles == NULL) {
		return EXIT_FAILURE;
	}

	//std::map<std::string, std::string> user_ids;
	for (unsigned int i = 0; i < json_size(profiles); i++) {
		JSONNODE *it = json_at(profiles, i);

		// TODO: allow more users to parse at once
		std::string id = json_name(it);

		if (fbu->user_id == id) {
			parseUser(it, fbu);
			break;
		}
	}

	return EXIT_SUCCESS;
}

int facebook_json_parser::parse_chat_info(std::string *data, facebook_chatroom* fbc)
{
	std::string jsonData = data->substr(9);

	JSONROOT root(jsonData.c_str());
	if (root == NULL)
		return EXIT_FAILURE;

	JSONNODE *payload = json_get(root, "payload");
	if (payload == NULL) {
		return EXIT_FAILURE;
	}

	//JSONNODE *actions = json_get(payload, "actions");
	JSONNODE *threads = json_get(payload, "threads");
	if (/*actions == NULL || */threads == NULL) {
		return EXIT_FAILURE;
	}

	/*JSONNODE *roger = json_get(payload, "roger");
	if (roger != NULL) {
	for (unsigned int i = 0; i < json_size(roger); i++) {
	JSONNODE *it = json_at(roger, i);
	std::tstring id = _A2T(json_name(it));

	// Ignore "wrong" (duplicit) identifiers - these that doesn't begin with "id."
	if (id.substr(0, 3) == _T("id.")) {
	facebook_chatroom *room = new facebook_chatroom();
	room->thread_id = id;

	chatrooms->insert(std::make_pair((char*)_T2A(room->thread_id.c_str()), room));
	}
	}
	}*/

	//std::map<std::string, std::string> thread_ids;
	for (unsigned int i = 0; i < json_size(threads); i++) {
		JSONNODE *it = json_at(threads, i);

		JSONNODE *is_canonical_user_ = json_get(it, "is_canonical_user");
		JSONNODE *thread_id_ = json_get(it, "thread_id");
		JSONNODE *name_ = json_get(it, "name");
		//JSONNODE *message_count_ = json_get(it, "message_count");
		//JSONNODE *unread_count_ = json_get(it, "unread_count"); // TODO: use it to check against number of loaded messages... but how?

		if (thread_id_ == NULL || is_canonical_user_ == NULL || json_as_bool(is_canonical_user_))
			continue;

		std::tstring tid = ptrT(json_as_string(thread_id_));

		// TODO: allow more users to parse at once
		if (fbc->thread_id != tid) {
			continue;
		}

		JSONNODE *participants = json_get(it, "participants");
		for (unsigned int j = 0; j < json_size(participants); j++) {
			JSONNODE *jt = json_at(participants, j);
			std::string user_id = json_as_pstring(jt);
			fbc->participants.insert(std::make_pair(user_id.substr(5), ""));
		}

		fbc->chat_name = ptrT(json_as_string(name_));
	}

	return EXIT_SUCCESS;
}