/**
 * @file hippofloaterxml.cpp
 * @author Mana Janus
 *
 * $LicenseInfo:firstyear=2001&license=viewergpl$
 *
 * Copyright (c) 2001-2009, Linden Research, Inc.
 *
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.	Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
 *
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception
 *
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 *
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/LicenseInfo$
 */


#include "llviewerprecompiledheaders.h"

#include "hippofloaterxml.h"

#include <llchat.h>
#include <llcombobox.h>
#include <llfloater.h>
#include <llradiogroup.h>
#include <lluictrlfactory.h>

#include "llviewerwindow.h"


#define CHANNEL 427169570


// ********************************************************************
// floater implementation class

class HippoFloaterXmlImpl : public LLFloater
{
	public:
		HippoFloaterXmlImpl(const std::string &name, const std::string &xml);
		virtual ~HippoFloaterXmlImpl();

		BOOL postBuild();
		void onClose(bool quitting);

		static bool execute(LLUICtrl *ctrl,
							const std::string &cmds, std::string::size_type &offset,
							std::string &response);

	private:
		std::string mName;
		bool mIsNotifyOnClose;
		//Must retain handles to unregister notices.
		typedef boost::signals2::scoped_connection notice_connection_t;
		typedef boost::shared_ptr<notice_connection_t> notice_ptr_t;

		std::map<LLUICtrl*, notice_ptr_t > mNotices;
};


// ********************************************************************
// global floater data

typedef std::map<std::string, HippoFloaterXmlImpl*> FloaterMap;
static FloaterMap gInstances;


// ********************************************************************
// floater XML descriptions

class XmlData
{
	public:
		explicit XmlData(int numParts) :
			mData(numParts), mNumParts(numParts), mPartsReceived(0)
		{
		}

		int getNumParts() const { return mNumParts; }
		bool isComplete() const { return (mPartsReceived >= mNumParts); }

		void add(int index, const std::string &data)
		{
			--index;  // 1 <= index <= mNumParts
			if ((index >= 0) && (index < mNumParts)) {
				if (mData[index].empty()) mPartsReceived++;
				mData[index] = data;
			}
		}

		void get(std::string &xml) const
		{
			xml = "";
			for (int i=0; i<mNumParts; i++)
				xml += mData[i];
		}

		void reinit(int numParts)
		{
			mNumParts = numParts;
			mPartsReceived = 0;
			mData.reserve(numParts);
			for (int i=0; i<mNumParts; i++)
				mData[i].clear();
		}

		static XmlData *getInstance(const std::string &floaterName, int numParts);
		static void release(const std::string &floaterName);

	private:
		std::vector<std::string> mData;
		int mNumParts;
		int mPartsReceived;
};

typedef std::map<std::string, XmlData*> XmlDataMap;
static XmlDataMap gXmlData;

XmlData *XmlData::getInstance(const std::string &floaterName, int numParts)
{
	XmlDataMap::iterator it = gXmlData.find(floaterName);
	if (it == gXmlData.end()) {
		XmlData *data = new XmlData(numParts);
		gXmlData[floaterName] = data;
		return data;
	} else {
		XmlData *data = it->second;
		if (data->getNumParts() != numParts)
			data->reinit(numParts);
		return data;
	}
}

void XmlData::release(const std::string &floaterName)
{
	XmlDataMap::iterator it = gXmlData.find(floaterName);
	if (it != gXmlData.end()) {
		delete gXmlData[floaterName];
		gXmlData.erase(it);
	}
}


// ********************************************************************
// create HippoFloaterXmlImpl

HippoFloaterXmlImpl::HippoFloaterXmlImpl(const std::string &name, const std::string &xml) :
	mName(name), mIsNotifyOnClose(false)
{
	gInstances[mName] = this;
	LLUICtrlFactory::getInstance()->buildFloaterFromBuffer(this, xml);
}

HippoFloaterXmlImpl::~HippoFloaterXmlImpl()
{
	FloaterMap::iterator it = gInstances.find(mName);
	if (it != gInstances.end())
		gInstances.erase(it);
}

BOOL HippoFloaterXmlImpl::postBuild()
{
	LLRect rect = getRect();
	if ((rect.mLeft == 0) && (rect.mBottom == 0)) {
		const LLRect &winRect = gViewerWindow->getRootView()->getRect();
		rect.setCenterAndSize((winRect.getWidth()+1)>>1, (winRect.getHeight()+1)>>1,
							   rect.getWidth(), rect.getHeight());
		setRect(rect);
	}
	return true;
}


// ********************************************************************
// static function declarations

static bool cmdGetToken(const std::string &cmds, std::string::size_type &offset, std::string &token);

// defined in llchatbar.cpp, but not declared in any header
void send_chat_from_viewer(std::string utf8_out_text, EChatType type, S32 channel);


// ********************************************************************
// execute commands static

void HippoFloaterXml::execute(const std::string &cmds)
{
	std::string::size_type offset = 0;

	std::string floaterName;
	if (!cmdGetToken(cmds, offset, floaterName)) return;

	HippoFloaterXmlImpl *floater;
	FloaterMap::iterator it = gInstances.find(floaterName);
	if (it != gInstances.end())
		floater = it->second;
	else
		floater = 0;

	std::string token;
	while (cmdGetToken(cmds, offset, token)) {

		if (token == "{") {
			if (floater) {
				std::string response;
				if (!floater->execute(floater, cmds, offset, response))
					break;
				if (!response.empty())
					send_chat_from_viewer(response, CHAT_TYPE_WHISPER, CHANNEL);
			}
		} else

		if (token == "create") {
			if (floater) delete floater;
			std::string nStr, mStr;
			if (!cmdGetToken(cmds, offset, nStr)) break;
			if (!cmdGetToken(cmds, offset, mStr)) break;
			if (mStr != "/") break;
			if (!cmdGetToken(cmds, offset, mStr)) break;
			int n = strtol(nStr.c_str(), 0, 10);
			int m = strtol(mStr.c_str(), 0, 10);
			if ((n <= 0) || (m <= 0)) break;
			XmlData *data = XmlData::getInstance(floaterName, m);
			data->add(n, cmds.substr(offset+1));
			if (data->isComplete()) {
				std::string xml;
				data->get(xml);
				XmlData::release(floaterName);
				new HippoFloaterXmlImpl(floaterName, xml);
			}
			break;
		} else

		if (token == "show") {
			if (floater) floater->setVisible(true);
		} else

		if (token == "hide") {
			if (floater) floater->setVisible(false);
		} else

		if (token == "destroy") {
			if (floater) delete floater;
			floater = 0;
		}
	}
}


// ********************************************************************
// generic notification callbacks

static void notifyCallback(LLUICtrl *ctrl)
{
	std::string msg = "NOTIFY:";
	msg += ctrl->getName();
	msg += ':';
	msg += ctrl->getValue().asString();
	send_chat_from_viewer(msg, CHAT_TYPE_WHISPER, CHANNEL);
}

void HippoFloaterXmlImpl::onClose(bool quitting)
{
	if (mIsNotifyOnClose)
		send_chat_from_viewer("NOTIFY:" + mName + ":closed",
							  CHAT_TYPE_WHISPER, CHANNEL);
	LLFloater::onClose(quitting);
}


// ********************************************************************
// execute commands on instance

bool HippoFloaterXmlImpl::execute(LLUICtrl *ctrl,
								  const std::string &cmds, std::string::size_type &offset,
								  std::string &response)
{
	std::string token;

	while (cmdGetToken(cmds, offset, token)) {

		if (token == "}") {
			return true;
		} else	{
			std::string key = token;
			if (key == "getValue") {
				response += "VALUE:" + ctrl->getName() + ':' + ctrl->getValue().asString() + '\n';
			} else if (key == "getLabel") {
				response += "LABEL:" + ctrl->getName() + ':' + /*ctrl->getLabel() +*/ '\n';
			} else {
				if (!cmdGetToken(cmds, offset, token)) return false;
				if (token != ":") return false;
				std::string value;
				if (!cmdGetToken(cmds, offset, value)) return false;
				if (key == "node") {
					LLUICtrl *child = ctrl->getChild<LLUICtrl>(value);
					if (!child) return false;
					if (!cmdGetToken(cmds, offset, token)) return false;
					if (token != "{") return false;
					if (!execute(child, cmds, offset, response))
						return false;
				} else if (key == "setValue") {
					ctrl->setValue(value);
				} else if (key == "setLabel") {
					/*ctrl->setLabel(value);*/
				} else if (key == "setVisible") {
					ctrl->setVisible(value != "0");
				} else if (key == "notify") {
					bool set = (value != "0");
					if (HippoFloaterXmlImpl *floater = dynamic_cast<HippoFloaterXmlImpl*>(ctrl)) {
						floater->mIsNotifyOnClose = set;
					} else if (LLButton *button = dynamic_cast<LLButton*>(ctrl)) 
					{
						if (set)
							floater->mNotices[button] = notice_ptr_t(new notice_connection_t(button->setClickedCallback(boost::bind(&notifyCallback, ctrl))));
						else
							floater->mNotices.erase(button);
					} 
					else 
					{
						if (set)
							floater->mNotices[ctrl] = notice_ptr_t(new notice_connection_t(ctrl->setCommitCallback(boost::bind(&notifyCallback, ctrl))));
						else
							floater->mNotices.erase(ctrl);
					}
				}
			}
		}
	}
	return false;
}


// ********************************************************************
// parsing tools

static bool cmdGetToken(const std::string &cmds, std::string::size_type &offset, std::string &token)
{
	token = "";
	std::string::size_type size = cmds.size();
	char ch;
	while ((offset < size) && isspace(cmds[offset])) offset++;
	if (offset >= size) return false;
	ch = cmds[offset];
	if (ch == '\'') {
		ch = cmds[++offset];
		while ((offset < size) && (ch != '\'')) {
			if (ch == '&') ch = cmds[++offset];
			token += ch;
			ch = cmds[++offset];
		}
		offset++;
	} else if (isdigit(ch)) {
		while ((offset < size) && isdigit(ch)) {
			token += ch;
			ch = cmds[++offset];
		}
	} else if (isalpha(ch)) {
		while ((offset < size) && (isalnum(ch) || (ch == '_'))) {
			token += ch;
			ch = cmds[++offset];
		}
	} else {
		token += ch;
		offset++;
	}
	return (token != "");
}

