#pragma once

#include "card.h"

#include <vector>
#include <list>
#include <cmath>
#include <algorithm>
#include <random>
#include <tuple>

using namespace std;

class Spider {
public:
	static const int MARGIN_X = 10;
	static const int MARGIN_Y = 20;

	static const int FINALS_COUNT = 8; // 8 колод
	static const int STACKS_COUNT = 10;
	static const int INITIAL_CARDS_COUNT = 54; // 6 колод по 9 карт

	struct State {
		vector<Card*> cards;

		list<int> deckCards;
		list<int> finalCards;
		list<int> futureCards;

		int finishedStacks;
	};

	list<State*> history;

	vector<Card*> cards;
	int maxColorsCount;

	list<Card*> deckCards;
	list<Card*> finalCards;
	list<Card*> futureCards;

	list<Card*> selectedCards;

	int finishedStacks;
	uint64_t maxZIndex;

	bool mouseDown;
	IPoint mouseDownAt;

	DWORD finishedAt = 0;

	Spider(int maxColorsCount = 4) : maxColorsCount(maxColorsCount), finishedStacks(0), maxZIndex(0) {
		for (int i = 0; i < FINALS_COUNT; i++) {
			for (int j = 0; j < 13; j++) {
				Card* card = new Card(j, i % maxColorsCount, false);
				card->id = i * 13 + j;

				cards.push_back(card);
			}
		}

		random_device rd;
		mt19937 g(rd());
		ranges::shuffle(cards, g);

		for (int i = INITIAL_CARDS_COUNT; i < cards.size(); i++) {
			cards[i]->animating = false;
			cards[i]->visible = false;
			resetCardPosition(cards[i], Future);
			cards[i]->resetDrawPosition();

			futureCards.push_back(cards[i]);
		}

		for (int i = 0; i < INITIAL_CARDS_COUNT; i++) {
			int stack = i % STACKS_COUNT;
			int positionInStack = i / STACKS_COUNT;

			cards[i]->stack = stack;
			cards[i]->positionInStack = positionInStack;
			cards[i]->animating = true;
			cards[i]->visible = false;
			resetCardPosition(cards[i], Deck);

			deckCards.push_back(cards[i]);
		}

		for (int i = 0; i < STACKS_COUNT; i++) {
			Card* card = findStackBottom(i);
			if (card) {
				card->visible = true;
			}
		}

		for (int i = 0; i < cards.size(); i++) {
			cards[i]->zIndex = maxZIndex++;
			cards[i]->update(0);
		}
	}

	~Spider() {
		for (Card* card : cards) {
			delete card;
		}

		for (State* state : history) {
			for (Card* card : state->cards) {
				delete card;
			}

			delete state;
		}
	}

	void update(float deltaTime) {
		for (int i = 0; i < cards.size(); i++) {
			cards[i]->update(deltaTime);
		}

		if (deckCards.empty()) {
			if (finishedAt == 0) {
				finishedAt = GetTickCount();
			}
		}
	}

	void draw(ID2D1RenderTarget* rt, ID2D1Bitmap* cardsSheet) {
		float srcLeft = 13 * (CARD_SPRITE_WIDTH + CARD_SPRITE_MARGIN_X);
		float srcTop = 1 * (CARD_SPRITE_HEIGHT + CARD_SPRITE_MARGIN_Y);
		D2D1_RECT_F srcRect = D2D1::RectF(srcLeft, srcTop, srcLeft + CARD_SPRITE_WIDTH, srcTop + CARD_SPRITE_HEIGHT);
		for (int i = 0; i < FINALS_COUNT; i++) {
			D2D1_RECT_F dest = D2D1::RectF((float)(MARGIN_X + i * (CARD_WIDTH + MARGIN_X)), (float)MARGIN_Y, (float)(MARGIN_X + i * (CARD_WIDTH + MARGIN_X) + CARD_WIDTH), (float)(MARGIN_Y + CARD_HEIGHT));
			rt->DrawBitmap(cardsSheet, dest, 1.0f, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, &srcRect);
		}

		for (int i = 0; i < STACKS_COUNT; i++) {
			D2D1_RECT_F dest = D2D1::RectF((float)(MARGIN_X + i * (CARD_WIDTH + MARGIN_X)), (float)(MARGIN_Y * 2 + CARD_HEIGHT), (float)(MARGIN_X + i * (CARD_WIDTH + MARGIN_X) + CARD_WIDTH), (float)(MARGIN_Y * 2 + CARD_HEIGHT + CARD_HEIGHT));
			rt->DrawBitmap(cardsSheet, dest, 1.0f, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, &srcRect);
		}

		{
			D2D1_RECT_F dest = D2D1::RectF((float)(MARGIN_X + (STACKS_COUNT - 1) * (CARD_WIDTH + MARGIN_X)), (float)MARGIN_Y, (float)(MARGIN_X + (STACKS_COUNT - 1) * (CARD_WIDTH + MARGIN_X) + CARD_WIDTH), (float)(MARGIN_Y + CARD_HEIGHT));
			rt->DrawBitmap(cardsSheet, dest, 1.0f, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, &srcRect);
		}

		for (Card* card : cards) {
			card->draw(rt, cardsSheet);
		}

		if (finishedAt != 0) {
			D2D1_SIZE_U currentSize = rt->GetPixelSize();

			for (Card* card : cards) {
				card->position.X = sinf((GetTickCount() - finishedAt) / 1000.0f + card->id) * CARD_WIDTH * 2 + currentSize.width / 2 - CARD_WIDTH / 2;
				card->position.Y = cosf((GetTickCount() - finishedAt) / 1000.0f + card->id) * CARD_HEIGHT * 2 + currentSize.height / 2 - CARD_HEIGHT / 2;
				card->animating = true;
			}
		}
	}

	void onMouseMove(int x, int y) {
		if (!mouseDown) return;

		for (Card* card : selectedCards) {
			card->drawPosition.X = card->position.X + x - mouseDownAt.X;
			card->drawPosition.Y = card->position.Y + y - mouseDownAt.Y;
		}
	}

	void onMouseDown(int x, int y) {
		mouseDown = true;
		mouseDownAt.X = x;
		mouseDownAt.Y = y;

		if (!futureCards.empty()) {
			Card* futureCard = *futureCards.begin();

			if (x >= futureCard->position.X && x <= futureCard->position.X + CARD_WIDTH && y >= futureCard->position.Y && y <= futureCard->position.Y + CARD_HEIGHT) {
				dealCards();
				return;
			}
		}

		selectedCards.clear();
		Card* card = getCardAt(x, y);
		if (card && canMove(card)) {
			int stack = card->stack;
			for (int positionInStack = card->positionInStack;; positionInStack++) {
				Card* currCard = findCard(stack, positionInStack);
				if (!currCard) {
					break;
				}
				currCard->animating = false;
				currCard->zIndex = maxZIndex++;
				selectedCards.push_back(currCard);
			}

			sortCardsByZIndex();
		}
	}

	void onMouseUp(int x, int y) {
		mouseDown = false;

		if (!selectedCards.empty()) {
			Card* topCard = *selectedCards.begin();

			int stack = getStackAt(topCard->drawPosition.X + CARD_WIDTH / 2, topCard->drawPosition.Y + CARD_HEIGHT / 2);
			if (stack != -1 && canDrop(topCard, stack)) {
				dropCards(topCard, stack);
			}
			else {
				for (Card* card : selectedCards) {
					card->animating = true;
				}
			}

			selectedCards.clear();

			sortCardsByZIndex();
		}
	}

	void onMouseDoubleClick(int x, int y) {
		Card* card = getCardAt(x, y);
		if (card && canMove(card)) {
			vector<int> possibleStacks;
			vector<int> allPossibleStacks;

			for (int i = 0; i < STACKS_COUNT; i++) {
				if (canDrop(card, i)) {
					Card* stackBottom = findStackBottom(i);
					if (stackBottom && stackBottom->color == card->color) {
						possibleStacks.push_back(i);
					}
					allPossibleStacks.push_back(i);
				}
			}

			random_device rd;
			mt19937 g(rd());

			if (!possibleStacks.empty()) {
				// сначала сортируем по количеству уже выложенных карт того же цвета
				std::sort(possibleStacks.begin(), possibleStacks.end(), [this](const int a, const int b) {
					int lenA = getLength(findStackBottom(a));
					int lenB = getLength(findStackBottom(b));
					return lenA > lenB;
				});

				// если таких несколько - убираем все, кроме тех, у которых больше всего карт того же цвета
				int maxLength = getLength(findStackBottom(possibleStacks[0]));
				possibleStacks.erase(std::remove_if(possibleStacks.begin(), possibleStacks.end(), [this, maxLength](const int stack) {
					int length = getLength(findStackBottom(stack));
					return length < maxLength;
				}), possibleStacks.end());

				// теперь сортируем по размеру всей стопки - чем меньше карт, тем выше приоритет
				std::sort(possibleStacks.begin(), possibleStacks.end(), [this](const int a, const int b) {
					return findStackBottom(a)->positionInStack < findStackBottom(b)->positionInStack;
				});
				
				// удаляем стопки, у которых меньше карт, чем в стопке с максимальным приоритетом
				int maxStackPositionInStack = findStackBottom(possibleStacks[0])->positionInStack;
				possibleStacks.erase(std::remove_if(possibleStacks.begin(), possibleStacks.end(), [this, maxStackPositionInStack](const int stack) {
					int stackPositionInStack = findStackBottom(stack)->positionInStack;
					return stackPositionInStack < maxStackPositionInStack;
				}), possibleStacks.end());

				ranges::shuffle(possibleStacks, g);

				dropCards(card, possibleStacks[0]);

				sortCardsByZIndex();
			} else if (!allPossibleStacks.empty()) {
				ranges::shuffle(allPossibleStacks, g);

				dropCards(card, allPossibleStacks[0]);

				sortCardsByZIndex();
			}
		}
	}

	void pushHistory() {
		State* state = new State();

		for (Card* card : cards) {
			state->cards.push_back(card->clone());
		}

		for (Card* card : deckCards) {
			state->deckCards.push_back(card->id);
		}
		for (Card* card : finalCards) {
			state->finalCards.push_back(card->id);
		}
		for (Card* card : futureCards) {
			state->futureCards.push_back(card->id);
		}

		state->finishedStacks = finishedStacks;

		history.push_back(state);
	}

	void popHistory() {
		if (history.empty()) return;

		mouseDown = false;
		selectedCards.clear();

		State* state = history.back();
		history.pop_back();

		for (Card* hCard : state->cards) {
			Card* card = findCardByID(hCard->id);
			if (card) {
				card->index = hCard->index;
				card->color = hCard->color;
				card->visible = hCard->visible;

				card->zIndex = hCard->zIndex;

				card->stack = hCard->stack;
				card->positionInStack = hCard->positionInStack;

				card->position = hCard->position;
				card->animating = true;
			}

			delete hCard;
		}

		deckCards.clear();
		finalCards.clear();
		futureCards.clear();

		for (int id : state->deckCards) {
			deckCards.push_back(findCardByID(id));
		}
		for (int id : state->finalCards) {
			finalCards.push_back(findCardByID(id));
		}
		for (int id : state->futureCards) {
			futureCards.push_back(findCardByID(id));
		}

		finishedStacks = state->finishedStacks;

		delete state;

		sortCardsByZIndex();
	}
protected:
	enum CardLocation {
		Deck,
		Final,
		Future
	};

	void resetCardPosition(Card* card, CardLocation location) {
		if (!card) return;

		switch (location) {
		case Deck:
			card->position.X = MARGIN_X + card->stack * (CARD_WIDTH + MARGIN_X);
			card->position.Y = MARGIN_Y * 2 + CARD_HEIGHT + (int)(card->positionInStack * CARD_HEIGHT * 0.2f);
			break;
		case Final:
			card->position.X = MARGIN_X + card->stack * (CARD_WIDTH + MARGIN_X);
			card->position.Y = MARGIN_Y;
			break;
		case Future:
			card->position.X = MARGIN_X + (STACKS_COUNT - 1) * (CARD_WIDTH + MARGIN_X);
			card->position.Y = MARGIN_Y;
			break;
		}
	}

	void sortCardsByZIndex() {
		std::sort(cards.begin(), cards.end(), [](const Card* a, const Card* b) {
			return a->zIndex < b->zIndex;
		});
	}

	int getStackAt(int x, int y) {
		if (y < MARGIN_Y * 2 + CARD_HEIGHT) {
			return -1;
		}

		if (x < 0 || x > MARGIN_X * 2 + STACKS_COUNT * (CARD_WIDTH + MARGIN_X)) {
			return -1;
		}

		return (x - MARGIN_X / 2) / (CARD_WIDTH + MARGIN_X);
	}

	Card* findCardByID(int id) {
		for (Card* card : cards) {
			if (card->id == id) {
				return card;
			}
		}
		return NULL;
	}

	Card* getCardAt(int x, int y) {
		Card* result = NULL;
		for (Card* card : deckCards) {
			if (card->position.X <= x && card->position.X + CARD_WIDTH >= x && card->position.Y <= y && card->position.Y + CARD_HEIGHT >= y) {
				if (result == NULL || result->positionInStack < card->positionInStack) {
					result = card;
				}
			}
		}
		return result;
	}

	Card* findCard(int stack, int positionInStack) {
		for (Card* card : deckCards) {
			if (card->stack == stack && card->positionInStack == positionInStack) {
				return card;
			}
		}
		return NULL;
	}

	Card* findStackBottom(int stack) {
		Card* result = NULL;
		for (Card* card : deckCards) {
			if (card->stack == stack) {
				if (result == NULL || result->positionInStack < card->positionInStack) {
					result = card;
				}
			}
		}
		return result;
	}

	bool canMove(Card* card) {
		if (!card) return false;
		if (!card->visible) return false;

		bool canMove = true;
		Card* currCard = card;
		for (int i = card->positionInStack + 1;; i++) {
			Card* nextCard = findCard(card->stack, i);
			if (nextCard == NULL) {
				break;
			}
			if (nextCard->index != currCard->index - 1 || nextCard->color != currCard->color) {
				canMove = false;
				break;
			}
			currCard = nextCard;
		}
		return canMove;
	}

	bool canDrop(Card* card, int stack) {
		if (!card) return false;
		Card* stackBottom = findStackBottom(stack);
		if (stackBottom == NULL) {
			return true;
		}
		else {
			return stackBottom->index == card->index + 1;
		}
	}

	int getLength(Card* bottomCard) {
		if (!bottomCard) {
			return 0;
		}

		int length = 0;
		for (int i = 0;; i++) {
			Card* card = findCard(bottomCard->stack, bottomCard->positionInStack - i);
			if (!card || card->index != bottomCard->index + i || card->color != bottomCard->color) {
				break;
			}
			length++;
		}
		return length;
	}

	int getStackBottomLength(int stack) {
		Card* stackBottom = findStackBottom(stack);
		if (stackBottom == NULL) {
			return 0;
		}

		return getLength(stackBottom);
	}

	void checkFinalCards() {
		for (int i = 0; i < STACKS_COUNT; i++) {
			int length = getStackBottomLength(i);
			if (length < 13) {
				continue;
			}

			Card* stackBottom = findStackBottom(i);

			for (int j = 0; j < 13; j++) {
				Card* card = findCard(i, stackBottom->positionInStack - j);
				if (card) {
					card->stack = finishedStacks;
					card->animating = true;
					card->zIndex = maxZIndex++;
					resetCardPosition(card, Final);

					finalCards.push_back(card);
					erase(deckCards, card);
				}
			}
			finishedStacks++;

			Card* card = findStackBottom(i);
			if (card) {
				card->visible = true;
			}

			sortCardsByZIndex();
		}
	}

	void dealCards() {
		if (futureCards.empty()) {
			return;
		}

		for (int i = 0; i < STACKS_COUNT; i++) {
			if (findStackBottom(i) == NULL) {
				return;
			}
		}

		pushHistory();

		for (int i = 0; i < STACKS_COUNT; i++) {
			Card* card = *futureCards.begin();
			if (card == NULL) {
				break;
			}

			Card* stackBottom = findStackBottom(i);

			card->stack = i;
			card->positionInStack = stackBottom ? stackBottom->positionInStack + 1 : 0;
			card->visible = true;
			card->animating = true;
			card->zIndex = maxZIndex++;
			resetCardPosition(card, Deck);

			deckCards.push_back(card);
			erase(futureCards, card);
		}

		checkFinalCards();
		sortCardsByZIndex();
	}

	void dropCards(Card* topCard, int stack) {
		pushHistory();

		if (topCard->positionInStack > 0) {
			Card* belowCard = findCard(topCard->stack, topCard->positionInStack - 1);
			if (belowCard) {
				belowCard->visible = true;
			}
		}

		Card* bottomCard = findStackBottom(stack);
		int maxPositionInStack = bottomCard ? bottomCard->positionInStack : -1;


		int prevStack = topCard->stack;
		int prevPositionInStack = topCard->positionInStack;

		for (int i = 0;; i++) {
			Card* card = findCard(prevStack, prevPositionInStack + i);
			if (!card) {
				break;
			}

			card->stack = stack;
			card->positionInStack = maxPositionInStack + 1 + i;
			card->animating = true;
			card->zIndex = maxZIndex++;
			resetCardPosition(card, Deck);
		}

		checkFinalCards();
	}
};