//----------------------------------------------------------------------------------
// <copyright file="RegulusREST.cpp" company="LiquidRobotics">
//
// Copyright (c) 2014 Liquid Robotics, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// This software is associated with a release.  Liquid Robotics makes no
// representation that the design, interfaces or component distribution will
// remain unchanged in future releases.
//
// </copyright>
//
// <summary>
//   RegulusREST provides some methods for implementing REST calls to Regulus from a
//	 payload.
// </summary>
//
// <owner>Mike Cookson</owner>
//----------------------------------------------------------------------------------

#include "RegulusREST.h"
#include "CurlUtils.h"
#include "nsextend.h"
#include <fstream>
#include <sstream>
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include "json/json.h"

using namespace std;
using namespace LiquidRobotics::SV3RemotingLib::CurlUtils;

const bool RUN_VERBOSE = false;

#include "RegulusREST-inline.h"

namespace LiquidRobotics {
namespace SV3RemotingLib {

IMPLEMENT_INNER_EXCEPTION(RegulusREST, EmptyCommandException,std::runtime_error);
IMPLEMENT_INNER_EXCEPTION(RegulusREST, JSONParsingError, std::runtime_error);
IMPLEMENT_INNER_EXCEPTION(RegulusREST, RemoteCommandFormatError, std::runtime_error);
IMPLEMENT_INNER_EXCEPTION(RegulusREST, ServerTimeoutOnRequest, std::runtime_error);

template<typename T>
void ExpectOK(const T& objectWithHeaders, const string& operation)
{
	string item;
	bool found = false;

	for (typename T::StringArray::const_iterator ci = objectWithHeaders.headers.begin(); !found && (ci != objectWithHeaders.headers.end()); ++ci)
		if (boost::starts_with(*ci, "HTTP"))
		{
			item = *ci;
			boost::trim(item);
			found = true;
		}

	if (!found)
	{
		stringstream ss;
		ss << "For " << operation << ", no HTTP header was returned";
		throw runtime_error(ss.str());
	}
	else if (!boost::ends_with(item, " OK"))
	{
		stringstream ss;
		ss << "For " << operation << ", the HTTP header did not return OK: " << item;
		throw runtime_error(ss.str());
	}
}

//! Constructor providing the address field of the URL (should include port if needed) including logger
RegulusREST::RegulusREST(
		const string& _address /**< [in] Address field of the URL (should include port if needed) */,
		Logger& _logger /**< [in] instance of Logger to use for output */)
: address(_address)
, logger(_logger)
{
}

void RegulusREST::DelayShutdown(
		const string& id,						/**< [in] Interface ID */
		long seconds							/**< [in] Number of seconds to delay (allows payload to shut down) */)
{
	stringstream ssurl;
	ssurl << "http://" << address << "/xp?op=offdelay&id=" << EncodeURL(id) << "&delay=" << seconds << "&setdefault=0";
	const string url = ssurl.str();
	HttpGet get(url, logger, RUN_VERBOSE);
	ExpectOK(get, "Poll");
}

void RegulusREST::Poll(
		IDVector& ids,						/**< [in] list of ids to reference (must be at least one) */
		RemoteCommandVector* commands,		/**< [in] inlcude if there is a desire to receive commands */
		GPSInfo* gpsInfo,					/**< [in] include if there is a desire to receive GPS information */
		ShutdownInfo* shutdownInfo,			/**< [in] include if there is a desire to receive information about a shutdown */
		long maxWait,						/**< [in] maximum milliseconds to wait (-1=no limit, or maybe 1 hour) */
		long maxCommands					/**< [in] maximum number of commands to accept */)
{
	if ((!commands || (maxCommands < 1)) && !gpsInfo && !shutdownInfo)
		throw invalid_argument("Poll command will not do anything");

	stringstream ssurl;
	ssurl << "http://" << address << "/xp?op=poll";

	for (IDVector::const_iterator ci = ids.begin(); ci != ids.end(); ++ci)
		ssurl << "&id=" << EncodeURL(*ci);

	ssurl << "&format=2";
	if (!commands)
		maxCommands = 0;
	else
		commands->clear();

	if (gpsInfo)			{ gpsInfo->valid = false; ssurl << "&gps=true"; }
	if (shutdownInfo)		{ shutdownInfo->shutdown = false; ssurl << "&shutdown=true"; }
	if (maxCommands != 1) 	ssurl << "&maxcommands=" << maxCommands;
	if (maxWait < 0) 		maxWait = 60*60*1000;
	if (maxWait >= 0)		ssurl << "&timeout=" << maxWait;

	const string url = ssurl.str();
	HttpGet get(url, logger, RUN_VERBOSE, (maxWait+999)/1000+60);
	ExpectOK(get, "Poll");
	if (get.body.length() == 0) throw EmptyCommandException("The command returned was empty");

	Json::Value root;

	try {
		root = StringToJson(get.body);
	}
	catch(RegulusREST::JSONParsingError& ex)
	{
		stringstream ss;
		ss << "Failed to parse poll result to JSON (" << typeid(ex).name() << endl
		   << ex.what() << ")" << endl;
		const string sstr = ss.str();
		throw JSONParsingError(sstr);
	}

	Json::Value Error = root["error"];

	if (!Error.isNull()) {

		stringstream msg;
		msg << "Poll operation returned error from server: ";

		if (Error.isString())
			msg << Error.asString();
		else
			msg << Error.toStyledString();

		throw runtime_error(msg.str());
	}

	Json::Value Timeout = root["timeout"];

	if (!Timeout.isNull() && Timeout.asBool())
		throw ServerTimeoutOnRequest("Server returned timeout on Poll operation");

	Json::Value Commands = root["commands"];

	if (Commands.isArray() && commands) {
		unsigned numCommands = Commands.size();
		Json::Value def;
		for (unsigned i = 0; i < numCommands; ++i) {
			Json::Value item = Commands.get(i, def);
			if (item.isObject()) {
				RemoteCommand rc(item.toStyledString());
				commands->push_back(rc);
			} else {
				stringstream ss;
				ss << "Non-object found in command item list at index " << i << " in response to Poll";
				throw runtime_error(ss.str());
			}
		}
	}

	Json::Value GPS = root["gps"];

	if (GPS.isObject() && gpsInfo)
		*gpsInfo = GPSInfo(GPS.toStyledString());

	Json::Value Shutdown = root["shutdown"];

	if (Shutdown.isBool() && shutdownInfo)
		shutdownInfo->shutdown = true;

	if (!((commands && commands->size()) || (gpsInfo && gpsInfo->valid) || (shutdownInfo && shutdownInfo->shutdown)))
		throw runtime_error("None of the expected conditions were satisfied in response to Poll");
}

//! Provides a way to respond to a previously-received command
void RegulusREST::RespondToCommand(const string& id, const string& responseTag, const string& response, bool complete, bool succeeded)
{
	stringstream ssurl;
	/*ssurl << "http://" << address << "/xp?op=respond"
			<< "&id=" << EncodeURL(id)
			<< "&instance=" << EncodeURL(command.responseTag)
			<< "&c=" << EncodeURL(complete ? "true" : "false")
			<< "&s=" << EncodeURL(succeeded ? "true" : "false");*/

	ssurl << "http://" << address << "/xp?op=reply&id=" << id
			<< "&tag=" << EncodeURL(responseTag);
	if (!complete)	ssurl << "&complete=false";
	if (!succeeded)	ssurl << "&succeeded=false";
	const string url = ssurl.str();
	PostFile2 post(url, "content", response, logger, false, RUN_VERBOSE);
	ExpectOK(post, "Respond to Command");
}

//! Installs the given JAR file on Regulus
void RegulusREST::InstallJar(
		const string& id,		/**< [in] Interface ID */
		const string& fileName /**< [in] Name and path of JAR file to send */)
{
	stringstream ssurl;
	ssurl << "http://" << address << "/xp?op=jar&id=" << EncodeURL(id);
	const string url = ssurl.str();
	PostFile2 post(url, "content", fileName, logger, true, RUN_VERBOSE);
	ExpectOK(post, "Install JAR");
}

//! Returns the payload registration content given a file and a registration structure
string RegulusREST::GetPayloadRegistrationContent(
		const string& fileName,				    /**< [in] Name of file with default values */
		const PayloadRegistration& payloadReg  	/**< [in] Registration data */)
{
	return payloadReg.ToJSON(fileName);
}

//! Registers the payload with Regulus
void RegulusREST::RegisterPayload(
		const string& id,						/**< [in] Interface ID */
		const string& fileName,				    /**< [in] Name of file with default values */
		const PayloadRegistration& payloadReg, /**< [in] Registration data */
		const string& hostIDPurge				/**< [in] Host ID of this payload, purges all others with the same host ID if provided */)
{
	RegisterPayloadWithContent(id, payloadReg.ToJSON(fileName), hostIDPurge);
}

//! Registers the payload with Regulus
void RegulusREST::RegisterPayloadWithContent(
		const string& id,						/**< [in] Interface ID */
		const string& content,   				/**< [in] Content of the registration file */
		const string& hostIDPurge				/**< [in] Host ID of this payload, purges all others with the same host ID if provided */)
{
	string con2 = content;
	boost::trim(con2);
	if (boost::starts_with(con2, "{") && boost::ends_with(con2,"}"))
		con2 = con2.substr(1,con2.length()-2);

	stringstream ssurl;
	ssurl << "http://" << address << "/xp?op=meta";
	if (id.length()) ssurl << "&id=" << EncodeURL(id);
	if (hostIDPurge.length()) ssurl << "&hostid=" << EncodeURL(hostIDPurge);

	const string url = ssurl.str();

	PostFile2 post(url, "content", con2, logger, false, RUN_VERBOSE);
	ExpectOK(post, "Payload Registration");
}

//! Registration is complete (OK to send file to shore)
void RegulusREST::RegistrationComplete(
		const string& id						/**< [in] Interface ID */)
{
	stringstream ssurl;
	ssurl << "http://" << address << "/xp?op=metadone&id=" << EncodeURL(id);

	const string url = ssurl.str();
	HttpGet get(url, logger, RUN_VERBOSE);
	ExpectOK(get, "Registration complete");
}

//! Executes the given script string on Regulus
string RegulusREST::RunScript(
		const string& id,		/**< [in] Interface ID */
		const string& script /**< [in] Script to be executed */)
{
	stringstream ssurl;
	ssurl << "http://" << address << "/xp?op=script&id=" << EncodeURL(id) << "&cmd=" << EncodeURL(script);
	const string url = ssurl.str();
	HttpGet get(url, logger, RUN_VERBOSE);
	ExpectOK(get, "Run Script");
	return get.body;
}

//! Sends the given file to shore
void RegulusREST::SendFileToShore(
		const string& id,			/**< [in] Interface ID */
		const string& fileName 		/**< [in] Name and path of file to be sent */,
		const string& remoteName	/**< [in] The name of the file on the remote system */)
{
	stringstream ssurl;
	ssurl << "http://" << address << "/xp?op=upload&id=" << EncodeURL(id);
	if (remoteName.length()) ssurl << "&remote=" << EncodeURL(remoteName);
	const string url = ssurl.str();
	PostFile2 post(url, "content", fileName, logger, true, RUN_VERBOSE);
	ExpectOK(post, "Send File to Shore");
}

//! Sends the given file to shore (via the legacy WGMS file transfer protocol)
void RegulusREST::SendFileToShoreLegacy(
		const string& id,						/**< [in] Interface ID */
		const string& fileName,					/**< [in] Name and path of file to be sent */
		const string& remoteName				/**< [in] The name of the file on the remote system */)
{
	using namespace boost::filesystem;

	if (!exists(fileName)) {
		stringstream ss;
		ss << "SendFileToShoreLegacy: filename (" << fileName << ") does not exist";
		throw invalid_argument(ss.str());
	}

	stringstream ssurl;
	ssurl << "http://" << address << "/xp?op=uploadlegacy&id=" << EncodeURL(id);
	if (remoteName.length()) ssurl << "&remote=" << EncodeURL(remoteName);
	const string url = ssurl.str();
	PostFile2 post(url, "content", fileName, logger, true, RUN_VERBOSE);
	ExpectOK(post, "Send File to Shore Legacy");
}

//! Sends a raw packet to shore (byte array parameter)
void RegulusREST::SendRawPacketToShore(
		const string& id,		/**< [in] Interface ID */
		const ByteArray& rawPacket /**< [in] the data to be sent in a byte array */)
{
	stringstream ssurl;
	ssurl << "http://" << address << "/xp?op=rawpacket&id=" << EncodeURL(id);

	const string url = ssurl.str();
	PostFile2 post(url, "content", rawPacket.ToASCIIString(), logger, false, RUN_VERBOSE);
	ExpectOK(post, "Send Raw Packet to Shore");
}

//! Sends a raw packet to shore (string parameter)
void RegulusREST::SendRawPacketToShore(
		const string& id,		/**< [in] Interface ID */
		const string& rawPacket /**< [in] the data to be sent in a string */)
{
	ByteArray ba(rawPacket.begin(), rawPacket.end());
	SendRawPacketToShore(id, ba);
}

//! Turns power switch associated with payload on or off
void RegulusREST::SetPayloadAMPSSwitch(
		const string& id,						/**< [in] Interface ID */
		bool on									/**< [in] true to turn the switch on, false to turn the switch off */)
{
	stringstream ssurl;
	ssurl << "http://" << address << "/xp?op=pwr&id=" << EncodeURL(id) << "&cmd=" << (on ? "on" : "off");

	const string url = ssurl.str();
	HttpGet get(url, logger, RUN_VERBOSE);
	ExpectOK(get, "Set Payload AMPS Switch");
	if (!boost::equals(boost::trim_copy(get.body), "OK")) {
		stringstream ss;
		ss << "Server operation returned '" << get.body << "' for " << ssurl.str();
		throw runtime_error(ss.str());
	}
}

//! Turns power switch associated with payload on or off
void RegulusREST::SetPayloadAMPSSwitch(
		const string& id,						/**< [in] Interface ID */
		const string& switchName,				/**< [in] Name of the switch to affect */
		bool on									/**< [in] true to turn the switch on, false to turn the switch off */)
{
	stringstream ssurl;
	ssurl << "http://" << address << "/xp?op=pwr&id=" << EncodeURL(id) << "&sw=" << EncodeURL(switchName) << "&cmd=" << (on ? "on" : "off");

	const string url = ssurl.str();
	HttpGet get(url, logger, RUN_VERBOSE);
	ExpectOK(get, "Set Payload AMPS Switch");
	if (!boost::equals(boost::trim_copy(get.body), "OK")) {
		stringstream ss;
		ss << "Server operation returned '" << get.body << "' for " << ssurl.str();
		throw runtime_error(ss.str());
	}
}

//! Requests to turn this payload off
void RegulusREST::TurnPayloadOff(
		const string& id		/**< [in] Interface ID */)
{
	stringstream ssurl;
	ssurl << "http://" << address << "/xp?op=off&id=" << EncodeURL(id);

	const string url = ssurl.str();
	HttpGet get(url, logger, RUN_VERBOSE);
	ExpectOK(get, "Turn Payload Off");
}

string RegulusREST::EncodeURL(const string& url)
{
	static const string Unreserved =
		"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~";

	if (url.find_first_not_of(Unreserved) == string::npos)
		return url;

	stringstream stm;

	for (string::const_iterator ci = url.begin(); ci != url.end(); ++ci)
	{
		const char c = *ci;

		if (!isalnum(c) && (Unreserved.find(*ci) == string::npos))
			stm << "%" << ultos(*ci,16,2);
		else
			stm.put(*ci);
	}

	return stm.str();
}

static const string PREGTag = "tag";
static const string PREGDesc = "description";
static const string PREGDefaults = "defaults";
static const string PREGEnabled = "enabled";
static const string PREGGenAlarms = "generateAlarms";
static const string PREGBoardAddress = "boardAddress";
static const string PREGReportingAddress = "reportingAddress";
static const string PREGCORBAName = "corbaName";
static const string PREGHostID = "hostID";

RegulusREST::PayloadRegistration::PayloadRegistration()
: boardAddress(0)
, reportingAddress(0)
{
}

RegulusREST::PayloadRegistration::PayloadRegistration(const string& fileName)
: boardAddress(0)
, reportingAddress(0)
{
	Json::Value root;
	try {
		root = FileToJson(fileName);
	}
	catch(exception& ex) {
		cout << "PayloadRegistration(): " << ex.what();
		throw;
	}

	if (root.isMember(PREGTag))				tag = root[PREGTag].asString();
	if (root.isMember(PREGDesc)) 				description = root[PREGDesc].asString();
	if (root.isMember(PREGBoardAddress))		boardAddress = root[PREGBoardAddress].asUInt();
	if (root.isMember(PREGReportingAddress))	reportingAddress = root[PREGReportingAddress].asUInt();
	if (root.isMember(PREGCORBAName))			corbaName = root[PREGCORBAName].asString();
	if (root.isMember(PREGHostID))				hostID = root[PREGHostID].asString();

	//Json::Value defaults;

	//if ((defaults = root[PREGDefaults]).isObject())
	//{
	//	enabled = defaults.isMember(PREGEnabled) && defaults[PREGEnabled].asBool();
	//	generateAlarms = defaults.isMember(PREGGenAlarms) && defaults[PREGGenAlarms].asBool();
	//}
}

string RegulusREST::PayloadRegistration::ToJSON(const string& fileName) const
{
	Json::Value root;
	try {
		root = FileToJson(fileName);
	}
	catch(exception& ex) {
		cout << "ToJSON: " << ex.what() << endl;
	}

	root[PREGTag] = tag;
	root[PREGDesc] = description;
	root[PREGBoardAddress] = boardAddress;
	root[PREGReportingAddress] = reportingAddress;
	root[PREGCORBAName] = corbaName;
	root[PREGHostID] = hostID;

	//Json::Value defaults;

	//if (!(defaults = root[PREGDefaults]).isObject())
	//	defaults = Json::Value();

	//defaults[PREGEnabled] = enabled;
	//defaults[PREGGenAlarms] = generateAlarms;
	//root[PREGDefaults] = defaults;

	const string result = root.toStyledString();
	return result;
}

RegulusREST::RemoteCommand::RemoteCommand()
: returnFile(false)
{
}

RegulusREST::RemoteCommand::RemoteCommand(const string& json)
: returnFile(false)
{
	Json::Value root;

	try {
		root = StringToJson(json);
	}
	catch(RegulusREST::JSONParsingError& ex)
	{
		stringstream ss;
		ss << "Failed to parse command to JSON (" << typeid(ex).name() << ": " << ex.what() << ")" << endl;
		const string sstr = ss.str();
		throw JSONParsingError(sstr);
	}

	const string idTag = "id";
	const string commandTag = "command";

	if (!root[idTag].isString())
		throw RegulusREST::RemoteCommandFormatError("Missing string element 'id'");
	else if (!root[commandTag].isString())
		throw RegulusREST::RemoteCommandFormatError("Missing string element 'cmd'");
	else
	{
		serverTag = root[idTag].asString();
		string cmd = root[commandTag].asString();
		returnFile = boost::istarts_with(cmd, "file:");
		if (returnFile) cmd = cmd.substr(5);
		bool foundResponseTag = false;
		if ((cmd.length() >= 2) && ((cmd[0] == '\'') || (cmd[0] == '\"'))) {
			size_t endMarker = cmd.find(cmd[0],1);
			foundResponseTag = (endMarker != cmd.npos);
			if (foundResponseTag) {
				responseTag = cmd.substr(1,endMarker-1);
				commandContent = cmd.substr(endMarker+1);
			}
		}
		if (!foundResponseTag)
			throw RegulusREST::RemoteCommandFormatError("Response tag not found at start of message");
	}
}

RegulusREST::GPSInfo::GPSInfo()
: valid(false)
, error(false)
, latitude(NAN)
, longitude(NAN)
, timestamp(NAN)
{
}

RegulusREST::GPSInfo::GPSInfo(const string& json)
: valid(true)
, error(false)
, latitude(NAN)
, longitude(NAN)
, timestamp(NAN)
{
	Json::Value root;

	try {
		root = StringToJson(json);
	}
	catch(RegulusREST::JSONParsingError& ex)
	{
		stringstream ss;
		ss << "Failed to parse gps info to JSON (" << typeid(ex).name() << ": " << ex.what() << ")" << endl;
		const string sstr = ss.str();
		throw JSONParsingError(sstr);
	}

	const string errorMessageTag = "error";
	const string latitudeTag = "latitude";
	const string longitudeTag = "longitude";
	const string timeTag = "timeFix";

	error = root[errorMessageTag].isString();
	if (error)								errorMessage = root[errorMessageTag].asString();
	if (root[latitudeTag].isNumeric())		latitude = root[latitudeTag].asDouble();
	if (root[longitudeTag].isNumeric())	longitude = root[longitudeTag].asDouble();
	if (root[timeTag].isNumeric())			timestamp = root[timeTag].asDouble();

	if (!error && (isnan(latitude) | isnan(longitude) || isnan(timestamp)))
	{
		error = true;
		errorMessage = "One or more fields are not valid";
	}
}

RegulusREST::ShutdownInfo::ShutdownInfo()
: shutdown(false)
{
}

RegulusREST::ShutdownInfo::ShutdownInfo(const string& json)
: shutdown(false)
{
}

static const string PRHHardware = "hardware";
static const string PRHManufacturer = "manufacturer";
static const string PRHModel = "model";
static const string PRHPartNumber = "pn";
static const string PRHSerialNumber = "serial";
static const string PRHSWVer = "swver";
static const string PRHTTY = "tty";
static const string PRHPower = "power";
static const string PRHAMPSPower = "connector";

RegulusREST::PRHardware::PRHardware()
: powerSpecified(false)
{
}

//! Initializes from a single element
void RegulusREST::PRHardware::InitFromJSON(
		const string& json						/**< [in] json string for this entry */)
{
	*this = PRHardware();
	Json::Value ob = StringToJson(json);
	Json::Value o;
	if ((o = ob[PRHManufacturer]).isString())	manufacturer = o.asString();
	if ((o = ob[PRHModel]).isString())			model = o.asString();
	if ((o = ob[PRHPartNumber]).isString())	pn = o.asString();
	if ((o = ob[PRHSerialNumber]).isString())	serialNumber = o.asString();
	if ((o = ob[PRHSWVer]).isString())			swver = o.asString();
	if ((o = ob[PRHTTY]).isString())			tty = o.asString();
	if ((o = ob[PRHPower]).isInt())				{ powerSwitch = o.asInt(); powerSpecified = true; }
	if ((o = ob[PRHAMPSPower]).isString())		ampsPowerSwitch = o.asString();
}

void RegulusREST::PRHardware::InitFromRegJSON(
		const string& json,						/**< [in] registration json string */
		Vector& hardware						/**< [in,out] array of hardware values to fill out */)
{
	Json::Value root = StringToJson(json);
	Json::Value hw = root[PRHHardware];

	hardware.clear();

	if (hw.isArray()) {
		const unsigned arraySize = hw.size();
		for (unsigned index = 0; index < arraySize; ++index) {
			PRHardware item;
			item.InitFromJSON(hw[index].toStyledString());
			hardware.push_back(item);
		}
	}
}

string RegulusREST::PRHardware::UpdateJSON(
		const string& json   					/**< [in] original json string */,
		const Vector& hardware					/**< [in] array of hardware values to merge in */)
{
	Json::Value root = StringToJson(json);

	if (hardware.size())
	{
		Json::Value ar;

		for (Vector::const_iterator ci = hardware.begin(); ci != hardware.end(); ++ci)
		{
			if (!ci->manufacturer.empty()
			 || !ci->model.empty()
			 || !ci->pn.empty()
			 || !ci->serialNumber.empty()
		     || !ci->swver.empty()
		     || !ci->tty.empty()
		     || ci->powerSpecified
		     || !ci->ampsPowerSwitch.empty())
			{
				Json::Value ob;
				if (!ci->manufacturer.empty())	ob[PRHManufacturer] = ci->manufacturer;
				if (!ci->model.empty())			ob[PRHModel] = ci->model;
				if (!ci->pn.empty())			ob[PRHPartNumber] = ci->pn;
				if (!ci->serialNumber.empty())	ob[PRHSerialNumber] = ci->serialNumber;
				if (!ci->swver.empty())			ob[PRHSWVer] = ci->swver;
				if (!ci->tty.empty())			ob[PRHTTY] = ci->tty;
				if (ci->powerSpecified)			ob[PRHPower] = ci->powerSwitch;
				if (!ci->ampsPowerSwitch.empty()) ob[PRHAMPSPower] = ci->ampsPowerSwitch;

				ar.append(ob);
			}
		}

		root[PRHHardware] = ar;
	}
	else
		root.removeMember(PRHHardware);

	const string result = root.toStyledString();
	return result;
}

static const string PRIInterface = "interface";
static const string PRIPartNumber = "pn";
static const string PRISWVer = "swver";

RegulusREST::PRInterface::PRInterface()
{
}

//! Reads from JSON for just this item
void RegulusREST::PRInterface::InitFromJSON(
		const string& json						/**< [in] json string for this item */)
{
	Json::Value ob = StringToJson(json);
	if (ob.isObject()) {
		Json::Value o;
		*this = PRInterface();
		if ((o = ob[PRIPartNumber]).isString())	pn = o.asString();
		if ((o = ob[PRISWVer]).isString())			swver = o.asString();
	}
}

//! Reads from registration JSON and initializes PRInterface
void RegulusREST::PRInterface::InitFromRegJSON(
		const string& json						/**< [in] registration json string */)
{
	Json::Value root = StringToJson(json);
	InitFromJSON(root[PRIInterface].toStyledString());
}

string RegulusREST::PRInterface::UpdateJSON(
		const string& json   					/**< [in] original json string */) const
{
	Json::Value root = StringToJson(json);

	if (!pn.empty() || !swver.empty())
	{
		Json::Value ar;
		Json::Value ob;

		if (!pn.empty()) ob[PRIPartNumber] = pn;
		if (!swver.empty()) ob[PRISWVer] = swver;

		ar.append(ob);
		root[PRIInterface] = ar;
	}
	else
		root.removeMember(PRIInterface);

	const string result = root.toStyledString();
	return result;
}

}
}
