

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  This file is part of PSYCLONE version 2.1.1.260726 (26 July 2026)
//_/_/  
//_/_/  developed by
//_/_/  
//_/_/  Communicative Machines Ltd. 
//_/_/    http://cmlabs.com/
//_/_/  Copyright (c) 2026
//_/_/  
//_/_/  in collaboration with 
//_/_/  
//_/_/  Icelandic Institute for Intelligent Machines ses
//_/_/    Menntavegur 1
//_/_/    101 Reykjavik
//_/_/    ICELAND
//_/_/    http://www.iiim.is/
//_/_/
//_/_/  PSYCLONE 2.1 is released under the GNU Lesser General Public License 
//_/_/  (LGPL) version 3.0, and is subject to the following
//_/_/  
//_/_/  - CADIA Clause: The license granted in and to the software under this 
//_/_/    agreement is a limited-use license. The software may not be used in 
//_/_/    furtherance of: (i) intentionally causing bodily injury or severe 
//_/_/    emotional harm to any person; (ii) invading the personal privacy or 
//_/_/    violating the human rights of any person; or (iii) committing or 
//_/_/    preparing for any act of war. 
//_/_/    
//_/_/  A commercial license for PSYCLONE 2.1 is also available to 
//_/_/  any interested party from the copyright holder.
//_/_/  
//_/_/  You can redistribute this copy of PSYCLONE 2.1 and/or modify it under 
//_/_/  the terms of the GNU General Public License as published by the Free 
//_/_/  Software Foundation, either version 3 of the License, or (at your 
//_/_/  option) any later version.
//_/_/  
//_/_/  You should have received a copy of the GNU General Public License
//_/_/  and the GNU Lesser General Public License along with PSYCLONE 2.1.  
//_/_/  If not, see <http://www.gnu.org/licenses/>.
//_/_/  
//_/_/  PSYCLONE 2.1 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.
//_/_/
//_/_/  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
//_/_/  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
//_/_/  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
//_/_/  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 
//_/_/  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
//_/_/  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
//_/_/  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
//_/_/  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
//_/_/  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
//_/_/  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
//_/_/  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
#include "ControlPanel.h"


namespace cmlabs{


int8 ControlPanel(PsyAPI* api){
	// Psyclone specific definitions
	const char* triggerName;
	DataMessage* inMsg;

	ControlPanelObject* console = new ControlPanelObject();
	std::string iniFile = api->getParameterString("inifile");
	if (!iniFile.length()) {
		api->logPrint(1, "No panel inifile specified");
		return -1;
	}

	std::string iniFileText = utils::ReadAFileString(iniFile.c_str());
	if (!iniFileText.length()) {
		api->logPrint(1, "Panel inifile '%s' doesn't contain any panel configuration", iniFile.c_str());
		return -1;
	}

	if (!console->init(iniFileText.c_str(), api->getParameterAsBool("Print"))) {
		api->logPrint(1, "Panel inifile '%s' doesn't contain valid panel configuration", iniFile.c_str());
		return -1;
	}

	// Add hierarchy view to PsyProbe????
	api->addPsyProbeCustomView("Control Panel", "elements/controlpanel.html");

	PanelResponse res;
	std::string postName, html;
	uint64 startTime = GetTimeNow();
	uint64 serial = 0;
	DataMessage* msg, *mostRecentViewUpdate = NULL;
	const char* data;
	while(api->shouldContinue()){
		msg = NULL;
		res = NONE;
		postName.clear();
		if(inMsg = api->waitForNewMessage(console->getOptionFlashInterval(), triggerName)) {
			if (triggerName && stricmp(triggerName, "DefaultComponentTrigger") == 0) {
				//api->logPrint(2, "Got wakeup message...");
			}
			else if (inMsg->getType() == PsyAPI::CTRL_QUERY) {
				//api->logPrint(1, "%s\n", inMsg->getUserEntriesAsString().c_str());
				bool webCall = inMsg->hasInteger("HTTP_OPERATION");
				uint8 status = QUERY_FAILED;
				const char* query = inMsg->getString("Query");
				DataMessage* replyMsg = NULL;
				//if (!webCall)
				//	api->logPrint(1, "%s\n", inMsg->getUserEntriesAsString().c_str());
				if (!query) {
					status = QUERY_NOT_AVAILABLE;
				}
				else if (stricmp(query, "HTML") == 0) {
					const char* consoleName = inMsg->getString("ConsoleName");
					if (console->hasHTMLChanged(consoleName) || inMsg->getString("force")) {
						html = console->getScreenHTML(consoleName);
						replyMsg = new DataMessage();
						replyMsg->setString("HTML", html.c_str());
						//api->logPrint(1, "Panel sent updated HTML: %s", html.substr(0, 60).c_str());
					}
					status = QUERY_SUCCESS;
				}
				else if (webCall && (stricmp("OptionSelect", query) == 0)) {
					console->selectOption(inMsg->getString("name"));
					status = QUERY_SUCCESS;
				}
				else if (webCall && (stricmp(query, "AddPIN") == 0)) {
					console->addPIN(inMsg->getString("name"));
					status = QUERY_SUCCESS;
				}
				else if (webCall && (stricmp(query, "BackNavigation") == 0)) {
					console->navigateBack();
					status = QUERY_SUCCESS;
				}
				else if (webCall && (stricmp(query, "Reset") == 0)) {
					console->reset();
					status = QUERY_SUCCESS;
				}
				else if (stricmp("ReadScreen", query) == 0) {
					replyMsg = console->getScreenContent(false);
					if (replyMsg) {
						api->logPrint(1, "Screen read success");
						status = QUERY_SUCCESS;
					}
					else
						api->logPrint(1, "Screen read failed");
				}
				else if (stricmp(query, "GetUpdatedView") == 0) {
					if (mostRecentViewUpdate) {
						replyMsg = mostRecentViewUpdate;
						mostRecentViewUpdate = NULL;
					}
					status = QUERY_SUCCESS;
				}
				else {
					res = NONE;
					if (stricmp("Reset", query) == 0) {
						res = console->reset();
						replyMsg = console->getScreenContent(false);
						replyMsg->setInt("PanelResponse", (int64)res);
						status = QUERY_SUCCESS;
					}
					else if (stricmp("OptionSelect", query) == 0) {
						data = inMsg->getString("OptionName");
						if (data && strlen(data)) {
							res = console->selectOption(data);
							replyMsg = console->getScreenContent(false);
							replyMsg->setInt("PanelResponse", (int64)res);
							status = QUERY_SUCCESS;
						}
						else {
							res = FAILED;
							replyMsg = console->getScreenContent(false);
							replyMsg->setInt("PanelResponse", (int64)res);
							replyMsg->setString("Error", "No 'OptionName' entry provided");
							api->logPrint(1, "No 'OptionName' entry provided");
							status = QUERY_FAILED;
							api->queryReply((uint32)inMsg->getReference(), status, replyMsg);
							continue;
						}
					}
					else if (stricmp("BackNavigation", query) == 0) {
						res = console->navigateBack();
						replyMsg = console->getScreenContent(false);
						replyMsg->setInt("PanelResponse", (int64)res);
						status = QUERY_SUCCESS;
					}
					else if (stricmp("EnterPIN", query) == 0) {
						data = inMsg->getString("pin");
						if (data && strlen(data)) {
							res = console->enterPIN(data);
							replyMsg = console->getScreenContent(false);
							replyMsg->setInt("PanelResponse", (int64)res);
							status = QUERY_SUCCESS;
						}
						else {
							res = FAILED;
							replyMsg = console->getScreenContent(false);
							replyMsg->setInt("PanelResponse", (int64)res);
							replyMsg->setString("Error", "No 'PIN' entry provided");
							api->logPrint(1, "No 'PIN' entry provided");
							status = QUERY_FAILED;
							api->queryReply((uint32)inMsg->getReference(), status, replyMsg);
							continue;
						}
					}
					if (status == QUERY_SUCCESS)
						api->logPrint(1, "Screen command: '%s' - response: %u", query, (uint32)res);
					else
						api->logPrint(1, "Screen command unknown: '%s'", query);
				}

				api->queryReply((uint32)inMsg->getReference(), status, replyMsg);
			}
		}

		console->runMaintenance();

		if (console->hasViewChanged()) {
			//html = console->getScreenHTML();
			//api->setPrivateData("Screen", html.c_str(), html.length(), "text/html");
			msg = console->getScreenContent(true);
			if (msg && mostRecentViewUpdate)
				delete mostRecentViewUpdate;
			if (msg)
				mostRecentViewUpdate = msg;
		}
	}
	return 0;
};













int8 ControlPanelController(PsyAPI* api) {
	const char* triggerName;
	DataMessage* inMsg, *msg, *resultMsg;
	uint8 res;
	PanelResponse response;
	int32 intervalMS = 1000;
	uint64 lastQuery = 0;
	std::map<std::string, DataMessage*> messages;
	std::map<std::string, DataMessage*>::iterator i, e;

	while (api->shouldContinue()) {
		if (inMsg = api->waitForNewMessage(100, triggerName)) {
			resultMsg = NULL;
			msg = inMsg->copy();
			msg->setString("Query", triggerName);
			res = api->queryCatalog(&resultMsg, "ControlPanel", msg, 5000);
			switch (res) {
			case QUERY_SUCCESS:
				if (!resultMsg) {
					api->postOutputMessage(utils::StringFormat("%sSuccess", triggerName).c_str());
					api->logPrint(1, "Control panel responded with success back to controller");
				}
				else {
					response = (PanelResponse)resultMsg->getInt("PanelResponse");
					switch (response) {
					case SUCCESS:
						api->postOutputMessage(utils::StringFormat("%sSuccess", triggerName).c_str(), resultMsg);
						api->logPrint(1, "Control panel responded with success: Operation successful");
						break;
					case UNKNOWN:
						api->logPrint(1, "Error from ControlPanel: Option Unknown");
						resultMsg->setString("Error", "Option Unknown");
						api->postOutputMessage(utils::StringFormat("%sFailed", triggerName).c_str(), resultMsg);
						break;
					case FAILED:
						api->logPrint(1, "Error from ControlPanel: Operation Failed");
						resultMsg->setString("Error", "Operation Failed");
						api->postOutputMessage(utils::StringFormat("%sFailed", triggerName).c_str(), resultMsg);
						break;
					case BUSY:
						api->logPrint(1, "Error from ControlPanel: Panel Busy");
						resultMsg->setString("Error", "Panel Busy");
						api->postOutputMessage(utils::StringFormat("%sFailed", triggerName).c_str(), resultMsg);
						break;
					case NOACCESS:
						api->logPrint(1, "Error from ControlPanel: Access Denied");
						resultMsg->setString("Error", "Access Denied");
						api->postOutputMessage(utils::StringFormat("%sFailed", triggerName).c_str(), resultMsg);
						break;
					case ENTERPIN:
						api->logPrint(1, "Error from ControlPanel: PIN Required");
						resultMsg->setString("Error", "PIN Required");
						api->postOutputMessage(utils::StringFormat("%sFailed", triggerName).c_str(), resultMsg);
						break;
					case NONE:
					default:
						api->logPrint(1, "Unknown response from ControlPanel: %d", (int)response);
						// do nothing
						break;
					}
				}
				break;
			case QUERY_TIMEOUT:
			default:
				api->logPrint(1, "Error from ControlPanel: Catalog Query Timed Out");
				msg = new DataMessage();
				msg->setString("Error", "Catalog Query Timeout");
				api->postOutputMessage(utils::StringFormat("%sFailed", triggerName).c_str(), msg);
				delete resultMsg;
				break;
			}
		}

/*		if (!lastQuery || (GetTimeAgeMS(lastQuery) > intervalMS)) {
			msg = new DataMessage();
			msg->setString("Query", "GetUpdatedView");
			res = api->queryCatalog(&resultMsg, "ControlPanel", msg, 5000);
			if (resultMsg)
				api->postOutputMessage("ViewUpdated", resultMsg);
			lastQuery = GetTimeNow();
		}*/

	}
	return 0;
};











ControlPanelObject::ControlPanelObject() {
	showPINPad.clear();
	viewHasChanged = true;
	//htmlHasChanged = true;
	optionSelectedTime = 0;
	optionFlashTimeMS = 1000;
	optionFlashIntervalMS = 200;
	currentScreen = NULL;
	currentSelectedOption.clear();
	printDebug = false;
}

ControlPanelObject::~ControlPanelObject() {
	mainScreen.clear();
}



bool ControlPanelObject::init(const char* json, bool printDebug) {
	this->printDebug = printDebug;
	jsmn_parser parser;
	jsmntok_t* tokens = new jsmntok_t[1024];
	int tokenCount = 0;
	const char* val;
	int valSize;
	jsmn_init(&parser);
	if ((tokenCount = jsmn_parse(&parser, json, strlen(json), tokens, 1024)) <= 0) {
		// No JSON found
		delete[] tokens;
		return false;
	}
	if (!(val = GetJSONValue(tokens, tokenCount, json, "name", JSMN_UNDEFINED, valSize))) {
		// name not found
		delete[] tokens;
		return false;
	}
	consoleName = std::string(val, valSize);

	ControlScreen screen;

	// now dig into screens
	std::vector<int>::iterator i, e;
	std::vector<int> screens = GetJSONArrayIndexes(tokens, tokenCount, json, "screens", JSMN_UNDEFINED);
	i = screens.begin();
	e = screens.end();
	// we are only expecting one main screen
	if (i != e) {
		if (!mainScreen.fromJSON(tokens, tokenCount, json, *i, JSMN_UNDEFINED)) {
			delete[] tokens;
			return false;
		}
	}
	mainScreen.parent = NULL;
	delete[] tokens;
	return true;
}


PanelResponse ControlPanelObject::reset() {
	if (printDebug)
		LogPrint(0, 0, 0, "Command Reset");
	currentPINEntry.clear();
	showPINPad.clear();
	htmlHasChanged.clear();
	viewHasChanged = true;
	currentSelectedOption.clear();
	optionSelectedTime = 0;

	if (navigateOption(""))
		return SUCCESS;
	else
		return FAILED;
}

PanelResponse ControlPanelObject::selectOption(const char* name) {
	if (!name)
		return FAILED;

	if (printDebug)
		LogPrint(0, 0, 0, "Command Select option: '%s'", name);

	if (optionSelectedTime)
		return BUSY;

	// confirm that we can navigate to this option
	if (!currentScreen)
		return FAILED;

	PanelResponse res = currentScreen->hasOption(name);
	if (res == SUCCESS) {
		// if we can, set navigation in motion
		currentSelectedOption = name;
		optionSelectedTime = GetTimeNow();
		return SUCCESS;
	}

	if (res == ENTERPIN) {
		showPINPad = currentScreen->getPIN(name);
		currentSelectedOption = name;
		htmlHasChanged.clear();
		if (!showPINPad.length()) {
			optionSelectedTime = GetTimeNow();
			return SUCCESS;
		}
		else
			optionSelectedTime = GetTimeNow();
	}

	return res;
}

PanelResponse ControlPanelObject::navigateBack() {
	if (printDebug)
		LogPrint(0, 0, 0, "Command Navigate backwards");
	std::string parent;
	if (!currentScreen)
		return FAILED;

	if (showPINPad.length()) {
		currentPINEntry.clear();
		showPINPad.clear();
		htmlHasChanged.clear();
		viewHasChanged = true;
		currentSelectedOption.clear();
		optionSelectedTime = 0;
		return SUCCESS;
	}

	// are we already on the main screen?
	if (!currentScreen->parent)
		return SUCCESS;

	if (navigateOption(currentScreen->parent->name.c_str()))
		return SUCCESS;
	else
		return FAILED;
}

PanelResponse ControlPanelObject::readScreen() {
	if (printDebug)
		LogPrint(0, 0, 0, "Command ReadScreen");
	DataMessage* msg = getScreenContent(false);
	if (!msg)
		return FAILED;
	outputQ.push(msg);
	return SUCCESS;
}

PanelResponse ControlPanelObject::addPIN(const char* str) {
	if (printDebug)
		LogPrint(0, 0, 0, "Adding PIN: '%s'", str);
	if (!showPINPad.length())
		return FAILED;

	htmlHasChanged.clear();
	currentPINEntry += str;
	if (currentPINEntry.length() >= showPINPad.length())
		return enterPIN(currentPINEntry.c_str());
	else
		return SUCCESS;
}

PanelResponse ControlPanelObject::enterPIN(const char* str) {
	if (!showPINPad.length()) {
		LogPrint(0, 0, 0, "PIN entered when no PIN required", str);
		return FAILED;
	}

	if (printDebug)
		LogPrint(0, 0, 0, "Entering PIN: '%s'", str);
	htmlHasChanged.clear();
	currentPINEntry = str;
	if (stricmp(currentPINEntry.c_str(), showPINPad.c_str()) == 0) {
		currentPINEntry = "CORRECT";
		return SUCCESS;
	}
	else {
		currentPINEntry = "WRONG";
		return FAILED;
	}
}


bool ControlPanelObject::hasHTMLChanged(const char* name) {
	if (!name)
		return true;
	return (optionSelectedTime ||
		(htmlHasChanged.find(name) == htmlHasChanged.end()));
}

bool ControlPanelObject::hasViewChanged() {
	return (viewHasChanged);
}


bool ControlPanelObject::runMaintenance() {
	if (optionSelectedTime && !showPINPad.length()) {
		uint64 t = GetTimeAgeMS(optionSelectedTime);
		if (t > optionFlashTimeMS) {
			navigateOption(currentSelectedOption.c_str());
			optionSelectedTime = 0;
			currentSelectedOption.clear();
		}
	}
	return true;
}

std::string ControlPanelObject::getScreenHTML(const char* name) {
	// generate HTML
	std::string html, pinStars, screenName;

	if (name)
		htmlHasChanged[name] = GetTimeNow();

	if (currentScreen) {
		if (currentScreen != &mainScreen)
			screenName = currentScreen->name;
	}

	if (showPINPad.length() && (GetTimeAgeMS(optionSelectedTime) > (int32)optionFlashTimeMS)) {
		std::string keyClass = "PINCode";
		screenName += "_pincode";
		html = utils::StringFormat("<input type = 'hidden' id='ScreenName' value='%s'>", screenName.c_str());
		if (stricmp(currentPINEntry.c_str(), "WRONG") == 0) {
			pinStars += "WRONG";
			currentPINEntry.clear();
			htmlHasChanged.clear();
			showPINPad.clear();
			optionSelectedTime = 0;
			keyClass = "PINCodeWrong";
		}
		else if (stricmp(currentPINEntry.c_str(), "CORRECT") == 0) {
			pinStars += "CORRECT";
			currentPINEntry.clear();
			htmlHasChanged.clear();
			showPINPad.clear();
			optionSelectedTime = 0;
			navigateOption(currentSelectedOption.c_str());
			keyClass = "PINCodeCorrect";
		}
		else
			for (uint32 n = 0; n < currentPINEntry.length(); n++)
				pinStars += "*";
		html += utils::StringFormat(
			"<table class='PINPadTable'>"
			"<tr><td colspan=3><span class='%s'>%s</span></td></tr>"
			"<tr><td colspan=3>&nbsp</td></tr>"
			"<tr>"
			"<td><button class='%s' onclick='pinClicked(\"1\"); return false;'>1</button></td>"
			"<td><button class='%s' onclick='pinClicked(\"2\"); return false;'>2</button></td>"
			"<td><button class='%s' onclick='pinClicked(\"3\"); return false;'>3</button></td>"
			"</tr>"
			"<tr>"
			"<td><button class='%s' onclick='pinClicked(\"4\"); return false;'>4</button></td>"
			"<td><button class='%s' onclick='pinClicked(\"5\"); return false;'>5</button></td>"
			"<td><button class='%s' onclick='pinClicked(\"6\"); return false;'>6</button></td>"
			"</tr>"
			"<tr>"
			"<td><button class='%s' onclick='pinClicked(\"7\"); return false;'>7</button></td>"
			"<td><button class='%s' onclick='pinClicked(\"8\"); return false;'>8</button></td>"
			"<td><button class='%s' onclick='pinClicked(\"9\"); return false;'>9</button></td>"
			"</tr>"
			"<tr><td colspan=3><button class='%s' onclick='pinClicked(\"0\"); return false;'>0</button></td></tr>"
			"</table>\n", keyClass.c_str(), pinStars.c_str(),
			keyClass.c_str(), keyClass.c_str(), keyClass.c_str(), keyClass.c_str(), keyClass.c_str(),
			keyClass.c_str(), keyClass.c_str(), keyClass.c_str(), keyClass.c_str(), keyClass.c_str());
		return html;
	}

	if (!currentScreen) {
		html = utils::StringFormat("<input type = 'hidden' id='ScreenName' value=''><H1>IDLE</H1>");
		return html;
	}

	//uint8 highlightOption = 0;
	//if (optionSelectedTime) {
	//	// still flashing, set to either 1 or 2
	//	uint64 t = GetTimeAgeMS(optionSelectedTime);
	//	highlightOption = (((uint32)(t/optionFlashIntervalMS)) % 2) + 1;
	//}

	//if (showPINPad.length()) {
	//	LogPrint(0,0,0,"Should be showing the PINPAD (%llu), screen: %s, currentSelectedOption: %s",
	//		optionSelectedTime, screenName.c_str(), currentSelectedOption.c_str());
	//}

	html = utils::StringFormat("<input type = 'hidden' id='ScreenName' value='%s'>", screenName.c_str());
	html += "<table class='OptionsTable'>";
	std::vector<ControlScreen*>::iterator i = currentScreen->options.begin(), e = currentScreen->options.end();
	while (i != e) {
		if (*i) {
			if ((*i)->name.length()) {
				if (optionSelectedTime && (stricmp((*i)->name.c_str(), currentSelectedOption.c_str()) == 0))
					html += utils::StringFormat("<tr><td><button class='OptionHighlight' onclick='optionClicked(\"%s\"); return false;'>%s</button></td></tr>", (*i)->name.c_str(), (*i)->title.c_str());
				else
					html += utils::StringFormat("<tr><td><button class='OptionNormal' onclick='optionClicked(\"%s\"); return false;'>%s</button></td></tr>", (*i)->name.c_str(), (*i)->title.c_str());
			}
			else {
				html += utils::StringFormat("<tr><td><span class='InformationOnly'>%s</span></td></tr>", (*i)->title.c_str());
			}
		}
		i++;
	}

	html += "</table>\n";
	return html;
}

DataMessage* ControlPanelObject::getScreenContent(bool clearChanged) {
	if (!currentScreen)
		return NULL;
	DataMessage* msg = new DataMessage();
	if (showPINPad.length()) {
		msg->setString("ScreenName", "PINCode");
		return msg;
	}
	else {
		msg->setString("ScreenName", currentScreen->name.c_str());
		msg->setString("ScreenTitle", currentScreen->title.c_str());
	}
	uint32 n = 1;
	std::vector<ControlScreen*>::iterator i = currentScreen->options.begin(), e = currentScreen->options.end();
	while (i != e) {
		if (*i) {
			msg->setString(n, "Name", (*i)->title.c_str());
			msg->setString(n, "Title", (*i)->title.c_str());
			n++;
		}
		i++;
	}
	if (clearChanged)
		viewHasChanged = false;
	return msg;
}

uint32 ControlPanelObject::getOptionFlashInterval() {
	return optionFlashIntervalMS;
}

bool ControlPanelObject::navigateOption(const char* name) {
	if (printDebug)
		LogPrint(0, 0, 0, "Trying to navigate to screen: '%s'", name);

	currentPINEntry.clear();
	showPINPad.clear();
	htmlHasChanged.clear();
	viewHasChanged = true;
	optionSelectedTime = 0;
	if (!name || (stricmp(name, "") == 0)) {
		if (printDebug)
			LogPrint(0,0,0, "Navigating to Main Screen");
		currentScreen = &mainScreen;
		currentSelectedOption.clear();
		return true;
	}
	if (!currentScreen)
		return false;

	std::vector<ControlScreen*>::iterator i = currentScreen->options.begin(), e = currentScreen->options.end();
	while (i != e) {
		if (*i) {
			if ((stricmp((*i)->name.c_str(), name) == 0) || (stricmp((*i)->title.c_str(), name) == 0)) {
				currentScreen = (*i);
				currentSelectedOption.clear();
				if (!currentScreen->options.size()) {
					if (currentScreen->parent && currentScreen->parent->parent) {
						currentScreen = currentScreen->parent->parent;
						if (printDebug)
							LogPrint(0, 0, 0, "Navigating: Found info only screen named '%s'", name);
					}
					else {
						currentScreen = &mainScreen;
						if (printDebug)
							LogPrint(0, 0, 0, "Navigating: Found screen named '%s' = Main Screen", name);
					}
					return true;
				}
				else {
					if (printDebug)
						LogPrint(0, 0, 0, "Navigating: Found screen named '%s'", name);
				}
				return true;
			}
		}
		i++;
	}

	if (currentScreen->parent) {
		if ((stricmp(currentScreen->parent->name.c_str(), name) == 0) || (stricmp(currentScreen->parent->title.c_str(), name) == 0)) {
			currentScreen = currentScreen->parent;
			currentSelectedOption.clear();
			if (printDebug)
				LogPrint(0, 0, 0, "Navigating: Found parent screen named '%s'", name);
			return true;
		}
	}

	if (!currentScreen->options.size()) {
		currentScreen = currentScreen->parent;
		currentSelectedOption.clear();
		if (printDebug)
			LogPrint(0, 0, 0, "Navigating: Found parent info screen named '%s'", name);
		return true;
	}

	currentSelectedOption.clear();
	if (printDebug)
		LogPrint(0, 0, 0, "Navigating: Didn't find screen named '%s'", name);
	return false;
}







bool ControlScreen::fromJSON(jsmntok_t* tokens, int tokenCount, const char* json, int index, jsmntype_t type) {

	parent = NULL;
	title = GetJSONChildValueString(tokens, tokenCount, json, "title", index);
	if (!title.length())
		return false;

	ControlScreen* screen;
	std::vector<int>::iterator i, e;
	std::vector<int> subScreens;
	name = GetJSONChildValueString(tokens, tokenCount, json, "name", index);
	pin = GetJSONChildValueString(tokens, tokenCount, json, "pin", index);
	options.clear();
	subScreens = GetJSONChildArrayIndexes(tokens, tokenCount, json, "options", index, JSMN_UNDEFINED);
	i = subScreens.begin();
	e = subScreens.end();
	while (i != e) {
		screen = new ControlScreen;
		if (screen->fromJSON(tokens, tokenCount, json, *i, type)) {
			screen->parent = this;
			options.push_back(screen);
		}
		else
			delete screen;
		i++;
	}
	return true;
}

PanelResponse ControlScreen::hasOption(const char* name) {
	if (!name)
		return UNKNOWN;
	std::vector<ControlScreen*>::iterator i = options.begin(), e = options.end();
	while (i != e) {
		if (*i) {
			if ((stricmp(name, (*i)->name.c_str()) == 0) || (stricmp(name, (*i)->title.c_str()) == 0)) {
				if ((*i)->pin.length())
					return ENTERPIN;
				else
					return SUCCESS;
			}
		}
		i++;
	}
	return UNKNOWN;
}

std::string ControlScreen::getPIN(const char* name) {
	std::vector<ControlScreen*>::iterator i = options.begin(), e = options.end();
	while (i != e) {
		if (*i) {
			if ((stricmp(name, (*i)->name.c_str()) == 0) || (stricmp(name, (*i)->title.c_str()) == 0)) {
				if ((*i)->pin.length())
					return (*i)->pin;
				else
					return "";
			}
		}
		i++;
	}
	return "";
}

bool ControlScreen::clear() {
	std::vector<ControlScreen*>::iterator i = options.begin(), e = options.end();
	while (i != e) {
		if (*i) {
			(*i)->clear();
			delete (*i);
		}
		i++;
	}
	options.clear();
	return true;
}

}//end of namespace cmlabs:
