/*
	Copyright 2022 Bga <bga.email@gmail.com>

	Licensed under the Apache License, Version 2.0 (the "License");
	you may not use this file except in compliance with the License.
	You may obtain a copy of the License at

		http://www.apache.org/licenses/LICENSE-2.0

	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
*/

#pragma once

#include <!cpp/bitManipulations.h>
#include <!cpp/wrapper/optional>
#include <!cpp/common.h>
#include <!cpp/TestRunner.h>


#pragma push_macro("Self")
#undef Self
#define Self Queue

namespace Bga {
	#warning Never fully fill Queue_NoCaryyCheck or you will not pop from it
	template<class ArrayTypeArg, class IndexArg = Size>
	struct Queue_NoCaryyCheck {
		typedef ArrayTypeArg ArrayType;
		typedef IndexArg Index;

		ArrayType* const data;
		const Index size;

		Index tailIndex;
		Index headIndex;
		
		void clear() {
			this->tailIndex = this->headIndex = 0;
		}
		
		Queue_NoCaryyCheck(ArrayType* data_, Index size_): data(data_), size(size_) {
			this->clear();
		}
		
		inline Index postCycleIndex(Index& v) {
			Index vCopy = v;
			cycleInc(v, this->size);
			return vCopy; 
		}
		
		inline Bool isEmpty() const {
			return this->tailIndex == this->headIndex;
		}
		inline Bool isFull() const {
			return false;
		}
		inline Bool push(ArrayType const& v) {
			this->data[this->postCycleIndex(this->headIndex)] = v;
			return true;
		}
		inline ::std::optional<ArrayType> peek() const {
			if(this->isEmpty()) {
				return ::std::nullopt;
			};
			return this->data[this->tailIndex];
		}
		inline ::std::optional<ArrayType> pop() {
			if(this->isEmpty()) {
				return ::std::nullopt;
			};
			return this->data[this->postCycleIndex(this->tailIndex)];
		}
	};

	template<class ArrayTypeArg, class IndexArg = Size>
	struct Queue_CarryCheckBoolFlag {
		typedef ArrayTypeArg ArrayType;
		typedef IndexArg Index;

		ArrayType* const  data;
		const Index size;

		Index tailIndex;
		Index headIndex;
		
		Bool isCarry;
		
		void clear() {
			this->tailIndex = this->headIndex = 0;
			this->isCarry = false;
		}
		
		Queue_CarryCheckBoolFlag(ArrayType* data_, Index size_): data(data_), size(size_) {
			this->clear();
		}
		
		inline Index postCycleIndex(Index& v) {
			Index vCopy = v;
			cycleInc(v, this->size);
			return vCopy; 
		}
		
		inline Bool isEmpty() const {
			return this->tailIndex == this->headIndex && !this->isCarry;
		}
		inline Bool isFull() const {
			return this->tailIndex == this->headIndex && this->isCarry;
		}
		inline Bool push(ArrayType const& v) {
			if(this->isFull()) {
				return false;
			};
			this->data[this->postCycleIndex(this->headIndex)] = v;
			if(this->headIndex == this->tailIndex) {
				this->isCarry = true;
			};
			return true;
		}
		inline ::std::optional<ArrayType> peek() const {
			if(this->isEmpty()) {
				return ::std::nullopt;
			};
			return this->data[this->tailIndex];
		}
		inline ::std::optional<ArrayType> pop() {
			if(this->isEmpty()) {
				return ::std::nullopt;
			};
			this->isCarry = false;
			return this->data[this->postCycleIndex(this->tailIndex)];
		}
	};

	template<class ArrayTypeArg, class IndexArg = Size>
	struct Queue_CarryCheckBiggerIndex {
		typedef ArrayTypeArg ArrayType;
		typedef IndexArg Index;

		ArrayType* const data;
		const Index size;

		Index tailIndex;
		Index headIndex;
		
		void clear() {
			this->tailIndex = this->headIndex = 0;
		}
		
		Queue_CarryCheckBiggerIndex(ArrayType* data_, Index size_): data(data_), size(size_) {
			this->clear();
		}
		
		inline Index postCycleIndex(Index& v) {
			Index vCopy = v;
			cycleInc(v, this->size);
			return vCopy; 
		}
		
		inline Bool isEmpty() const {
			return this->tailIndex == this->headIndex;
		}
		inline Bool isFull() const {
			if(isPowerOf2(this->size)) {
				Index x = this->headIndex ^ this->tailIndex;
				return x != 0 && (x & (this->size - 1)) == 0; 
				
			}
			else {
				return this->headIndex != this->tailIndex && this->headIndex % this->size == this->tailIndex % this->size;
			}
			// return this->headIndex != this->tailIndex && this->headIndex % this->size == this->tailIndex % this->size;
		}
		inline Bool push(ArrayType const& v) {
			if(this->isFull()) {
				return false;
			};
			this->data[this->headIndex++ % this->size] = v;
			return true;
		}
		inline ::std::optional<ArrayType> peek() const {
			if(this->isEmpty()) {
				return ::std::nullopt;
			};
			return this->data[this->tailIndex % this->size];
		}
		inline ::std::optional<ArrayType> pop() {
			if(this->isEmpty()) {
				return ::std::nullopt;
			};
			return this->data[this->tailIndex++ % this->size];
		}
	};

	template<class ArrayTypeArg, class IndexArg = Size>
	struct Queue_TailPlusSize {
		typedef ArrayTypeArg ArrayType;
		typedef IndexArg Index;

		ArrayType* const data;
		const Index size;

		Index tailIndex;
		Index currentSize;
		
		void clear() {
			this->tailIndex = this->currentSize = 0;
		}
		
		Queue_TailPlusSize(ArrayType* data_, Index size_): data(data_), size(size_) {
			this->clear();
		}
		
		inline Index postCycleIndex(Index& v) {
			Index vCopy = v;
			cycleInc(v, this->size);
			return vCopy; 
		}
		
		inline Bool isEmpty() const {
			return this->currentSize == 0;
		}
		inline Bool isFull() const {
			return this->size <= this->currentSize;
		}
		inline Bool push(ArrayType const& v) {
			if(this->isFull()) {
				return false;
			};
			this->data[(this->tailIndex + this->currentSize++) % this->size] = v;
			return true;
		}
		inline ::std::optional<ArrayType> peek() const {
			if(this->isEmpty()) {
				return ::std::nullopt;
			};
			return this->data[(this->tailIndex + this->currentSize) % this->size];
		}
		inline ::std::optional<ArrayType> pop() {
			if(this->isEmpty()) {
				return ::std::nullopt;
			};
			this->currentSize--;
			return this->data[this->tailIndex++ % this->size];
		}
	};

} //# namespace

#ifdef BGA__TESTRUNNER_ON
char const* const nullopt_string =  "nullopt";
template<class TArg>
::std::ostream& operator<<(::std::ostream& stream, const ::std::optional<TArg>& v) {
	if(v) {
		stream << *v;
	}
	else {
		stream << nullopt_string;
	}
	
	return stream;
}
::std::ostream& operator<<(::std::ostream& stream, const ::std::nullopt_t& v) {
	stream << nullopt_string;
	
	return stream;
}

enum Queue_test_Option {
	Queue_test_Option_noCarryCheck = _BV(1), 
};
template<template<class ValueArg, class IndexArg = Size> class QueueArg>
void Queue_test(char const* className, UInt options = 0) {
	TestRunnerNS::setUserDefinedScope(className);
	
	Int data[2];
	Int uuid = 1;
	QueueArg<Int> q(data, arraySize(data));
	q.clear();
	assert_eq(q.isEmpty(), true);
	assert_eq(q.isFull(), false);
	assert_eq(q.pop(), ::std::nullopt);

	assert_eq(q.push(++uuid), true);
	assert_eq(q.isEmpty(), false);
	assert_eq(q.isFull(), false);
	assert_eq(q.pop(), ::std::make_optional(Int(uuid)));
	assert_eq(q.pop(), ::std::nullopt);
	assert_eq(q.pop(), ::std::nullopt);

	assert_eq(q.push(++uuid), true);
	assert_eq(q.isEmpty(), false);
	assert_eq(q.isFull(), false);
	assert_eq(q.pop(), ::std::make_optional(Int(uuid)));
	assert_eq(q.pop(), ::std::nullopt);
	assert_eq(q.pop(), ::std::nullopt);

	assert_eq(q.push(++uuid), true);
	assert_eq(q.push(++uuid), true);
	assert_eq(q.isEmpty(), false);
	assert_eq(q.isFull(), true);
	if(!hasBitMask(options, Queue_test_Option_noCarryCheck)) {
		assert_eq(q.push(++uuid), false);
	};
}

example(BGA__STR(Self)) {
	using namespace ::Bga;
	
	Queue_test<Queue_NoCaryyCheck>("Queue_NoCaryyCheck", Queue_test_Option_noCarryCheck);
	Queue_test<Queue_CarryCheckBoolFlag>("Queue_CarryCheckBoolFlag");
	Queue_test<Queue_CarryCheckBiggerIndex>("Queue_CarryCheckBiggerIndex");
	Queue_test<Queue_TailPlusSize>("Queue_TailPlusSize");

}
#endif

#pragma pop_macro("Self")