// Copyright (c) 2012- PPSSPP Project.

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.

// 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 2.0 for more details.

// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/

// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.

#pragma once

#include "Core/HLE/sceKernel.h"
#include "Common/Serialize/Serializer.h"

struct ThreadQueueList {
	// Number of queues (number of priority levels starting at 0.)
	static const int NUM_QUEUES = 128;
	// Initial number of threads a single queue can handle.
	static const int INITIAL_CAPACITY = 32;

	struct Queue {
		// Next ever-been-used queue (worse priority.)
		Queue *next;
		// First valid item in data.
		int first;
		// One after last valid item in data.
		int end;
		// A too-large array with room on the front and end.
		SceUID *data;
		// Size of data array.
		int capacity;

		inline int size() const {
			return end - first;
		}
		inline bool empty() const {
			return first == end;
		}
		inline int full() const {
			return end == capacity;
		}
	};

	ThreadQueueList() {
		memset(queues, 0, sizeof(queues));
		first = invalid();
	}

	~ThreadQueueList() {
		clear();
	}

	// Only for debugging, returns priority level.
	int contains(const SceUID uid) {
		for (int i = 0; i < NUM_QUEUES; ++i) {
			if (queues[i].data == nullptr)
				continue;

			Queue *cur = &queues[i];
			for (int j = cur->first; j < cur->end; ++j) {
				if (cur->data[j] == uid)
					return i;
			}
		}

		return -1;
	}

	inline SceUID pop_first() {
		Queue *cur = first;
		while (cur != invalid()) {
			if (cur->size() > 0)
				return cur->data[cur->first++];
			cur = cur->next;
		}

		_dbg_assert_msg_(false, "ThreadQueueList should not be empty.");
		return 0;
	}

	inline SceUID pop_first_better(u32 priority) {
		Queue *cur = first;
		// Don't bother looking past (worse than) this priority.
		Queue *stop = &queues[priority];
		while (cur < stop) {
			if (cur->size() > 0)
				return cur->data[cur->first++];
			cur = cur->next;
		}

		return 0;
	}

	inline SceUID peek_first() {
		Queue *cur = first;
		while (cur != invalid()) {
			if (cur->size() > 0)
				return cur->data[cur->first];
			cur = cur->next;
		}

		return 0;
	}

	inline void push_front(u32 priority, const SceUID threadID) {
		Queue *cur = &queues[priority];
		cur->data[--cur->first] = threadID;
		// If we ran out of room toward the front, add more room for next time.
		if (cur->first == 0)
			rebalance(priority);
	}

	inline void push_back(u32 priority, const SceUID threadID) {
		Queue *cur = &queues[priority];
		cur->data[cur->end++] = threadID;
		if (cur->full())
			rebalance(priority);
	}

	inline void remove(u32 priority, const SceUID threadID) {
		Queue *cur = &queues[priority];
		_dbg_assert_msg_(cur->next != nullptr, "ThreadQueueList::Queue should already be linked up.");

		for (int i = cur->first; i < cur->end; ++i) {
			if (cur->data[i] == threadID) {
				// How many more after this one?
				int remaining = cur->end - i;
				// If there are more, move them into place.
				if (remaining > 0)
					memmove(&cur->data[i], &cur->data[i + 1], remaining * sizeof(SceUID));

				// Now we're one shorter.
				--cur->end;
				return;
			}
		}

		// Wasn't there.
	}

	inline void rotate(u32 priority) {
		Queue *cur = &queues[priority];
		_dbg_assert_msg_(cur->next != nullptr, "ThreadQueueList::Queue should already be linked up.");

		if (cur->size() > 1) {
			// Grab the front and push it on the end.
			cur->data[cur->end++] = cur->data[cur->first++];
			if (cur->full())
				rebalance(priority);
		}
	}

	inline void clear() {
		for (int i = 0; i < NUM_QUEUES; ++i) {
			if (queues[i].data != nullptr)
				free(queues[i].data);
		}
		memset(queues, 0, sizeof(queues));
		first = invalid();
	}

	inline bool empty(u32 priority) const {
		const Queue *cur = &queues[priority];
		return cur->empty();
	}

	inline void prepare(u32 priority) {
		Queue *cur = &queues[priority];
		if (cur->next == nullptr)
			link(priority, INITIAL_CAPACITY);
	}

	void DoState(PointerWrap &p) {
		auto s = p.Section("ThreadQueueList", 1);
		if (!s)
			return;

		int numQueues = NUM_QUEUES;
		Do(p, numQueues);
		if (numQueues != NUM_QUEUES) {
			p.SetError(p.ERROR_FAILURE);
			ERROR_LOG(SCEKERNEL, "Savestate loading error: invalid data");
			return;
		}

		if (p.mode == p.MODE_READ)
			clear();

		for (int i = 0; i < NUM_QUEUES; ++i) {
			Queue *cur = &queues[i];
			int size = cur->size();
			Do(p, size);
			int capacity = cur->capacity;
			Do(p, capacity);

			if (capacity == 0)
				continue;

			if (p.mode == p.MODE_READ) {
				link(i, capacity);
				cur->first = (cur->capacity - size) / 2;
				cur->end = cur->first + size;
			}

			if (size != 0)
				DoArray(p, &cur->data[cur->first], size);
		}
	}

private:
	Queue *invalid() const {
		return (Queue *)-1;
	}

	// Initialize a priority level and link to other queues.
	void link(u32 priority, int size) {
		_dbg_assert_msg_(queues[priority].data == nullptr, "ThreadQueueList::Queue should only be initialized once.");

		// Make sure we stay a multiple of INITIAL_CAPACITY.
		if (size <= INITIAL_CAPACITY)
			size = INITIAL_CAPACITY;
		else {
			int goal = size;
			size = INITIAL_CAPACITY;
			while (size < goal)
				size *= 2;
		}

		// Allocate the queue.
		Queue *cur = &queues[priority];
		cur->data = (SceUID *)malloc(sizeof(SceUID) * size);
		cur->capacity = size;
		// Start smack in the middle so it can move both directions.
		cur->first = size / 2;
		cur->end = size / 2;

		for (int i = (int)priority - 1; i >= 0; --i) {
			// This queue is before ours, and points past us.
			// We'll have it point to our new queue, inserting into the chain.
			if (queues[i].next != nullptr) {
				cur->next = queues[i].next;
				queues[i].next = cur;
				return;
			}
		}

		// Never found above - that means there's no better queue yet.
		// The new one is now first, and whoever was first is after it.
		cur->next = first;
		first = cur;
	}

	// Move or allocate as necessary to maintain free space on both sides.
	void rebalance(u32 priority) {
		Queue *cur = &queues[priority];
		int size = cur->size();
		// Basically full.  Time for a larger queue?
		if (size >= cur->capacity - 2) {
			int new_capacity = cur->capacity * 2;
			SceUID *new_data = (SceUID *)realloc(cur->data, new_capacity * sizeof(SceUID));
			if (new_data != nullptr) {
				// Success, it's bigger now.
				cur->capacity = new_capacity;
				cur->data = new_data;
			}
		}

		// If we center all the items, it should start here.
		int newFirst = (cur->capacity - size) / 2;
		if (newFirst != cur->first) {
			memmove(&cur->data[newFirst], &cur->data[cur->first], size * sizeof(SceUID));
			cur->first = newFirst;
			cur->end = newFirst + size;
		}
	}

	// The first queue that's ever been used.
	Queue *first;
	// The priority level queues of thread ids.
	Queue queues[NUM_QUEUES];
};
