#pragma once
#include <iostream>
#include <SFML/Audio.hpp>
#include "Bubble.h"

using namespace std;

const int GRIDROWS = 12, GRIDCOLS = 9;
const string newBubbleRowSFX = "Assets/Sounds/General Sounds/Neutral Sounds/sfx_sound_neutral6.wav";

sf::SoundBuffer newBubbleRowSFXBuffer;


class BubbleDelivery {
public:
	BubbleType type;
	int count;

	BubbleDelivery(int _count, BubbleType _type) {
		type = _type;
		count = _count;
	}
};

class BubbleGrid {
private:
	Bubble* grid[GRIDROWS][GRIDCOLS]{};
	sf::Sound newRowSound;
	
public:
	bool gameOver = false;

	BubbleGrid() {
		addRow();

		if (!newBubbleRowSFXBuffer.loadFromFile(newBubbleRowSFX)) {
			cout << "Error loading sound: " << newBubbleRowSFX << endl;
		}

		newRowSound = sf::Sound(newBubbleRowSFXBuffer);
	}

	void triggerGameOver() {
		gameOver = true;

		for (auto& row : getGrid()) {
			for (auto& bubble : row) {
				if (!bubble) continue;
				sf::Vector2i bubblePos = bubble->getRelativePosition(); // Why tf is my coords backwards? How tf did I code that??
				grid[bubblePos.y][bubblePos.x] = nullptr;

				bubble->popSFX();
			}
		}

	}

	void addRow() {
		if (gameOver || rowContainsBubbles(11)) {
			// Last row contains bubbles, game over condition has now been triggered
			triggerGameOver();
			return;
		}

		// We've got space, time to add to the grid :3

		// First: Move all existing bubbles down by one
		for (int row = (GRIDROWS - 2); row >= 0; row--) {
			for (int col = 0; col < GRIDCOLS; col++) {
				if (!grid[row][col]) continue;

				grid[row + 1][col] = grid[row][col];
				grid[row][col] = nullptr;
				//cout << "moved " << row << ":" << col << " to " << row + 1 << ":" << col << endl;
				grid[row + 1][col]->setPosition(col * 64.0f, (row + 1) * 64.0f);
			}
		}

		for (int col = 0; col < GRIDCOLS; col++) {
			grid[0][col] = new Bubble(col * 64.0f, 0.0f);
		}

		newRowSound.play();
	}

	bool rowContainsBubbles(int rowNumber) {
		for (Bubble* bubble : grid[rowNumber]) {
			if (!bubble) continue;
			return true;
		}
		return false;
	}

	Bubble* (&getGrid())[GRIDROWS][GRIDCOLS] {
		return grid;
	}

	int remainingBubbles() {
		int bubbles = 0;
		for (auto& row : getGrid()) {
			for (auto& col : row) {
				if (!col) continue;
				bubbles++;
			}
		}

		return bubbles;
	}

	Bubble* (&getRow(int row))[GRIDCOLS] {
		if (row < 0 || row >(GRIDROWS - 1)) {
			cout << "Expecting row between 0-11, received: " << row << endl;
			system("pause");
		}

		return grid[row];
	}

	BubbleDelivery* grabFromCol(int col, BubbleType limiter) {
		int row = GRIDROWS - 1;
		int foundBubbles = 0;
		while (row >= 0) {
			Bubble* bubble = grid[row][col];

			if (!bubble || bubble == nullptr) {
				row--;
				continue;
			}

			if (limiter > -1 && bubble->bubbleType != limiter) {
				cout << "Bubble Found, but wrong type so ending chain of collection" << endl;
				row = -1;
				continue;
			}
			else if (limiter == -1) {
				cout << "Bubble found, but no limit was set before - so using this type as the limiter" << endl;
				limiter = bubble->bubbleType;
			}

			foundBubbles++;
			grid[row][col] = nullptr;

			row--;
		}

		return new BubbleDelivery(foundBubbles, limiter);
	}

	enum checkIncomingDirection {
		LEFT,
		RIGHT,
		UP,
		DOWN
	};

	vector<Bubble*> checkTouchingOfSame(int row, int col, checkIncomingDirection ignoreDirection, int depth = 0) {
		cout << "checking for touching in " << row << " | " << col << " | " << depth << endl;
		Bubble* originBubble = grid[row][col];
		if ((depth > GRIDROWS * GRIDCOLS) || !originBubble || (depth > 0 && originBubble == originalCheckBubble)) return {};
		// The check above is hacky to prevent infinite recursion... I need to strip out the touch check into somewhere seperate
		// Where I can work on it in more detail.
		vector<Bubble*> similarBubbles;
		similarBubbles.push_back(originBubble);

		if (ignoreDirection != checkIncomingDirection::DOWN && row > 0) {
			// Check Above
			if (grid[row - 1][col] && grid[row - 1][col]->bubbleType == originBubble->bubbleType) {
				for (Bubble* bubble : checkTouchingOfSame(row - 1, col, checkIncomingDirection::UP, depth + 1)) {
					similarBubbles.push_back(bubble);
				}
			}
		}

		//if (ignoreDirection != checkIncomingDirection::UP && row < GRIDROWS - 1) {
		//	// Check below
		//	if (grid[row + 1][col] && grid[row + 1][col]->bubbleType == originBubble->bubbleType) {
		//		for (Bubble* bubble : checkTouchingOfSame(row + 1, col, checkIncomingDirection::DOWN, depth + 1)) {
		//			similarBubbles.push_back(bubble);
		//		}
		//	}
		//}

		if (ignoreDirection != checkIncomingDirection::RIGHT && col > 0) {
			// Check left
			if (grid[row][col - 1] && grid[row][col - 1]->bubbleType == originBubble->bubbleType) {
				for (Bubble* bubble : checkTouchingOfSame(row, col - 1, checkIncomingDirection::LEFT, depth + 1)) {
					similarBubbles.push_back(bubble);
				}
			}
		}

		if (ignoreDirection != checkIncomingDirection::LEFT && col < GRIDCOLS - 1) {
			// Check Right
			if (grid[row][col + 1] && grid[row][col + 1]->bubbleType == originBubble->bubbleType) {
				for (Bubble* bubble : checkTouchingOfSame(row, col + 1, checkIncomingDirection::RIGHT, depth + 1)) {
					similarBubbles.push_back(bubble);
				}
			}
		}

		return similarBubbles;
	}

	Bubble* originalCheckBubble; // This feels hacky... TODO: Revisit (Original purpose, prevent loops when checking for touching)

	int checkForMatches(int row, int col) {
		cout << "checking for match in " << row << " | " << col << endl;
		Bubble* originBubble = grid[row][col];
		if (!originBubble) return 0;

		originalCheckBubble = originBubble;

		vector<Bubble*> similarBubbles;

		for (Bubble* bubble : checkTouchingOfSame(row, col, (checkIncomingDirection)-1)) {
			similarBubbles.push_back(bubble);
		}

		cout << "Found " << similarBubbles.size() << " Similar bubbles" << endl;

		if (similarBubbles.size() >= 3) {
			cout << "Pop Time!" << endl;
			// Pop time!
			// TODO: Add about... maybe 2 points per bubble popped to score?
			for (Bubble* bubble : similarBubbles) {
				sf::Vector2i bubblePos = bubble->getRelativePosition(); // Why tf is my coords backwards? How tf did I code that??
				cout << "Popping bubble at: " << bubblePos.y << " | " << bubblePos.x << endl;
				grid[bubblePos.y][bubblePos.x] = nullptr;

				bubble->popSFX();
			}

			return similarBubbles.size() * 2;
		}

		return 0;
	}

	bool checkForFloaters() {
		bool movedBubble = false;

		for (int row = GRIDROWS - 1; row > 0; row--) {
			for (int col = GRIDCOLS - 1; col >= 0; col--) {
				if (!grid[row][col]) continue;

				if (!grid[row - 1][col]) {
					cout << "Floater!" << endl;
					grid[row - 1][col] = grid[row][col];
					grid[row][col] = nullptr;
					grid[row - 1][col]->setPosition(col * 64.0f, (row - 1) * 64.0f);
					movedBubble = true;

					int scoreGain = checkForMatches(row - 1, col);
					cout << "Threw away " << to_string(scoreGain) << " Points... TODO: Add this to player score!" << endl;
				}
			}
		}

		if (movedBubble) checkForFloaters();
		return movedBubble;
	}

	class AddToColResult {
	public:
		bool success = false;
		int scoreGain = 0;

		AddToColResult(bool _success = false, int  _scoreGain = 0) {
			success = _success;
			scoreGain = _scoreGain;
		}
	};

	AddToColResult* addToCol(int col, BubbleDelivery* incoming) {
		int row = GRIDROWS - 1;
		int startingRow = row;
		Bubble* lastBubble;
		while (row >= 0) {
			Bubble* bubble = grid[row][col];

			if (!bubble || bubble == nullptr) {
				row--;
				continue;
			}

			startingRow = row + 1;
			break;
		}

		if (startingRow + incoming->count > (GRIDROWS - 1)) {
			// TODO: Allow if incoming count is greater or equal to pop amount, because it will pop before game over.
			return new AddToColResult();
		}

		for (int i = 0; i < incoming->count; i++) {
			int row = startingRow + i;
			grid[row][col] = new Bubble(col * 64.0f, row * 64.0f, incoming->type);
		}

		int scoreGain = checkForMatches(startingRow + incoming->count - 1, col);
		checkForFloaters();

		return new AddToColResult(true, scoreGain);
	}
};