//-----------------------------------------------------------------------------------
// <copyright file="TelemetryBuffer.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>
//   Declarations for TelemetryBuffer, a collection of telemetry reports and settings
//   in progress.
// </summary>
//
// <owner>Mike Cookson</owner>
//-----------------------------------------------------------------------------------

#include "TelemetryBuffer.h"
#include "EPVFormatConfig.h"
#include "EventPropertyValue.h"
#include "IEventBase.h"
#include "PayloadManagerClient.h"
#include "TelemetryEventBase.h"
#include "TelemetrySettingsChangedEvent.h"
#include "TelemetryPublisher.h"
#include "pugixml.hpp"
#include <cerrno>
#include <iostream>
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/foreach.hpp>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

using namespace std;
using namespace boost::archive;
using namespace boost::serialization;
using namespace LiquidRobotics::SMACFramework;
using namespace LiquidRobotics::SMACFramework::XmlDOM;
using namespace LiquidRobotics::EventLib;
using namespace pugi;

namespace LiquidRobotics {
namespace PayloadInterface {

TelemetryBuffer::Config::Config(IObjectBroker& _broker, PayloadManagerClient& _pmclient)
: broker(_broker)
, pmclient(_pmclient)
, fieldLibrary(_broker)
{
	fieldLibrary.Add("FieldName1", EPVFormatConfig(broker));
	fieldLibrary.Add("FieldName2", EPVFormatConfig(broker, EPVFormatConfig::CA_RAW, 20, 25));

	EventPropertyValue temp(broker);
	temp.SetString("MyLiteral");
	fieldLibrary.Add("FieldName3", EPVFormatConfig(broker, temp));

	KeyToPublisher keyToPub;
	TelemetryPublisherKey telemPubKey(0x20, 0x99);
	TelemetryPublisher::Ptr pub;
	pub.reset(new TelemetryPublisher(telemPubKey, 123));
	TelemetryFieldList header, sample;
	header.Add("FieldName1", "Data1");
	sample.Add("FieldName2", "Data2");
	sample.Add("FieldName3");
	TelemetryReportParameters params1(8, (time_t) -1, 2, header, sample);
	pub->SetReportParameters(1, params1);
	pub->SetFormatCodeAndEvent(0x1, "MySampleEvent");
	keyToPub[telemPubKey] = pub;
	SetPublishers(keyToPub);
}

TelemetryBuffer::Config::Config(const Config& other)
: broker(other.broker)
, pmclient(other.pmclient)
, fieldLibrary(other.broker)
{
	DeepCopy(other);
}

TelemetryBuffer::Config& TelemetryBuffer::Config::operator=(const Config& other)
{
	if (this != &other)
		DeepCopy(other);

	return *this;
}

const TelemetryFieldLibrary& TelemetryBuffer::Config::GetFieldLibrary() const
{
	return fieldLibrary;
}

void TelemetryBuffer::Config::SetFieldLibrary(const TelemetryFieldLibrary& newValue)
{
	fieldLibrary = newValue;
}

TelemetryBuffer::KeyToPublisher& TelemetryBuffer::Config::GetPublishers()
{
	return publishers;
}

const TelemetryBuffer::KeyToPublisher& TelemetryBuffer::Config::GetPublishers() const
{
	return publishers;
}

void TelemetryBuffer::Config::SetPublishers(const KeyToPublisher& newValue)
{
	if (&newValue != &publishers)
	{
		publishers.clear();

		for (KeyToPublisher::const_iterator __it = newValue.begin(); __it != newValue.end(); ++__it)
		{
			const KeyToPublisher::value_type& item = *__it;

			TelemetryPublisher::Ptr newItem;
			newItem.reset(new TelemetryPublisher(*item.second));

			publishers[item.first] = newItem;
		}
	}
}

void TelemetryBuffer::Config::DeepCopy(const Config& other)
{
	SetPublishers(other.publishers);
	fieldLibrary = other.fieldLibrary;
}

void TelemetryBuffer::Config::UpdateBoardID()
{
	const PayloadManagerClient::byte defaultBoardID = pmclient.GetDefaultBoardID();

	for (KeyToPublisher::iterator it = publishers.begin(); it != publishers.end(); )
	{
		const PayloadManagerClient::byte originalBoardID = it->first.GetBoardID();
		const PayloadManagerClient::byte translatedBoardID = pmclient.TranslateBoardID(originalBoardID, defaultBoardID);

		if (originalBoardID != translatedBoardID)
		{
			TelemetryPublisherKey newKey(translatedBoardID, it->first.GetTaskID());
			TelemetryPublisher::Ptr saved = it->second;
			publishers.erase(it);
			saved->SetPublisherKey(newKey);
			publishers[newKey] = saved;
			it = publishers.begin();
		}
		else
			++it;
	}
}

static const char rootLevel[] = { "rootlevel" };
static const char className[] = { "TelemetryBuffer::Config" };

void TelemetryBuffer::Config::Load(const string& fileName)
{
	LoadFromFile(fileName, *this);
	UpdateBoardID();
}

void TelemetryBuffer::Config::Save(const string& fileName) const
{
	SaveToFile(fileName, *this);
}

void TelemetryBuffer::Config::Load(XmlNode& node, const XmlNVPBase& nvp)
{
	node >> XmlNVP<KeyToPublisher>("publishers", publishers)
	     >> XmlNVP<TelemetryFieldLibrary>("fieldLibrary", fieldLibrary);

	UpdateBoardID();
}

void TelemetryBuffer::Config::Save(XmlNode& node, const XmlNVPBase& nvp) const
{
	node << XmlNVP<KeyToPublisher>("publishers", publishers)
	     << XmlNVP<TelemetryFieldLibrary>("fieldLibrary", fieldLibrary);
}


//const IEventBase::PropertyNameType TelemetryBuffer::boardIDTag = "TELEM_BOARD_ID";
const IEventBase::PropertyNameType TelemetryBuffer::formatCodeTag = "Telem-FormatCode";
const IEventBase::PropertyNameType TelemetryBuffer::lengthToFollowTag = "Telem-LengthToFollow";
const IEventBase::PropertyNameType TelemetryBuffer::numSamplesTag = "Telem-NumSamples";
const IEventBase::PropertyNameType TelemetryBuffer::payloadTypeTag = "Telem-PayloadType";
//const IEventBase::PropertyNameType TelemetryBuffer::taskIDTag = "TELEM_TASK_ID";

TelemetryBuffer::TelemetryBuffer(IObjectBroker& _broker, PayloadManagerClient& _pmclient)
: telemetryReportSubscriber(telemetryReportSignal, firingMutex)
, broker(_broker)
, config(_broker, _pmclient)
, savedLatitude(0)
, savedLongitude(0)
{
}

TelemetryBuffer::TelemetryBuffer(IObjectBroker& _broker, const Config& _config)
: telemetryReportSubscriber(telemetryReportSignal, firingMutex)
, broker(_broker)
, config(_config)
, savedLatitude(0)
, savedLongitude(0)
{
}

TelemetryBuffer::TelemetryBuffer(const TelemetryBuffer& other)
: telemetryReportSubscriber(telemetryReportSignal, firingMutex)
, broker(other.broker)
, config(other.config)
, savedLatitude(other.savedLatitude)
, savedLongitude(other.savedLongitude)
, timeoutWakeup(0)
{
	ILockableConceptLock lock(const_cast<LockableConceptMutex&>(other.localMutex));
	DeepCopy(keyToReport, other.keyToReport);
}

TelemetryBuffer& TelemetryBuffer::operator=(const TelemetryBuffer& other)
{
	ILockableConceptLock lock(localMutex);

	if (this != &other)
	{
		config = other.config;
		savedLatitude = other.savedLatitude;
		savedLongitude = other.savedLongitude;
		ILockableConceptLock lock(const_cast<LockableConceptMutex&>(other.localMutex));
		DeepCopy(keyToReport, other.keyToReport);
	}

	return *this;
}

void TelemetryBuffer::ClearPending(TelemetryPublisherKey publisher)
{
	const KeyToPublisher& pubs = config.GetPublishers();
	KeyToPublisher::const_iterator pubit = pubs.find(publisher);

	if (pubit == pubs.end())
	{
		stringstream msg;
		msg << "No publisher found for key { " << publisher << " }";
		throw invalid_argument(msg.str());
	}

	for (KeyToReport::iterator it = keyToReport.begin(); it != keyToReport.end(); ++it)
		if (it->first.GetPublisherKey() == publisher)
			it->second->Clear();
}

void TelemetryBuffer::ClearPending(TelemetryPublisherKey publisher, TelemetryPublisher::FormatCode formatCode)
{
	ILockableConceptLock lock(localMutex);
	const KeyToPublisher& pubs = config.GetPublishers();
	KeyToPublisher::const_iterator pubit = pubs.find(publisher);

	if (pubit == pubs.end())
	{
		stringstream msg;
		msg << "No publisher found for key { " << publisher << " }";
		throw invalid_argument(msg.str());
	}

	if (!pubit->second->HaveReportParameters(formatCode))
	{
		stringstream msg;
		msg << "Publisher { " << publisher << " } does not contain publish format code " << formatCode;
		throw invalid_argument(msg.str());
	}

	TelemetryReportKey key;
	key.SetPublisherKey(publisher);
	key.SetFormatCode(formatCode);

	KeyToReport::iterator it = keyToReport.find(key);

	if (it != keyToReport.end())
		it->second->Clear();

	if (timeoutThread) timeoutWakeup.Post();
}

ostream& TelemetryBuffer::DescribeState(ostream& stm) const
{
	ILockableConceptLock lock(const_cast<LockableConceptMutex&>(localMutex));
	const KeyToPublisher& pubs = config.GetPublishers();

	for (KeyToPublisher::const_iterator __it = pubs.begin(); __it != pubs.end(); ++__it)
	{
		const KeyToPublisher::value_type& pub = *__it;
		TelemetryPublisher::FormatCodeArray formatCodes;
		pub.second->GetFormatCodes(formatCodes);

		for (TelemetryPublisher::FormatCodeArray::const_iterator __it2 = formatCodes.begin(); __it2 < formatCodes.end(); ++__it2)
		{
			TelemetryPublisher::FormatCode formatCode = *__it2;
			stm << "Publisher " << *pub.second;

			TelemetryReportKey rptkey;
			rptkey.SetPublisherKey(pub.first);
			rptkey.SetFormatCode(formatCode);

			KeyToReport::const_iterator it = keyToReport.find(rptkey);

			if (it != keyToReport.end())
				stm << " " << it->second->GetNumSamples();
			else
				stm << " no samples for format code " << formatCode;

			stm << endl;
		}
	}

	return stm;
}

const TelemetryBuffer::Config& TelemetryBuffer::GetConfig() const
{
	return config;
}

void TelemetryBuffer::ProcessEvent(const IEventBase& evt)
{
	ILockableConceptLock lock(localMutex);

	// look up the publisher by board ID/task ID
	TelemetryPublisherKey pubKey;
	TelemetryPublisher::Ptr pub;

	if (!GetEventInfo(evt, pubKey, pub))
		return;

	// look up the format code
	FormatCode formatCode = pub->GetFormatCodeForEvent(evt.GetEventName());

	// make a copy of the received event
	IEventBase::Ptr copy = evt.Duplicate();

	// add one-time fields if they don't exist in the received event
	EventPropertyValue evp(broker);
	evp.SetUInt32(pub->GetPayloadType());
	copy->SetProperty(payloadTypeTag, evp);

	evp.SetUInt16(formatCode);
	copy->SetProperty(formatCodeTag, evp);

	if (!evt.ContainsProperty(TelemetryEventBase::T_GenerationTimeTagSec))
	{
		time_t timeNow;
		time(&timeNow);

		evp.SetUInt32(timeNow);
		copy->SetProperty(TelemetryEventBase::T_GenerationTimeTagSec, evp);
	}

	if (!evt.ContainsProperty(TelemetryEventBase::T_LatitudeTag)
	 || !evt.ContainsProperty(TelemetryEventBase::T_LongitudeTag))
	{
		evp.SetInt32(TelemetryEventBase::ConvertDoublePosToInt(savedLatitude));
		copy->SetProperty(TelemetryEventBase::T_LatitudeTag, evp);

		evp.SetInt32(TelemetryEventBase::ConvertDoublePosToInt(savedLongitude));
		copy->SetProperty(TelemetryEventBase::T_LongitudeTag, evp);
	}
	else
	{
		const uint32_t lat0 = TelemetryEventBase::ConvertDoublePosToInt(evt.GetProperty(TelemetryEventBase::T_LatitudeTag).GetDoubleFloat());
		const uint32_t lon0 = TelemetryEventBase::ConvertDoublePosToInt(evt.GetProperty(TelemetryEventBase::T_LongitudeTag).GetDoubleFloat());

		evp.SetInt32(lat0);
		copy->SetProperty(TelemetryEventBase::T_LatitudeTag, evp);

		evp.SetInt32(lon0);
		copy->SetProperty(TelemetryEventBase::T_LongitudeTag, evp);
	}

	// find any existing report, create a new one if none exists
	TelemetryReportKey repKey(pubKey.GetBoardID(), pubKey.GetTaskID(), formatCode);
	KeyToReport::iterator repit = keyToReport.find(repKey);
	TelemetryReport::Ptr rep;

	if (repit == keyToReport.end())
	{
		rep.reset(new TelemetryReport);
		keyToReport[repKey] = rep;
		repit = keyToReport.find(repKey);
	}
	else
	{
		rep = repit->second;

		if (!rep)
		{
			rep.reset(new TelemetryReport);
			repit->second = rep;
		}
	}

	// fill out a new sample
	const uint16_t newSampleCount = rep->GetNumSamples()+1;
	evp.SetUInt16(newSampleCount);
	copy->SetProperty(numSamplesTag, evp);

	const TelemetryReportParameters& repParam = pub->GetReportParameters(formatCode);
	ByteArray sample;

	Format(*copy, *pub, repParam.GetSampleHeaderFormat(), 0, sample);

	// fill out a new header
	ByteArray header;

	Format(*copy, *pub, repParam.GetReportHeaderFormat(), rep->GetSamples().size()+sample.size(), header);

	// update the report
	rep->SetHeader(header);
	rep->AddSample(sample);

	// if the number of samples is now enough to publish, push them
	if (repParam.GetMaxSamples() <= newSampleCount) {
		lock.unlock();
		PublishPending(pubKey, formatCode);
	}
}

void TelemetryBuffer::ProcessTelemetryChangedEvent(const TelemetrySettingsChangedEvent& evt)
{
	ILockableConceptLock lock(localMutex);

	// look up the publisher by board ID/task ID
	TelemetryPublisherKey pubKey;
	TelemetryPublisher::Ptr pub;

	if (GetEventInfo((const IEventBase&) evt, pubKey, pub, true))
	{
		// look up the format code
		FormatCode formatCode = evt.GetFormatCode();

		// handle pending messages
		switch(evt.GetPendingMessageTreatment())
		{
		case TelemetrySettingsChangedEvent::PMT_CLEAR:

			ClearPending(pubKey, formatCode);
			break;

		case TelemetrySettingsChangedEvent::PMT_PUBLISH:

			PublishPending(pubKey, formatCode);
			break;

		case TelemetrySettingsChangedEvent::PMT_RETAIN:
		default:

			break;
		}

		// update max retain time if specified
		if (evt.HaveMaxRetainTime())
			SetMaxRetainTime(pubKey, formatCode, evt.GetMaxRetainTime());

		// update the max number of samples if specified
		if (evt.HaveMaxSamples())
			SetMaxSamples(pubKey, formatCode, evt.GetMaxSamples());
	}
}

bool TelemetryBuffer::GetEventInfo(const IEventBase& evt, TelemetryPublisherKey& pubKey, TelemetryPublisher::Ptr& pub, bool formatCodeCase) const
{
	// reject events that don't implement the required fields
	const IEventBase::PropertyNameType* pMissing = NULL;

	if (!evt.ContainsProperty(TelemetryEventBase::T_BoardIDTag))
		pMissing = &TelemetryEventBase::T_BoardIDTag;
	else if (!evt.ContainsProperty(TelemetryEventBase::T_TaskIDTag))
		pMissing = &TelemetryEventBase::T_TaskIDTag;

	if (pMissing)
		return false;

	// look up the publisher by board ID/task ID
	TelemetryPublisherKey::BoardID boardID;
	TelemetryPublisherKey::TaskID taskID;

	boardID = evt.GetProperty(TelemetryEventBase::T_BoardIDTag).GetUInt8();
	taskID = evt.GetProperty(TelemetryEventBase::T_TaskIDTag).GetUInt8();

	pubKey.SetBoardID(boardID);
	pubKey.SetTaskID(taskID);

	const KeyToPublisher& pubs = config.GetPublishers();
	KeyToPublisher::const_iterator pubit = pubs.find(pubKey);

	if (pubit == pubs.end())
		return false;

	pub = pubit->second;

	// stop if there's no published event based on name
	if (!formatCodeCase && !pub->HaveFormatCodeForEvent(evt.GetEventName()))
		return false;

	// stop on invalid field
	const IEventBase::PropertyNameType* pInvalid = NULL;

	if (!formatCodeCase && evt.ContainsProperty(formatCodeTag))
		pInvalid = &formatCodeTag;
	else if (evt.ContainsProperty(lengthToFollowTag))
		pInvalid = &lengthToFollowTag;
	else if (evt.ContainsProperty(numSamplesTag))
		pInvalid = &numSamplesTag;
	else if (evt.ContainsProperty(payloadTypeTag))
		pInvalid = &payloadTypeTag;

	if (pInvalid)
	{
		stringstream msg;
		msg << "(TelemetryBuffer::ProcessEvent) Event '" << evt.GetEventName() << "' contains reserved property '" << *pInvalid << "'";
		throw invalid_argument(msg.str());
	}

	return true;
}

void TelemetryBuffer::Format(IEventBase& evt, const TelemetryPublisher& pub, const TelemetryFieldList& fieldList, uint16_t bytesToFollow, ByteArray& seq)
{
	const TelemetryFieldLibrary& fieldLibrary = config.GetFieldLibrary();
	EventPropertyValue evp(broker);
	evp.SetUInt32(pub.GetPayloadType());
	evt.SetProperty(payloadTypeTag, evp);

	for (size_t idx = fieldList.Count(); idx--; )
	{
		evp.SetUInt16(bytesToFollow+seq.size());
		evt.SetProperty(lengthToFollowTag, evp);

		const EPVFormatConfig& fmt = fieldLibrary.Lookup(fieldList.GetLibFieldName(idx));
		const string eventField = fieldList.GetEventFieldName(idx);
		const EventPropertyValue* pItem;

		EPVFormatConfig fmt2(fmt);

		if (fmt.GetContentAlgorithm() == EPVFormatConfig::CA_LITERAL)
		{
			fmt2.SetContentAlgorithm(fmt2.GetLiteralContentAlgorithm());
			pItem = &fmt.GetLiteral();
		}
		else
			pItem = &evt.GetProperty(eventField);

		ByteArray temp;
		pItem->ToBinary(temp, fmt2);

		if (temp.size())
			seq.insert(seq.begin(), temp.begin(), temp.end());
	}
}

void TelemetryBuffer::PublishPending(TelemetryPublisherKey publisher)
{
	typedef std::list<EventItemPtr> EventItemPtrList;
	EventItemPtrList events;
	ILockableConceptLock lock(localMutex);

	for (KeyToReport::iterator it = keyToReport.begin(); it != keyToReport.end(); ++it)
	{
		if (it->first.GetPublisherKey() == publisher)
			if (it->second)
				events.push_back(FormatEventItem(it->first, *it->second));
	}

	lock.unlock();
	Publish(events);
}

void TelemetryBuffer::PublishPending(TelemetryPublisherKey publisher, TelemetryPublisher::FormatCode formatCode)
{
	EventItemPtr eventp;
	ILockableConceptLock lock(localMutex);
	TelemetryReportKey rptKey(publisher.GetBoardID(), publisher.GetTaskID(), formatCode);
	KeyToReport::iterator it = keyToReport.find(rptKey);

	if (it != keyToReport.end())
		if (it->second)
			eventp = FormatEventItem(it->first, *it->second);

	lock.unlock();
	Publish(eventp);
}

void TelemetryBuffer::Publish(TelemetryReportKey rptKey, TelemetryReport& report)
{
	Publish(FormatEventItem(rptKey, report));
}

void TelemetryBuffer::PublishTimedOut(Logger& logger)
{
	typedef std::list<EventItemPtr> EventItemPtrList;
	EventItemPtrList events;

	ILockableConceptLock lock(localMutex);

	// for each item in the report map ...
	for (KeyToReport::iterator it = keyToReport.begin(); it != keyToReport.end(); ++it) {

		// if there is a report item attached with samples ...
		if (it->second && it->second->GetNumSamples())
		{
			// tease out the publishers list and look up the publisher
			KeyToPublisher& pubs = config.GetPublishers();
			KeyToPublisher::const_iterator pubIt = pubs.find(it->first.GetPublisherKey());

			// if the publisher was found ...
			if (pubIt != pubs.end())
			{

				// tease out the report
				TelemetryReport& rept = *it->second;

				// get the parameters for the report and the max retain time
				const TelemetryReportParameters& rptPrm = pubIt->second->GetReportParameters(it->first.GetFormatCode());
				const time_t retainTime = rptPrm.GetMaxRetainTime();

				// if the max retain time is not -1 and enough time has elapsed, publish it
				if ((retainTime != (time_t) -1) && (retainTime < rept.GetDwellTime()))
					events.push_back(FormatEventItem(it->first, rept));
			}
		}
	}

	lock.unlock();
	Publish(events);
}

void TelemetryBuffer::SetMaxRetainTime(TelemetryPublisherKey publisher, FormatCode formatCode, time_t newValue)
{
	ILockableConceptLock lock(localMutex);
	KeyToPublisher& pubs = config.GetPublishers();
	KeyToPublisher::iterator pubIt = pubs.find(publisher);

	if (pubIt == pubs.end())
		throw invalid_argument("(TelemetryBuffer::SetMaxRetainTime) Publisher not found");

	TelemetryPublisher& pub = *pubIt->second;

	if (!pub.HaveReportParameters(formatCode))
		throw invalid_argument("(TelemetryBuffer::SetMaxRetainTime) Format code not found for publisher");

	TelemetryReportParameters& reptParams = pub.GetReportParameters(formatCode);
	reptParams.SetMaxRetainTime(newValue);
	if (timeoutThread) timeoutWakeup.Post();
}

void TelemetryBuffer::SetMaxSamples(TelemetryPublisherKey publisher, FormatCode formatCode, uint16_t newValue)
{
	ILockableConceptLock lock(localMutex);
	KeyToPublisher& pubs = config.GetPublishers();
	KeyToPublisher::iterator pubIt = pubs.find(publisher);

	if (pubIt == pubs.end())
		throw invalid_argument("(TelemetryBuffer::SetMaxSamples) Publisher not found");

	TelemetryPublisher& pub = *pubIt->second;

	if (!pub.HaveReportParameters(formatCode))
		throw invalid_argument("(TelemetryBuffer::SetMaxSamples) Format code not found for publisher");

	TelemetryReportParameters& reptParams = pub.GetReportParameters(formatCode);
	reptParams.SetMaxSamples(newValue);
	if (timeoutThread) timeoutWakeup.Post();
}

void TelemetryBuffer::DeepCopy(KeyToReport& destination, const KeyToReport& source)
{
	destination.clear();

	for(KeyToReport::const_iterator it = source.begin(); it != source.end(); ++it)
	{
		const KeyToReport::value_type& item = *it;
		TelemetryReport::Ptr newVal;

		newVal.reset(new TelemetryReport(*item.second));
		destination[item.first] = newVal;
	}
}

void TelemetryBuffer::SavePosition(double newLat, double newLon)
{
	ILockableConceptLock lock(localMutex);
	savedLatitude = newLat;
	savedLongitude = newLon;
}

std::ostream& operator<<(std::ostream& stm, const TelemetryBuffer::Config& settings)
{
	stm << "not implemented";
	return stm;
}

void TelemetryBuffer::InitializeAutoPublishWaitingTelemetry(Logger& logger)
{
	timeoutThread.reset(new boost::thread(&TelemetryBuffer::TimeoutThread, this, &logger));
}

void TelemetryBuffer::TimeoutThread(Logger* loggerp)
{
	Logger::Locked(*loggerp)->Info << "Starting telemetry buffer timeout thread" << endl;
	ILockableConceptLock lock(localMutex);

	while(true)
	{
		unsigned ms = ComputeNextTimeout();
		lock.unlock();
		if (~ms) {
			Logger::Locked(*loggerp)->Info << "Telemetry buffer timeout thread waiting " << ms << "ms" << endl;
			timeoutWakeup.TimedWait(ms);
		}
		else {
			Logger::Locked(*loggerp)->Info << "Telemetry buffer timeout thread waiting indefinitely" << endl;
			timeoutWakeup.Wait();
		}
		timeoutWakeup.SetCount(0);
		lock.lock();
		PublishTimedOut(*loggerp);
	}
}

unsigned TelemetryBuffer::ComputeNextTimeout() const
{
	ILockableConceptLock lock(const_cast<LockableConceptMutex&>(localMutex));
	unsigned result = ~0;

	// for each item in the report map ...
	for (KeyToReport::const_iterator it = keyToReport.begin(); result && (it != keyToReport.end()); ++it) {

		// if there is a report item attached with samples ...
		if (it->second && it->second->GetNumSamples())
		{
			// tease out the publishers list and look up the publisher
			const KeyToPublisher& pubs = config.GetPublishers();
			KeyToPublisher::const_iterator pubIt = pubs.find(it->first.GetPublisherKey());

			// if the publisher was found ...
			if (pubIt != pubs.end())
			{

				// tease out the report
				const TelemetryReport& rept = *it->second;

				// get the parameters for the report and the max retain time
				const TelemetryReportParameters& rptPrm = pubIt->second->GetReportParameters(it->first.GetFormatCode());
				const time_t retainTime = rptPrm.GetMaxRetainTime();

				// if the max retain time is not -1 and enough time has elapsed, publish it
				if (retainTime != (time_t) -1) {
					const time_t dwellTime = rept.GetDwellTime();
					if (retainTime < dwellTime) {
						result = 0;
						break;
					}
					else {
						const unsigned timeLeftSec = (unsigned) (retainTime-dwellTime);
						result = min(result, 1000*timeLeftSec);
					}
				}
			}
		}
	}

	return result;
}

TelemetryBuffer::EventItemPtr TelemetryBuffer::FormatEventItem(TelemetryReportKey rptKey, TelemetryReport& report)
{
	TelemetryBuffer::EventItemPtr result;
	ILockableConceptLock lock(localMutex);

	if (report.GetNumSamples())
	{
		result.reset(new EventItem);
		result->contentp = report.GetFormattedMessage();
		result->reportKey = rptKey;
		result->targetAddress = config.GetPublishers()[rptKey.GetPublisherKey()]->GetReportParameters(rptKey.GetFormatCode()).GetTargetAddress();
		report.Clear();
	}

	return result;
}

void TelemetryBuffer::Publish(EventItemPtr eventp)
{
	if (eventp) {
		ILockableConceptLock firingLock(firingMutex);
		telemetryReportSignal(*this, eventp->reportKey, eventp->targetAddress, *eventp->contentp);
	}
}

void TelemetryBuffer::Publish(EventItemPtrList& events)
{
	for(EventItemPtrList::iterator it = events.begin(); it != events.end(); ++it)
		Publish(*it);
}

}
}
