#include "DataPoolAdmin.h"
#include "GlobalDataSet.h"
#include "GlobalDataPool.h"
#include "PoolRawAccess.h"

#include "../ipc/CommandMessage.h"
#include "../ipc/QueueFactory.h"
#include "../parameters/ParameterMessage.h"

DataPoolAdmin::DataPoolAdmin(object_id_t objectId) :
		SystemObject(objectId), storage(NULL), commandQueue(NULL), memoryHelper(
				this, NULL), actionHelper(this, NULL) {
	commandQueue = QueueFactory::instance()->createMessageQueue();
}

DataPoolAdmin::~DataPoolAdmin() {
	QueueFactory::instance()->deleteMessageQueue(commandQueue);
}

ReturnValue_t DataPoolAdmin::performOperation(uint8_t opCode) {
	handleCommand();
	return RETURN_OK;
}

MessageQueueId_t DataPoolAdmin::getCommandQueue() const {
	return commandQueue->getId();
}

ReturnValue_t DataPoolAdmin::executeAction(ActionId_t actionId,
		MessageQueueId_t commandedBy, const uint8_t* data, size_t size) {
	if (actionId != SET_VALIDITY) {
		return INVALID_ACTION_ID;
	}

	if (size != 5) {
		return INVALID_PARAMETERS;
	}

	uint32_t address = (data[0] << 24) | (data[1] << 16) | (data[2] << 8)
			| data[3];

	uint8_t valid = data[4];

	uint32_t poolId = glob::dataPool.PIDToDataPoolId(address);

	GlobDataSet mySet;
	PoolRawAccess variable(poolId, 0, &mySet, PoolVariableIF::VAR_READ_WRITE);
	ReturnValue_t status = mySet.read();
	if (status != RETURN_OK) {
		return INVALID_ADDRESS;
	}
	if (valid != 0) {
		variable.setValid(PoolVariableIF::VALID);
	} else {
		variable.setValid(PoolVariableIF::INVALID);
	}

	mySet.commit();

	return EXECUTION_FINISHED;
}

ReturnValue_t DataPoolAdmin::getParameter(uint8_t domainId,
		uint16_t parameterId, ParameterWrapper* parameterWrapper,
		const ParameterWrapper* newValues, uint16_t startAtIndex) {
	return HasReturnvaluesIF::RETURN_FAILED;
}

void DataPoolAdmin::handleCommand() {
	CommandMessage command;
	ReturnValue_t result = commandQueue->receiveMessage(&command);
	if (result != RETURN_OK) {
		return;
	}

	result = actionHelper.handleActionMessage(&command);

	if (result == HasReturnvaluesIF::RETURN_OK) {
		return;
	}

	result = handleParameterCommand(&command);
	if (result == HasReturnvaluesIF::RETURN_OK) {
		return;
	}

	result = memoryHelper.handleMemoryCommand(&command);
	if (result != RETURN_OK) {
		command.setToUnknownCommand();
		commandQueue->reply(&command);
	}
}

ReturnValue_t DataPoolAdmin::handleMemoryLoad(uint32_t address,
		const uint8_t* data, size_t size, uint8_t** dataPointer) {
	uint32_t poolId = glob::dataPool.PIDToDataPoolId(address);
	uint8_t arrayIndex = glob::dataPool.PIDToArrayIndex(address);
	GlobDataSet testSet;
	PoolRawAccess varToGetSize(poolId, arrayIndex, &testSet,
			PoolVariableIF::VAR_READ);
	ReturnValue_t status = testSet.read();
	if (status != RETURN_OK) {
		return INVALID_ADDRESS;
	}
	uint8_t typeSize = varToGetSize.getSizeOfType();

	if (size % typeSize != 0) {
		return INVALID_SIZE;
	}

	if (size > varToGetSize.getSizeTillEnd()) {
		return INVALID_SIZE;
	}
	const uint8_t* readPosition = data;

	for (; size > 0; size -= typeSize) {
		GlobDataSet rawSet;
		PoolRawAccess variable(poolId, arrayIndex, &rawSet,
				PoolVariableIF::VAR_READ_WRITE);
		status = rawSet.read();
		if (status == RETURN_OK) {
			status = variable.setEntryFromBigEndian(readPosition, typeSize);
			if (status == RETURN_OK) {
				status = rawSet.commit();
			}
		}
		arrayIndex += 1;
		readPosition += typeSize;
	}
	return ACTIVITY_COMPLETED;
}

ReturnValue_t DataPoolAdmin::handleMemoryDump(uint32_t address, size_t size,
		uint8_t** dataPointer, uint8_t* copyHere) {
	uint32_t poolId = glob::dataPool.PIDToDataPoolId(address);
	uint8_t arrayIndex = glob::dataPool.PIDToArrayIndex(address);
	GlobDataSet testSet;
	PoolRawAccess varToGetSize(poolId, arrayIndex, &testSet,
			PoolVariableIF::VAR_READ);
	ReturnValue_t status = testSet.read();
	if (status != RETURN_OK) {
		return INVALID_ADDRESS;
	}
	uint8_t typeSize = varToGetSize.getSizeOfType();
	if (size > varToGetSize.getSizeTillEnd()) {
		return INVALID_SIZE;
	}
	uint8_t* ptrToCopy = copyHere;
	for (; size > 0; size -= typeSize) {
		GlobDataSet rawSet;
		PoolRawAccess variable(poolId, arrayIndex, &rawSet,
				PoolVariableIF::VAR_READ);
		status = rawSet.read();
		if (status == RETURN_OK) {
			size_t temp = 0;
			status = variable.getEntryEndianSafe(ptrToCopy, &temp, size);
			if (status != RETURN_OK) {
				return RETURN_FAILED;
			}
		} else {
			//Error reading parameter.
		}
		arrayIndex += 1;
		ptrToCopy += typeSize;
	}
	return ACTIVITY_COMPLETED;
}

ReturnValue_t DataPoolAdmin::initialize() {
	ReturnValue_t result = SystemObject::initialize();
	if (result != HasReturnvaluesIF::RETURN_OK) {
		return result;
	}

	result = memoryHelper.initialize(commandQueue);
	if (result != HasReturnvaluesIF::RETURN_OK) {
		return result;
	}

	storage = objectManager->get<StorageManagerIF>(objects::IPC_STORE);
	if (storage == NULL) {
		return HasReturnvaluesIF::RETURN_FAILED;
	}

	result = actionHelper.initialize(commandQueue);

	return result;
}

//mostly identical to ParameterHelper::handleParameterMessage()
ReturnValue_t DataPoolAdmin::handleParameterCommand(CommandMessage* command) {
	ReturnValue_t result = HasReturnvaluesIF::RETURN_FAILED;
	switch (command->getCommand()) {
	case ParameterMessage::CMD_PARAMETER_DUMP: {
		uint8_t domain = HasParametersIF::getDomain(
				ParameterMessage::getParameterId(command));
		uint16_t parameterId = HasParametersIF::getMatrixId(
				ParameterMessage::getParameterId(command));

		DataPoolParameterWrapper wrapper;
		result = wrapper.set(domain, parameterId);

		if (result == HasReturnvaluesIF::RETURN_OK) {
			result = sendParameter(command->getSender(),
					ParameterMessage::getParameterId(command), &wrapper);
		}
	}
		break;
	case ParameterMessage::CMD_PARAMETER_LOAD: {

		uint8_t domain = HasParametersIF::getDomain(
				ParameterMessage::getParameterId(command));
		uint16_t parameterId = HasParametersIF::getMatrixId(
				ParameterMessage::getParameterId(command));
		uint8_t index = HasParametersIF::getIndex(
				ParameterMessage::getParameterId(command));

		const uint8_t *storedStream;
		size_t storedStreamSize;
		result = storage->getData(ParameterMessage::getStoreId(command),
				&storedStream, &storedStreamSize);
		if (result != HasReturnvaluesIF::RETURN_OK) {
			break;
		}

		ParameterWrapper streamWrapper;
		result = streamWrapper.set(storedStream, storedStreamSize);
		if (result != HasReturnvaluesIF::RETURN_OK) {
			storage->deleteData(ParameterMessage::getStoreId(command));
			break;
		}

		DataPoolParameterWrapper poolWrapper;
		result = poolWrapper.set(domain, parameterId);
		if (result != HasReturnvaluesIF::RETURN_OK) {
			storage->deleteData(ParameterMessage::getStoreId(command));
			break;
		}

		result = poolWrapper.copyFrom(&streamWrapper, index);

		storage->deleteData(ParameterMessage::getStoreId(command));

		if (result == HasReturnvaluesIF::RETURN_OK) {
			result = sendParameter(command->getSender(),
					ParameterMessage::getParameterId(command), &poolWrapper);
		}
	}
		break;
	default:
		return HasReturnvaluesIF::RETURN_FAILED;
	}

	if (result != HasReturnvaluesIF::RETURN_OK) {
		rejectCommand(command->getSender(), result, command->getCommand());
	}

	return HasReturnvaluesIF::RETURN_OK;

}

//identical to ParameterHelper::sendParameter()
ReturnValue_t DataPoolAdmin::sendParameter(MessageQueueId_t to, uint32_t id,
		const DataPoolParameterWrapper* wrapper) {
	size_t serializedSize = wrapper->getSerializedSize();

	uint8_t *storeElement;
	store_address_t address;

	ReturnValue_t result = storage->getFreeElement(&address, serializedSize,
			&storeElement);
	if (result != HasReturnvaluesIF::RETURN_OK) {
		return result;
	}

	size_t storeElementSize = 0;

	result = wrapper->serialize(&storeElement, &storeElementSize,
			serializedSize, SerializeIF::Endianness::BIG);

	if (result != HasReturnvaluesIF::RETURN_OK) {
		storage->deleteData(address);
		return result;
	}

	CommandMessage reply;

	ParameterMessage::setParameterDumpReply(&reply, id, address);

	commandQueue->sendMessage(to, &reply);

	return HasReturnvaluesIF::RETURN_OK;
}

//identical to ParameterHelper::rejectCommand()
void DataPoolAdmin::rejectCommand(MessageQueueId_t to, ReturnValue_t reason,
		Command_t initialCommand) {
	CommandMessage reply;
	reply.setReplyRejected(reason, initialCommand);
	commandQueue->sendMessage(to, &reply);
}