

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  This file is part of PSYCLONE version 2.0.0.170915_Beta4 (15 September 2017)
//_/_/  
//_/_/  developed by
//_/_/  
//_/_/  Communicative Machines Ltd. 
//_/_/    http://cmlabs.com/
//_/_/  Copyright (c) 2017
//_/_/  
//_/_/  in collaboration with 
//_/_/  
//_/_/  Icelandic Institute for Intelligent Machines ses
//_/_/    Menntavegur 1
//_/_/    101 Reykjavik
//_/_/    ICELAND
//_/_/    http://www.iiim.is/
//_/_/
//_/_/  PSYCLONE 2.0 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.0 is also available to 
//_/_/  any interested party from the copyright holder.
//_/_/  
//_/_/  You can redistribute this copy of PSYCLONE 2.0 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.0.  
//_/_/  If not, see <http://www.gnu.org/licenses/>.
//_/_/  
//_/_/  PSYCLONE 2.0 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 "IDManager.h"

namespace	cmlabs{

uint32 IDManager::MAXIDS[] = {0, 0xFFFFFFFF, 0xFFFF, 0xFFFFFFFF, 0xFFFF, 0xFFFF};
char IDManager::IDNAMES[][10] = {"", "Component", "Type", "Tag", "Context", "Crank"};

ReserveID::ReserveID() {
	type = 0;
	blockSize = 0;
	localID = 0;
	lastID = 0;
	memset(&currentReservation, 0, sizeof(IDReservation));
	memset(&nextReservation, 0, sizeof(IDReservation));
}

ReserveID::~ReserveID() {
	reservations.clear();
}

bool ReserveID::init(uint16 localID) {
	this->localID = localID;
	return true;
}

uint32 ReserveID::reserveNextID() {
	uint32 resID = 0;
	if (!mutex.enter())
		return 0;
	resID = ++lastID;
	mutex.leave();
	return resID;
}

uint32 ReserveID::getNextID() {
	uint32 resID = 0;
	if (!mutex.enter())
		return 0;
	if (currentReservation.time)
		if (lastID < currentReservation.end)
			resID = ++lastID;
	if (!resID && nextReservation.time) {
		// We have run out of the current reservation
		currentReservation = nextReservation;
		resID = lastID = currentReservation.start;
		memset(&nextReservation, 0, sizeof(IDReservation));
		reservations[currentReservation.start] = currentReservation;
	}
	mutex.leave();
	return resID;
}

uint32 ReserveID::getRemainingCount() {
	uint32 count = 0;
	if (!mutex.enter())
		return 0;
	if (currentReservation.time)
		count = currentReservation.end - lastID;
	if (nextReservation.time)
		count += nextReservation.end - nextReservation.start;
	mutex.leave();
	return count;
}

double ReserveID::getRemainingPercent() {
	uint32 count = getRemainingCount();
	if (!count)
		return 0;
	return (double)count/blockSize;
}

IDReservation ReserveID::getNextReservationBlock() {
	IDReservation res;
	memset(&res, 0, sizeof(IDReservation));

	if (!mutex.enter())
		return res;

	if (!currentReservation.time) {
		// use start value provided
		res.start = lastID + 1;
		res.end = res.start + blockSize;
		res.time = 0; // until approved
		res.owner = localID;
		res.type = type;
		nextReservation = res;
		mutex.leave();
		return res;
	}

	res.start = 1;
	std::map<uint32, IDReservation>::iterator it, itEnd;
	for (it = reservations.begin(), itEnd = reservations.end(); it != itEnd; it++)
		if (it->second.time && (it->second.end > res.start))
			res.start = it->second.end + 1;

	if (nextReservation.end > res.start) {
		// Next reservation failed, try next block
		// If nobody reserved the failed block it is lost
		res.start = nextReservation.end + 1;
	}

	res.end = res.start + blockSize;
	res.time = 0; // until approved
	res.owner = localID;
	res.type = type;
	nextReservation = res;
	mutex.leave();
	return res;
}

bool ReserveID::enterReservation(IDReservation& res) {
	if (!mutex.enter())
		return 0;

	if (!currentReservation.type && res.owner == localID) {
		// This is the first assignment of IDs, use it
		type = res.type;
		blockSize = res.end - res.start;
		memcpy(&currentReservation, &res, sizeof(IDReservation));
		memset(&nextReservation, 0, sizeof(IDReservation));
		currentReservation.time = GetTimeNow();
		lastID = res.start - 1;
		mutex.leave();
		return true;
	}

	if (!currentReservation.time && (res.owner == currentReservation.owner) && (res.start == currentReservation.start)) {
		// proposed first reservation was approved
		currentReservation.time = GetTimeNow();
		mutex.leave();
		return true;
	}

	if ((res.owner == nextReservation.owner) && (res.start == nextReservation.start)) {
		// proposed next reservation was approved
		nextReservation.time = GetTimeNow();
		mutex.leave();
		return true;
	}

	// this is someone else's reservation, check it
	if (reservations[res.start].time) {
		// reject
		mutex.leave();
		return false;
	}

	// Does it match our next
	if (nextReservation.start && nextReservation.start == res.start) {
		// reject
		mutex.leave();
		return false;
	}

	// approve
	reservations[res.start] = res;
	mutex.leave();
	return true;
}

uint32 ReserveID::writeAllReservationsToMsg(DataMessage* msg) {
	uint32 c = 0;
	std::map<uint32, IDReservation>::iterator it, itEnd;
	for (it = reservations.begin(), itEnd = reservations.end(); it != itEnd; it++) {
		it->second.size = sizeof(IDReservation);
		it->second.cid = IDRESERVATION;
		msg->setData(c++, "IDEntry", (char*)&(it->second), sizeof(IDReservation));
	}
	return c;
}







IDManager::IDManager() {
	networking = NULL;
	for (uint8 n=1; n<=RESERVE_ID_COUNT; n++)
		reserveIDs[n].type = n;
}

IDManager::~IDManager() {
	networking = NULL;
}

bool IDManager::init(Networking* networking) {
	this->networking = networking;
	for (uint8 n=1; n<=RESERVE_ID_COUNT; n++)
		reserveIDs[n].localID = networking->localNodeID;
	return true;
}

bool IDManager::setup(Networking* networking, uint16 nodeCount) {
	this->networking = networking;
	for (uint8 n=1; n<=RESERVE_ID_COUNT; n++)
		reserveIDs[n].localID = networking->localNodeID;

	IDReservation res;
	uint32 blockSize;

	memset(&res, 0, sizeof(IDReservation));

	for (uint8 n=1; n<=RESERVE_ID_COUNT; n++) {
		// Calculate own initial reservation
		// At this point we may already have pre-reserved ids, so calculate from the last ones onwards
		if (MAXIDS[n] > 0xFFFF)
			blockSize = 256;
		else if (nodeCount <= 100)
			blockSize = 256;
		else if (nodeCount <= 500)
			blockSize = 64;
		else
			blockSize = 40000/nodeCount;
		res.type = n;
		res.time = 0;
		res.owner = networking->localNodeID;
		res.start = reserveIDs[n].lastID + 1;
		res.end = res.start + blockSize;
		reserveIDs[n].enterReservation(res);
		if (syncIDReservation(res) < SYNC_SUCCESS) {
			LogPrint(0, LOG_NODE, 0, "IDManager couldn't reserve own IDs for %s...", IDNAMES[n]);
			return false;
		}

		// Send out other Nodes initial reservations
		for (uint16 i=1; i<=nodeCount; i++) {
			if (i == networking->localNodeID) continue;
			res.owner = i;
			res.start = res.end + 1;
			res.end = res.start + blockSize;
			reserveIDs[n].enterReservation(res);
			if (syncIDReservation(res) < SYNC_SUCCESS) {
				LogPrint(0, LOG_NODE, 0, "IDManager couldn't reserve IDs for %s for node %u...", IDNAMES[n], i);
				return false;
			}
		}
	}
	return true;
}


uint32 IDManager::getNextComponentID() {
	if (!networking)
		return reserveIDs[RESERVE_COMPONENT_ID].reserveNextID();
	else
		return reserveIDs[RESERVE_COMPONENT_ID].getNextID();
}

uint16 IDManager::getNextTypeID() {
	if (!networking)
		return (uint16)reserveIDs[RESERVE_TYPE_ID].reserveNextID();
	else
		return (uint16)reserveIDs[RESERVE_TYPE_ID].getNextID();
}

uint32 IDManager::getNextTagID() {
	if (!networking)
		return reserveIDs[RESERVE_TAG_ID].reserveNextID();
	else
		return reserveIDs[RESERVE_TAG_ID].getNextID();
}

uint16 IDManager::getNextContextID() {
	if (!networking)
		return (uint16)reserveIDs[RESERVE_CONTEXT_ID].reserveNextID();
	else
		return (uint16)reserveIDs[RESERVE_CONTEXT_ID].getNextID();
}

uint16 IDManager::getNextCrankID() {
	if (!networking)
		return (uint16)reserveIDs[RESERVE_CRANK_ID].reserveNextID();
	else
		return (uint16)reserveIDs[RESERVE_CRANK_ID].getNextID();
}

bool IDManager::enterIDReservation(IDReservation& res) {
	if (res.type > RESERVE_ID_COUNT)
		return false;
	return reserveIDs[res.type].enterReservation(res);
}

bool IDManager::enterIDReservations(DataMessage* msg) {
	uint32 s, n = 0;
	IDReservation* res;
	while (res = (IDReservation*)msg->getData(n, "IDEntry", s)) {
		if (!enterIDReservation(*res))
			return false;
		n++;
	}
	return true;
}


bool IDManager::checkSync() {
	IDReservation res;
	uint8 result;

	if (!networking)
		return false;

	for (uint8 n=1; n<=RESERVE_ID_COUNT; n++) {
		if (reserveIDs[n].getRemainingPercent() < 0.25) {
			res = reserveIDs[n].getNextReservationBlock();
			while ( (result = syncIDReservation(res)) < SYNC_SUCCESS ) {
				if (!result) {
					LogPrint(0, LOG_NODE, 0, "Failed reserving additional %s IDs...", IDNAMES[n]);
					break;
				}
				res = reserveIDs[n].getNextReservationBlock();
			}
			reserveIDs[n].enterReservation(res);
		}
	}

	return true;
}

uint8 IDManager::syncIDReservation(IDReservation& res) {
	res.size = sizeof(IDReservation);
	res.cid = IDRESERVATION;
	DataMessage* msg = new DataMessage(Networking::CTRL_NODE_SYNC_ID, networking->localNodeID);
	msg->setReference(SYNC_RESERVE_ID);
	msg->setData("IDEntry", (char*)&res, sizeof(IDReservation));
	LogDebug(0, LOG_SYNC, 2, "Sending ID %s reservation %u-%u...", IDNAMES[res.type], res.start, res.end);
	return networking->syncNodes(msg, 5000, true);
}

bool IDManager::resyncAllReservations() {
	uint8 result;

	if (!networking)
		return false;

	uint32 c;
	for (uint8 n = 1; n <= RESERVE_ID_COUNT; n++) {
		DataMessage* msg = new DataMessage(Networking::CTRL_NODE_SYNC_ID, networking->localNodeID);
		msg->setReference(SYNC_RESERVE_IDS);
		if (c = reserveIDs[n].writeAllReservationsToMsg(msg)) {
			LogDebug(0, LOG_SYNC, 2, "Sending ID %s bulk (%u) reservations...", IDNAMES[n], c);
			if ( (result = networking->syncNodes(msg, 5000, true)) != SYNC_SUCCESS)
				return false;
		}
		else {
			delete(msg);
		}
	}
	return true;
}

}
