package server;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import org.json.JSONArray;
import org.json.JSONObject;

import card.Card;
import player.BasePlayer;

public class ServerGame extends BaseGame {
	ServerSocket server;

	public enum PROTOCOL {
		VIBE_DUMB
	};

	List<Card> cards;
	long seed;
	Random rand;

	int movesLeft;
	boolean won;
	boolean lost;
	
	List<Socket> sockets;


	public static void main(String[] args){
		ServerGame sg = new ServerGame();
	}

	public ServerGame() {
		establishConnection();
	}
	
	protected void establishConnection(){
		port = 1337;
		sockets = new ArrayList<>();
		try {
			server = new ServerSocket(port);
			while(nrOfPlayers < 2){ //TODO
				Socket socket = server.accept();
				sockets.add(socket);
				JSONObject connectionJson = new JSONObject();
				connectionJson.put("id", nrOfPlayers);
				sendToClient(CONNECTION_VALID, connectionJson.toString(), socket);
				nrOfPlayers++;
				System.out.println(nrOfPlayers + " players connected!");
				// TODO do this somewhere else 
				System.out.println("Creating new Socket Listener for #" + (sockets.size()-1));
				System.out.println("Socket id: " + socket);
				new Thread(new SocketListenerThread(socket)).start();
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			// TODO exit
			e.printStackTrace();
		}
		System.out.println("Initalizing game...");
		init();
		System.out.println("Sending game to clients");
		sendToAll(MOVE_UPDATE, toJson().toString());
	}

	@Override
	protected void init() {
		super.init();
		seed = 0;
		rand = new Random(seed);
		movesLeft = -1;
		createPlayers();
		currentPlayer = 0;
		// shuffleCards(); // TODO use this instead of addCards()
		addCards();
		dealCards();
	}

	protected void shuffleCards() {
		cards = new ArrayList<>();
		//TODO add cards
		//TODO shuffle
	}

	// 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;
	// updateCard(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 receive(Socket socket) {
		DataInputStream inStream = null;
		byte[] magic = new byte[4];
		byte proto = 0;
		byte msgType = 0;
		byte[] useless = new byte[2];
		try {
			inStream = new DataInputStream(socket.getInputStream());
			inStream.read(magic);
			proto = inStream.readByte();
			msgType = inStream.readByte();
			inStream.read(useless);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		// TODO check Protocol
//		switch(proto){
//		case VIBE_DUMB:
//			break;
//		}
		
		String msg = null;
		JSONObject jo = null;
		boolean isValid = true;
		boolean wasMove = false;
		try {
			msg = inStream.readUTF();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if(msg != null) jo = new JSONObject(msg);
		// TODO MOVE_DRY
		switch(msgType){
		case MOVE_PLACE_CARD:
			isValid = placeCard(new Card(jo));
			wasMove = true;
			break;
		case MOVE_TRASH_CARD:
			isValid = moveCardToTrash(new Card(jo));
			wasMove = true;
			break;
		case MOVE_HINT:
			int hintType = jo.getInt("type");
			int value = jo.getInt("value");
			int player = jo.getInt("player_id");
			isValid = giveHint(hintType, value, player);
			wasMove = true;
			break;
		}
		if(!isValid) sendToAll(MOVE_INVALID);
		if(wasMove){
			currentPlayer++;
			if(currentPlayer == nrOfPlayers) currentPlayer = 0;
			// TODO notify players
			if(movesLeft >= 0) movesLeft--;
			sendToAll(MOVE_UPDATE, toJson().toString());
		}
	}

	protected Card getRealCard(Card c, int[] index){
		Card[] cards = getPlayer(currentPlayer).getCards();
		for(int i=0; i<cards.length; ++i){
			Card card = cards[i];
			if(card.getId() == c.getId()){
				if(card.getColorInt() == c.getColorInt() && card.getValue() == c.getValue()){
					if(index != null) index[0] = i;
					return card;
				}
			}
		}
		return null;
	}
	
	protected boolean placeCard(Card c) {
		int[] index = new int[1];
		Card card = getRealCard(c, index);
		if(card == null) return false;
		int color = card.getColorInt();
		int value = card.getValue();
		

		int deckValue = deckCounter[color];
		if(deckValue == value){ //place card possible
			deckCounter[color]++;
			deck[color][value] = card;
			if (value + 1 == 5) addHint();
		} else { //card too high or too low
			addFlash();
			moveCardToTrash(card, true);
		}
		setWonOrLost();
		dealCard(index[0], currentPlayer);
		return true;
	}
	
	protected boolean moveCardToTrash(Card c){
		return moveCardToTrash(c, false);
	}
	
	protected boolean moveCardToTrash(Card c, boolean receivedFlash) {
		int[] index = new int[1];
		Card card = getRealCard(c, index);
		if(card == null) return false;
		// TODO Fix clone removal
		if(!receivedFlash) addHint();
		trash.add(card);
		dealCard(index[0], currentPlayer);
		return true;
	}
	
	protected boolean giveHint(int type, int value, int playerId){
		if(!hintAvailable()) return false;
		if(currentPlayer == playerId) return false;
		Card[] cards = getPlayer(playerId).getCards();
		for(Card c : cards){
			boolean is = false;
			if(type == HINT_COLOR) is = c.getColorInt() == value;
			else if(type == HINT_VALUE) is = c.getValue() == value;
			else throw new RuntimeException(); // TODO "vorerst"
			c.addCardInfo(type, is, value, currentPlayer);
		}
		removeHint();
		return true;
	}

	protected void createPlayers() {
		players = new BasePlayer[nrOfPlayers];
		for(int i=0; i<nrOfPlayers; ++i){
			players[i] = new BasePlayer(i, CARDS_PER_PLAYER, "Player " + i);
		}
	}

	protected void dealCards() {
		for (int dealtCards =0; dealtCards < CARDS_PER_PLAYER; ++dealtCards) {
			for (int i = 0; i < nrOfPlayers; ++i) {
				int index = random(cardsInDeck--);
				Card card = cards.remove(index);
				players[i].setCard(dealtCards, card);
				// TODO use this code as soon as the shuffle is implemented
				// Card card = getCardFromDeck();
				// players[i].setCard(dealtCards, card);
			}
		}
	}

	//FIXME no endless do-while...
	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) {
			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), i));
		}
	}
	
	private int[] randomPosition(){
		int[] p = new int[2];
		p[0] = random(5);
		p[1] = random(5);
		return p;
	}
	
	private int random(int max){
		return rand.nextInt(max);
	}

	private void dealCard(int cardPos, int playerID) {
		Card card;
		if (cardsInDeck > 0) {
			int index = random(cardsInDeck--);
			card = cards.remove(index);
			// TODO Use this code as soon as shuffle is implemented
			// card = getCardFromDeck();
		} else {
			if (movesLeft == -1) movesLeft = nrOfPlayers;
			card = Card.DUMMY_CARD;
		}
		players[playerID].setCard(cardPos, card);
	}

	void setWonOrLost() {
		boolean won = true;
		boolean lost = false;
		for (int i = 0; i < COLORS; ++i) {
			if (deckCounter[i] != 5) {
				won = false;
				break;
			}
		}
		if (flashs == MAX_FLASHS) {
			lost = true;
			won = false;
		}
		if (movesLeft == 0)
			lost = true;
		if (won){
			sendToAll(MOVE_WON);
		}
		else if (lost){
			sendToAll(MOVE_LOST);
		}
		this.won = won;
		this.lost = lost;
	}
	
	protected void sendToClient(byte msgType, Socket s){
		sendToClient(msgType, null, s);
	}
	
	protected void sendToClient(byte msgType, String msg, Socket s){
		DataOutputStream os = null;
		try {
			os = new DataOutputStream(s.getOutputStream());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			os.write(MAGIC);
			os.write(ServerGame.PROTOCOL.VIBE_DUMB.ordinal());
			os.write(msgType);
			os.write(new byte[]{ 0, 0 });
			if(msg != null) os.writeUTF(msg);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	protected void sendToAll(byte msgType){
		sendToAll(msgType, null);
	}
	
	protected void sendToAll(byte msgType, String msg){
		int i=0;
		for(Socket s : sockets){
			System.out.println("Sending " + msg + " to socket " + (i++));
			sendToClient(msgType, msg, s);
		}
	}

	protected void removeHint() {
		hints--;
	}

	protected void addHint() {
		if (hints != MAX_HINTS) {
			hints++;
			if (hints > MAX_HINTS)
				hints = MAX_HINTS;
		}
	}

	protected void addFlash() {
		flashs++;
	}

	@Override
	protected void createUI() {
		// TODO Auto-generated method stub
	}

	/**
	 * 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);
	}
	
	protected JSONObject toJson(){
		JSONObject root = new JSONObject();
		root.put("movesLeft", movesLeft);
		root.put("cardsInDeck", cardsInDeck);
		root.put("hints", hints);
		root.put("flashs", flashs);
		root.put("movesLeft", movesLeft);
		root.put("movesLeft", movesLeft);
		root.put("movesLeft", movesLeft);
		root.put("movesLeft", movesLeft);
		root.put("movesLeft", movesLeft);
		JSONArray trashArray = new JSONArray();
		for(Card c : trash){
			trashArray.put(c.toJson());
		}
		root.put("trash", trashArray);
		
		JSONObject deckRow = new JSONObject();
		for(int i=0; i<deck.length; ++i){
			JSONObject deckCol = new JSONObject();
			for(int j=0; j<deck[i].length; ++j){
				deckCol.put(String.valueOf(j), deck[i][j]);
			}
			deckRow.put(String.valueOf(i), deckCol);
		}
		JSONArray playerArray = new JSONArray();
		for(BasePlayer p : players){
			playerArray.put(p.toJson());
		}
		root.put("players", playerArray);
		root.put("currentPlayer",  currentPlayer);
		return root;
	}

	@Override
	protected void createHistory() {
		history = new ArrayList<>();
	}
}
