//	BlitzUtility - Expanding the normal Blitz functionality.
//	Copyright (C) 2015 Project Kube (Michael Fabian Dirks)
//
//	This program is free software: you can redistribute it and/or modify
//	it under the terms of the GNU Lesser General Public License as
//	published by the Free Software Foundation, either version 3 of the 
//	License, or (at your option) any later version.
//
//	This program 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.
//
//	You should have received a copy of the GNU Lesser General Public License
//	along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include "IndexerV2.h"

IndexerV2::IndexerV2() {
	this->range.reserve(32);
}

IndexerV2::~IndexerV2() {
	this->range.clear();
}

void IndexerV2::mark(uint32_t index, bool used) {
	if (this->range.size() == 0) {
		if (used == true)
			this->range.push_back(IndexerV2Range(index, index));
	} else {
		auto iter = this->range.begin();
		for (auto iter = this->range.begin(); iter != this->range.end(); iter++) {
			if (used) {
				if ((index + 1) == iter->min) {
					iter->min--;
					break;
				} else if ((index - 1) == iter->max) {
					iter->max++;
					if ((iter + 1) != this->range.end() && iter->max == (iter + 1)->min) {
						(iter + 1)->min = iter->min;
						this->range.erase(iter);
					}
					break;
				} else if (index < iter->min) {
					this->range.insert(iter, IndexerV2Range(index, index));
					break;
				} 
			} else {
				if (index >= iter->min && index <= iter->max) {
					this->range.insert(iter - 1, IndexerV2Range(iter->min, index - 1));
					this->range.insert(iter + 1, IndexerV2Range(index + 1, iter->max));
					this->range.erase(iter); break;
				} else if (index == iter->min) {
					iter->min++;
					if (iter->min == iter->max)
						this->range.erase(iter);
					break;
				} else if (index == iter->max) {
					iter->max--;
					if (iter->min == iter->max)
						this->range.erase(iter);
					
					break;
				}
			}
		}
	}
}

bool IndexerV2::is(uint32_t index, bool used) {
	bool isUsed = false;
	for (auto iter = this->range.begin(); iter != this->range.end(); iter++) {
		if (index >= iter->min && index <= iter->max) {
			isUsed = true;
			break;
		}
	}
	return (isUsed == used);
}

uint32_t IndexerV2::get() {
	if (this->range.size() == 0) {
		this->range.push_back(IndexerV2Range(0, 0));
		return 0;
	}

	// We only need to check the first element to get a new free index.
	std::vector<IndexerV2Range>::iterator iter = this->range.begin();
	if (iter->min > 0) {
		return --iter->min;
	} else {
		if (iter->max == UINT32_MAX)
			return UINT32_MAX;

		uint32_t index = ++iter->max;

		// Check if we can combine this element with the next one.
		std::vector<IndexerV2Range>::iterator iterN = this->range.begin() + 1;
		if ((iterN != this->range.end()) && iterN->min == iter->max) {
			iter->max = iterN->max;
			this->range.erase(iterN);
		}

		return index;
	}

	return UINT32_MAX;
}

uint32_t IndexerV2::count(bool used) {
	uint32_t amount = 0;
	for (auto iter = this->range.begin(); iter != this->range.end(); iter++) {
		amount += iter->max - iter->min;
	}
	return (used ? UINT32_MAX - amount : amount);
}

IndexerV2::IndexerV2Range::IndexerV2Range(uint32_t min, uint32_t max) {
	this->min = min;
	this->max = max;
}


DLL_METHOD IndexerV2* DLL_CALL BU_IndexerV2_Create() {
	return new IndexerV2();
}

DLL_METHOD void DLL_CALL BU_IndexerV2_Destroy(IndexerV2* indexer) {
	delete indexer;
}

DLL_METHOD void DLL_CALL BU_IndexerV2_Mark(IndexerV2* indexer, uint32_t used, uint32_t index) {
	indexer->mark(index, used != 0);
}

DLL_METHOD void DLL_CALL BU_IndexerV2_MarkFree(IndexerV2* indexer, uint32_t index) {
	indexer->mark(index, false);
}

DLL_METHOD void DLL_CALL BU_IndexerV2_MarkUsed(IndexerV2* indexer, uint32_t index) {
	indexer->mark(index, true);
}

DLL_METHOD uint32_t DLL_CALL BU_IndexerV2_Is(IndexerV2* indexer, uint32_t index, uint32_t used) {
	return indexer->is(index, used != 0);
}

DLL_METHOD uint32_t DLL_CALL BU_IndexerV2_IsFree(IndexerV2* indexer, uint32_t index) {
	return indexer->is(index, false);
}

DLL_METHOD uint32_t DLL_CALL BU_IndexerV2_IsUsed(IndexerV2* indexer, uint32_t index) {
	return indexer->is(index, true);
}

DLL_METHOD uint32_t DLL_CALL BU_IndexerV2_Get(IndexerV2* indexer) {
	return indexer->get();
}

DLL_METHOD uint32_t DLL_CALL BU_IndexerV2_Count(IndexerV2* indexer, uint32_t used) {
	return indexer->count(used != 0);
}

DLL_METHOD uint32_t DLL_CALL BU_IndexerV2_CountFree(IndexerV2* indexer) {
	return indexer->count(false);
}

DLL_METHOD uint32_t DLL_CALL BU_IndexerV2_CountUsed(IndexerV2* indexer) {
	return indexer->count(true);
}
