void WebSocketSinkManager::addSink(libwebsocket* socket, VehicleProperty::Property property,string uuid)
{
	PropertyList foo = VehicleProperty::capabilities();
	if (!ListPlusPlus<VehicleProperty::Property>(&foo).contains(property))
	{
		DebugOut(DebugOut::Warning)<<"Invalid property requested: "<<property<<endl;
		return;
	}

	QVariantMap reply;

	reply["type"] = "methodReply";
	reply["name"] = "subscribe";
	reply["data"] = property.c_str();
	reply["transactionid"] = uuid.c_str();

	QByteArray replystr;

	if(doBinary)
		replystr = QJsonDocument::fromVariant(reply).toBinaryData();
	else
	{
		replystr = QJsonDocument::fromVariant(reply).toJson();
		cleanJson(replystr);
	}

	lwsWrite(socket, replystr, replystr.length());

	WebSocketSink *sink = new WebSocketSink(m_engine,socket,uuid,property,property);
	m_sinkMap[property].push_back(sink);
}
void WebSocketSinkManager::removeSink(libwebsocket* socket,VehicleProperty::Property property, string uuid)
{
	if (m_sinkMap.find(property) != m_sinkMap.end())
	{
		list<WebSocketSink*> sinks = m_sinkMap[property];

		for(auto i = sinks.begin(); i != sinks.end(); i++)
		{
			delete *i;
		}

		m_sinkMap.erase(property);

		QVariantMap reply;
		reply["type"]="methodReply";
		reply["name"]="unsubscribe";
		reply["data"]=property.c_str();
		reply["transactionid"]= uuid.c_str();

		QByteArray replystr;

		if(doBinary)
			replystr = QJsonDocument::fromVariant(reply).toBinaryData();
		else
		{
			replystr = QJsonDocument::fromVariant(reply).toJson();
			cleanJson(replystr);
		}

		lwsWrite(socket, replystr, replystr.length());
	}
}
void WebSocketSinkManager::addSingleShotSink(libwebsocket* socket, VehicleProperty::Property property, Zone::Type zone, string id)
{
	AsyncPropertyRequest request;
	PropertyList foo = VehicleProperty::capabilities();
	if (ListPlusPlus<VehicleProperty::Property>(&foo).contains(property))
	{
		request.property = property;
	}
	else
	{
		DebugOut(0)<<"websocketsink: Invalid property requested: "<<property;
		return;
	}

	request.zoneFilter = zone;
	request.completed = [socket,id,property](AsyncPropertyReply* reply)
	{
		DebugOut()<<"Got property: "<<reply->property.c_str()<<endl;
		if(!reply->success || !reply->value)
		{
			DebugOut()<<"Property value is null"<<endl;
			delete reply;
			return;
		}

		QVariantMap data;
		data["property"] = property.c_str();
		data["zone"] = reply->value->zone;
		data["value"] = reply->value->toString().c_str();
		data["timestamp"] = reply->value->timestamp;
		data["sequence"] = reply->value->sequence;

		QVariantMap replyvar;

		replyvar["type"]="methodReply";
		replyvar["name"]="get";
		replyvar["data"]= data;
		replyvar["transactionid"]=id.c_str();

		QByteArray replystr;

		if(doBinary)
			replystr = QJsonDocument::fromVariant(replyvar).toBinaryData();
		else
		{
			replystr = QJsonDocument::fromVariant(replyvar).toJson();
			cleanJson(replystr);
		}

		lwsWrite(socket, replystr, replystr.length());

		delete reply;
	};

	AsyncPropertyReply* reply = routingEngine->getPropertyAsync(request);
}
int lwsWriteVariant(libwebsocket *lws, QVariant d)
{
	QByteArray replystr;
	if(doBinary)
		replystr = QJsonDocument::fromVariant(d).toBinaryData();
	else
	{
		replystr = QJsonDocument::fromVariant(d).toJson();
		cleanJson(replystr);
	}

	lwsWrite(lws, replystr);
}
void WebSocketSinkManager::addSingleShotRangedSink(libwebsocket* socket, PropertyList properties, double start, double end, double seqstart,double seqend, string id)
{
	AsyncRangePropertyRequest rangedRequest;

	rangedRequest.timeBegin = start;
	rangedRequest.timeEnd = end;
	rangedRequest.sequenceBegin = seqstart;
	rangedRequest.sequenceEnd = seqend;

	rangedRequest.completed = [socket,id](AsyncRangePropertyReply* reply)
	{
		QVariantMap replyvar;
		QVariantList list;

		std::list<AbstractPropertyType*> values = reply->values;
		for(auto itr = values.begin(); itr != values.end(); itr++)
		{
			QVariantMap obj;
			obj["value"]= (*itr)->toString().c_str();
			obj["timestamp"] = (*itr)->timestamp;
			obj["sequence"] = (*itr)->sequence;

			list.append(obj);
		}

		replyvar["type"]="methodReply";
		replyvar["name"]="getRanged";
		replyvar["data"]=list;
		replyvar["transactionid"]=id.c_str();

		QByteArray replystr;

		if(doBinary)
			replystr = QJsonDocument::fromVariant(replyvar).toBinaryData();
		else
		{
			replystr = QJsonDocument::fromVariant(replyvar).toJson();
			cleanJson(replystr);
		}

		lwsWrite(socket, replystr, replystr.length());

		delete reply;
	};

	AsyncRangePropertyReply* reply = routingEngine->getRangePropertyAsync(rangedRequest);
}
void WebSocketSinkManager::setValue(libwebsocket* socket,VehicleProperty::Property property,string value,Zone::Type zone,string uuid)
{
	AbstractPropertyType* type = VehicleProperty::getPropertyTypeForPropertyNameValue(property,value);

	AsyncSetPropertyRequest request;
	request.property = property;
	request.value = type;
	request.zoneFilter = zone;
	request.completed = [&](AsyncPropertyReply* reply)
	{
		QVariantMap data;
		data["property"] = property.c_str();
		data["zone"] = zone;
		data["source"] = reply->value->sourceUuid.c_str();

		QVariantMap replyvar;
		replyvar["type"]="methodReply";
		replyvar["name"]="set";
		replyvar["data"]= data;
		replyvar["transactionid"]=uuid.c_str();

		QByteArray replystr;

		if(doBinary)
			replystr = QJsonDocument::fromVariant(replyvar).toBinaryData();
		else
		{
			replystr = QJsonDocument::fromVariant(replyvar).toJson();
			cleanJson(replystr);
		}

		lwsWrite(socket, replystr, replystr.length());

		delete reply;
	};

	m_engine->setProperty(request);
	DebugOut() << __SMALLFILE__ <<":"<< __LINE__ << "AbstractRoutingEngine::setProperty called with arguments:" << property << value << "\n";
	delete type;

}
int lwsWrite(libwebsocket *lws, QByteArray d, int len)
{
	if(!lws)
	{
		DebugOut(DebugOut::Error)<<__FUNCTION__<<": libwebsockets is not valid.  Perhaps it has not been initialized?"<<endl;
		return -1;
	}

	int retval = -1;

	QByteArray temp = d;

	int numframes = 1;
	int framesize = 122;

	if(d.length() > framesize)
	{
		numframes = qCeil((double)d.length() / 122.0);
		QVariantMap multiFrameMessage;
		multiFrameMessage["type"] = "multiframe";
		multiFrameMessage["frames"] = numframes;

		QByteArray msg;

		if(doBinary)
			msg = QJsonDocument::fromVariant(multiFrameMessage).toBinaryData();
		else
		{
			msg = QJsonDocument::fromVariant(multiFrameMessage).toJson();
			cleanJson(msg);
		}

		lwsWrite(lws, msg, msg.length());
	}

	while(numframes--)
	{
		int range = 0;
		if(temp.length() > framesize)
			range = framesize;
		else range = temp.length();

		QByteArray toWrite = temp.mid(0,range);
		const char* strToWrite = toWrite.data();

		temp = temp.mid(range);

		if(doBinary)
		{
			retval = libwebsocket_write(lws, (unsigned char*)strToWrite, toWrite.length(), LWS_WRITE_BINARY);
		}
		else
		{
			std::unique_ptr<char[]> buffer(new char[LWS_SEND_BUFFER_PRE_PADDING + len + LWS_SEND_BUFFER_POST_PADDING]);
			char *buf = buffer.get() + LWS_SEND_BUFFER_PRE_PADDING;
			memcpy(buf, strToWrite, toWrite.length());

			retval = libwebsocket_write(lws, (unsigned char*)strToWrite, toWrite.length(), LWS_WRITE_TEXT);
		}
	}
	return retval;

}
static int websocket_callback(struct libwebsocket_context *context,struct libwebsocket *wsi,enum libwebsocket_callback_reasons reason, void *user,void *in, size_t len)
{
	//printf("Switch: %i\n",reason);
	DebugOut(5) << __SMALLFILE__ << ":" << __LINE__ << "websocket_callback:" << reason << endl;


	switch (reason)
	{
		case LWS_CALLBACK_CLIENT_WRITEABLE:
		{
			break;
		}
		case LWS_CALLBACK_CLOSED:
		{
			sinkManager->disconnectAll(wsi);
			break;
		}
		case LWS_CALLBACK_CLIENT_RECEIVE:
		{
			break;
		}
		case LWS_CALLBACK_SERVER_WRITEABLE:
		{
			break;
		}

		case LWS_CALLBACK_RECEIVE:
		{

		}
		case LWS_CALLBACK_HTTP:
		{
			//TODO: Verify that ALL requests get sent via LWS_CALLBACK_HTTP, so we can use that instead of LWS_CALLBACK_RECIEVE
			//TODO: Do we want exceptions, or just to return an invalid json reply? Probably an invalid json reply.
			DebugOut() << __SMALLFILE__ << ":" << __LINE__ << " Requested: " << (char*)in << "\n";

			QByteArray d((char*)in,len);

			WebSocketSinkManager * manager = sinkManager;

			if(manager->expectedMessageFrames && manager->partialMessageIndex < manager->expectedMessageFrames)
			{
				manager->incompleteMessage += d;
				manager->partialMessageIndex++;
				break;
			}
			else if(manager->expectedMessageFrames && manager->partialMessageIndex == manager->expectedMessageFrames)
			{
				d = manager->incompleteMessage + d;
				manager->expectedMessageFrames = 0;
			}

			QJsonDocument doc;
			if(doBinary)
				doc = QJsonDocument::fromBinaryData(d);
			else
				doc = QJsonDocument::fromJson(d);

			if(doc.isNull())
			{
				DebugOut(DebugOut::Error)<<"Invalid message"<<endl;
				return 0;
			}

			QVariantMap call = doc.toVariant().toMap();

			string type = call["type"].toString().toStdString();
			string name = call["name"].toString().toStdString();
			string id = call["transactionid"].toString().toStdString();

			if (type == "multiframe")
			{

				manager->expectedMessageFrames = call["frames"].toInt();
				manager->partialMessageIndex = 1;
				manager->incompleteMessage = "";
			}
			else if (type == "method")
			{
				if(name == "getRanged")
				{
					QVariantMap data = call["data"].toMap();

					PropertyList propertyList;

					propertyList.push_back(data["property"].toString().toStdString());

					double timeBegin = data["timeBegin"].toDouble();
					double timeEnd = data["timeEnd"].toDouble();
					double sequenceBegin = data["sequenceBegin"].toInt();
					double sequenceEnd = data["sequenceEnd"].toInt();

					if ((timeBegin < 0 && timeEnd > 0) || (timeBegin > 0 && timeEnd < 0))
					{
						DebugOut(DebugOut::Warning)<<"Invalid time begin/end pair"<<endl;
					}
					else if ((sequenceBegin < 0 && sequenceEnd > 0) || (sequenceBegin > 0 && sequenceEnd < 0))
					{
						DebugOut(DebugOut::Warning)<<"Invalid sequence begin/end pair"<<endl;
					}
					else
					{
						sinkManager->addSingleShotRangedSink(wsi,propertyList,timeBegin,timeEnd,sequenceBegin,sequenceEnd,id);
					}
				}
				else if (name == "get")
				{
					QVariantMap data = call["data"].toMap();
					Zone::Type zone = Zone::None;
					if(data.contains("zone"))
					{
						zone = data["zone"].toInt();
					}
					sinkManager->addSingleShotSink(wsi,data["property"].toString().toStdString(),zone,id);

				}
				else if (name == "set")
				{
					QVariantMap data = call["data"].toMap();
					Zone::Type zone(Zone::None);
					if(data.contains("zone"))
					{
						zone = data["zone"].toInt();
					}
					sinkManager->setValue(wsi,data["property"].toString().toStdString(), data["value"].toString().toStdString(), zone, id);
				}
				else if (name == "subscribe")
				{
					std::string data = call["data"].toString().toStdString();
					sinkManager->addSink(wsi, data, id);

				}
				else if (name == "unsubscribe")
				{
					std::string data = call["data"].toString().toStdString();
					sinkManager->removeSink(wsi,data,id);

				}
				else if (name == "getSupportedEventTypes")
				{
					QVariantMap reply;
					QStringList list;

					PropertyList supported = sinkManager->getSupportedProperties();
					for(VehicleProperty::Property i : supported)
					{
						list.append(i.c_str());
					}

					reply["type"] = "methodReply";
					reply["name"] = "getSupportedEventTypes";
					reply["transactionid"] = id.c_str();
					reply["data"] = list;

					QByteArray replystr;

					if(doBinary)
						replystr = QJsonDocument::fromVariant(reply).toBinaryData();
					else
					{
						replystr = QJsonDocument::fromVariant(reply).toJson();
						cleanJson(replystr);
					}

					lwsWrite(wsi, replystr, replystr.length());
				}
				else
				{
					DebugOut(0)<<"Unknown method called."<<endl;
				}
			}
			break;
		}
		case LWS_CALLBACK_ADD_POLL_FD:
		{
			//printf("Adding poll %i\n",sinkManager);
			DebugOut(5) << __SMALLFILE__ <<":"<< __LINE__ << "Adding poll" << endl;
			if (sinkManager != 0)
			{
				//sinkManager->addPoll((int)(long)user);
				sinkManager->addPoll(libwebsocket_get_socket_fd(wsi));
			}
			else
			{
				DebugOut(5) << "Error, invalid sink manager!!" << endl;
			}
			break;
		}
		case LWS_CALLBACK_DEL_POLL_FD:
		{
			sinkManager->removePoll(libwebsocket_get_socket_fd(wsi));
			break;
		}
		case LWS_CALLBACK_SET_MODE_POLL_FD:
		{
			//Set the poll mode
			break;
		}
		case LWS_CALLBACK_CLEAR_MODE_POLL_FD:
		{
			//Don't handle this yet.
			break;
		}
		default:
		{
			//printf("Unhandled callback: %i\n",reason);
			DebugOut() << __SMALLFILE__ <<":"<< __LINE__ << "Unhandled callback:" << reason << "\n";
			break;
		}
	}
	return 0; 
}