package server;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

import org.json.*;

public abstract class BaseGame {
	/*
	 * COLORS:
	 * GREEN
	 * RED
	 * YELLOW
	 * WHITE
	 * BLUE
	 * (COLORED)
	 */

	public enum MSG_TYPES {ERROR, WARNING, INFORMATION, QUESTION};
	
	final static int GREEN = 0;
	final static int RED = 1;
	final static int YELLOW = 2;
	final static int WHITE = 3;
	final static int BLUE = 4;
	//final static int COLORED = 5;
	final static int COLORS = 5;
	final static int N_CARDS = 50;
	final static int MAX_HINTS = 8;
	final static int MAX_FLASHS = 3;
	
	int N_PLAYERS = 3;
	static int CARDS_PER_PLAYER;
	int CARDS_IN_DECK;
	
	int hints;
	int flashs;
	Card[][] deck;
	int[] deckCounter;
	ArrayList<Card> trash;
	AbstractPlayer[] players;
	int currentPlayer;
	boolean won;
	boolean lost;
	
	int port;
	
	List<String> history;

	public BaseGame(){
		init();
	}
	
	protected void init(){
		history = new ArrayList<>();
		loadOptions();
		loadFont();
		//establishConnection();
		CARDS_PER_PLAYER = (N_PLAYERS <= 3) ? 5 : 4;
		hints = MAX_HINTS;
		flashs = 0;
		roundsLeft = -1;
		createPlayers();
		currentPlayer = 0;
		resetMoveValues();
		addCards();
		CARDS_IN_DECK = cards.size();
		createUI();
		dealCards();
	}
	
	protected void createPlayers(){
		players = new AbstractPlayer[N_PLAYERS];
	}
	
	private void addCards(){
		cards = new ArrayList<>();
		deck = new Card[COLORS][5];
		deckCounter = new int[5];
		for(int i=0; i<5; ++i){
			deckCounter[i] = 0;
		}
		trash = new ArrayList<>();
		int[] dist = new int[]{3, 2, 2, 2, 1};
		int[][] tmp_cards = new int[][]{
				dist.clone(),
				dist.clone(),
				dist.clone(),
				dist.clone(),
				dist.clone()
			};
		for(int i=0; i<N_CARDS; ++i){
			//Point p = null;
			int[] p = new int[2];
			do{
				p = randomPosition();
			} while(tmp_cards[p[0]][p[1]] == 0);
			tmp_cards[p[0]][p[1]]--;
			cards.add(new Card(p[0], (p[1] + 1), -1, this));
		}
	}
	
	protected abstract void createUI();
	
	void moveCardToTrash(int index){
		trash.add(players[currentPlayer].getCard(index).clone());
		updateCards(index);
	}
	
	void updateCards(int index){
		dealCard(index, currentPlayer);
		players[currentPlayer].setCardInfo(index, "");
		Card c = players[currentPlayer].getCard(index);
		if(c.getValue() == -1){ //dummy card, no more cards
			setNoCard(index);
		}
	}
	
	protected abstract void setNoCard(int index);
	
	private void dealCard(int cardID, int playerID){
		Card card;
		if(CARDS_IN_DECK > 0){
			int index = random(CARDS_IN_DECK--);
			card = cards.remove(index);
		} else {
			if(roundsLeft == -1) roundsLeft = N_PLAYERS;
			card = new Card(3, -1, playerID, this);
		}
		players[playerID].setCard(cardID, card);
	}
	
	protected void dealCards(){
		int dealtCards = 0;
		for(; dealtCards < CARDS_PER_PLAYER; ++dealtCards){
			for(int i=0; i<N_PLAYERS; ++i){
				int index = random(CARDS_IN_DECK--);
				Card card = cards.remove(index);
				players[i].setCard(dealtCards, card);
			}
		}
	}
	
	boolean hintAvailable(){
		return hints > 0;
	}
	
	void setWonOrLost(){
		boolean won = true;
		boolean lost = false;
		for(int i=0; i<COLORS; ++i){
			System.out.println("Deck " + i + ": " + deckCounter[i]);
			if(deckCounter[i] != 5){
				won = false;
				break;
			}
		}
		if(thunders == MAX_THUNDERS){
			lost = true;
			won = false;
		}
		if(roundsLeft == 0) lost = true;
		if(won) printMessage("You won!", MSG_TYPES.INFORMATION);
		else if(lost) printMessage("You lost...", MSG_TYPES.INFORMATION);
		this.won = won;
		this.lost = lost;
	}
	
	private int[] randomPosition(){
		int[] p = new int[2];
		p[0] = random(5);
		p[1] = random(5);
		return p;
	}
	
	private int random(int max){
		return (int) (Math.random() * max);
	}
	
	public void setSelectedPlayer(int selectedPlayer){
		this.selectedPlayer = selectedPlayer;
	}
	
	public int getSelectedPlayer(){
		return selectedPlayer;
	}
	
	public AbstractPlayer getPlayer(int index){
		return players[index];
	}
	
	public int getCurrentPlayerIndex(){
		return currentPlayer;
	}
	
	public boolean isTrashCard(){
		return trashCard;
	}
	
	public boolean isPlaceCard(){
		return placeCard;
	}
	
	public boolean isUseColor(){
		return useColor;
	}
	
	public boolean isUseValue(){
		return useValue;
	}
	
	public int getChosenColor(){
		return chosenColor;
	}
	
	public void setChosenColor(int chosenColor){
		this.chosenColor = chosenColor;
	}
	
	public int getChosenValue(){
		return chosenValue;
	}
	
	public void setChosenValue(int chosenValue){
		this.chosenValue = chosenValue;
	}
	
	public int getSelectedCards(){
		return selectedCards;
	}
	
	public void addSelectedCards(){
		selectedCards++;
	}
	
	public void removeSelectedCards(){
		selectedCards--;
	}
	
	public int getDifficulty(){
		return difficulty;
	}
	
	public abstract void loadFont();
	
	public int getFontSize(){
		return fontSize;
	}
	
	public abstract Object getFont();


	public abstract void printMessage(String msg, MSG_TYPES type);
	
	protected void removeHint(){
		hints--;
	}
	
	protected void addHint(){
		if(hints != MAX_HINTS){
			hints++;
			if(hints > MAX_HINTS) hints = MAX_HINTS;
		}
	}
	
	protected void addThunder(){
		thunders++;
	}
	
	protected void placeCard(Card c, int color, int value){
		deckCounter[color]++;
		deck[color][value] = c;
		if(value+1 == 5) addHint();
	}
	
	protected void onNext(){
		if(!checkValidMove()){
			printMessage("Please select all cards of the selected color/value", MSG_TYPES.ERROR);
			return;
		}
		if(roundsLeft >= 0) roundsLeft--;
		int selCardIndex = 0;
		AbstractPlayer player = players[selectedPlayer];
		for(int i=0; i<CARDS_PER_PLAYER; ++i){
			Card card = player.getCard(i);
			String from = "(from Player " + currentPlayer + ")";
			if(card.isSelected()){
				selCardIndex = i;
				card.setSelected(false);
				if(useColor){
					String color = "- " + Card.intColorToText(chosenColor);
					player.setCardInfo(i, player.getCardInfo(i) + color + " " + from + "\n");
				} else if(useValue){
					String value = "- is " + chosenValue;
					player.setCardInfo(i, player.getCardInfo(i) + value + " " + from + "\n");
				}
			} else {
				if(useColor){
					String color = "- not " + Card.intColorToText(chosenColor);
					player.setCardInfo(i, player.getCardInfo(i) + color + " " + from + "\n");
				} else if(useValue){
					String value = "- is not " + chosenValue;
					player.setCardInfo(i, player.getCardInfo(i) + value + " " + from + "\n");
				}
			}
		}
		if(useColor || useValue){
			removeHint();
		} else if(trashCard){
			moveCardToTrash(selCardIndex);
			addHint();
		} else if(placeCard){
			Card c = players[currentPlayer].getCard(selCardIndex);
			int color = c.getColorInt();
			int value = c.getValue();
			int deckValue = deckCounter[color] + 1;
			if(deckValue == value){ //place card possible
				placeCard(c, color, value-1);
			} else { //card too high or too low
				addThunder();
				moveCardToTrash(selCardIndex);
			}
			setWonOrLost();
			if(lost) return;
			updateCards(selCardIndex);
		}
		players[currentPlayer].activeCards();
		printMessage("Player " + (currentPlayer+1) + " has finished his move!", MSG_TYPES.INFORMATION);
		currentPlayer++;
		if(currentPlayer == N_PLAYERS) currentPlayer = 0;
		players[currentPlayer].deactiveCards();
		resetMoveValues();
	}
	
	protected void onColor(){
		if(!hintAvailable()){
			printMessage("No hints left!", MSG_TYPES.ERROR);
			return;
		}
		if(selectedCards > 0){
			printMessage("Please deselect all cards first", MSG_TYPES.ERROR);
			return;
		}
		useColor = true;
		useValue = false;
		placeCard = false;
		trashCard = false;
	}
		
	protected void onValue(){
		if(!hintAvailable()){
			printMessage("No hints left!", MSG_TYPES.ERROR);
			return;
		}
		if(selectedCards > 0){
			printMessage("Please deselect all cards first", MSG_TYPES.ERROR);
			return;
		}
		useValue = true;
		useColor = false;
		placeCard = false;
		trashCard = false;
	}
	
	protected void onTrash(){
		if(selectedCards > 0){
			printMessage("Please deselect all cards first", MSG_TYPES.ERROR);
			return;
		}
		useColor = false;
		useValue = false;
		placeCard = false;
		trashCard = true;
	}
	
	protected void onPlace(){
		if(selectedCards > 0){
			printMessage("Please deselect all cards first", MSG_TYPES.ERROR);
			return;
		}
		useColor = false;
		useValue = false;
		placeCard = true;
		trashCard = false;
	}
	
	protected abstract void showTrash();
	
	/**
	 * Stores the current state of the game in the history
	 * @param json the current state as json object
	 */
	protected void addToHistory(){
		String json = "";
		history.add(json);
	}
	
	/**
	 * Returns the state of the game at a given @param move index
	 * @param move the index of the move you want to get.
	 * Positive values are interpreted as index from the start (0 is the initial game).
	 * Negative values are interpreted as index from the end (-1 is the last state).
	 */
	public List<String> getHistory(int move){
		final int index = move >= 0 ? move : history.size()-1;
		return  history.subList(0, index);
	}
}