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.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import card.Card;
import player.BasePlayer;

public abstract class ClientGame extends BaseGame {

	public enum MSG_TYPES {ERROR, WARNING, INFORMATION, QUESTION};

	//Options stuff
	final File options = new File("options.json");
	JSONObject root;
	int difficulty;
	int fontSize;

	Socket socket;
	String hostName;
	volatile boolean isConnected;
	volatile boolean initGameReceived;
	volatile int ownId; // TODO lock?

	public ClientGame(){
		preinit();
	}
	
	protected void preinit(){
		port = 1337;
		hostName = "localhost";
		isConnected = false;
		try {
			socket = new Socket(hostName, port);
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		new Thread(new SocketListenerThread(socket)).start();
		while(!isConnected){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("Waiting for connection to host");
		}
		System.out.println("Connection to " + hostName + " on port " + port + " as player " + ownId + " established!");
		while(!initGameReceived){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("Client " + ownId + " waiting for game...");
		}
		init();
	}
	
	@Override
	protected void init() {
		super.init();
		loadOptions();
		loadFont();
		createPlayers();
		createUI();
	}
	@Override
	protected void createHistory() {
		history = new ArrayList<>();
	}

	@Override
	protected void createTrash() {
		trash = new ArrayList<>();
	}

	protected void loadOptions(){
		try {
			JSONTokener tokener = new JSONTokener(options.toURI().toURL().openStream());
			root = new JSONObject(tokener);
			difficulty = root.getInt("difficulty");
			fontSize = root.getInt("fontSize");
			if(difficulty < 0) difficulty = 0;
			if(fontSize <= 0) fontSize = 20;
		} catch (JSONException e) {
			e.printStackTrace();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	protected void movePlaceCard(Card c){
		movePlaceCard(c, false);
	}
	
	protected void movePlaceCard(Card c, boolean isDry) {
		byte msgType = isDry ? MOVE_PLACE_CARD | MOVE_DRY : MOVE_PLACE_CARD;
		send(msgType, c.toJson().toString());
	}

	protected void moveTrashCard(Card c){
		movePlaceCard(c, false);
	}

	protected void moveTrashCard(Card c, boolean isDry) {
		byte msgType = isDry ? MOVE_TRASH_CARD | MOVE_DRY : MOVE_TRASH_CARD;
		send(msgType, c.toJson().toString());
	}

	protected void moveHint(Card c){
		movePlaceCard(c, false);
	}

	protected void moveHint(Card c, boolean isDry) {
		byte msgType = isDry ? MOVE_HINT | MOVE_DRY : MOVE_HINT;
		send(msgType, c.toJson().toString());
	}

	private void send(byte msgType, String msg) {
		DataOutputStream os = null;
		try {
			os = new DataOutputStream(socket.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 });
			os.writeUTF(msg);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	//public abstract void printMessage(String msg, MSG_TYPES type);


	protected abstract void createUI();
	
	public int getDifficulty(){
		return difficulty;
	}
	
	public abstract void loadFont();

	public int getFontSize(){
		return fontSize;
	}
	
	public Object getFont() {
		// TODO
		return null;
	}

	@Override
	protected void receive(DataInputStream inStream) {
		System.out.println("Received data!");
		byte[] magic = new byte[4];
		byte proto = 0;
		byte msgType = 0;
		byte[] useless = new byte[2];
		try {
			inStream.read(magic);
			proto = inStream.readByte();
			msgType = inStream.readByte();
			inStream.read(useless);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println("Received magic: " + magic.toString());
		
		// TODO check Protocol
//		switch(proto){
//		case VIBE_DUMB:
//			break;
//		}
		
		String msg = null;
		JSONObject jo = null;
		try {
			msg = inStream.readUTF();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		// TODO error handling
		if(msg != null) jo = new JSONObject(msg);
		else return;
		// TODO MOVE_DRY
		switch(msgType){
		case CONNECTION_VALID:
			isConnected = true;
			ownId = jo.getInt("id");
			break;
		case MOVE_UPDATE:
			if(!initGameReceived){
				initGameReceived = true;
				// TODO move createXY() somewhere else?
				createPlayers();
				createDeck();
				createTrash();
			}
			updateGame(jo);
			System.out.println("Received game:");
			System.out.println(jo.toString(4));
			break;
		}
	}
	
	private void updateGame(JSONObject jo){
		// TODO movesLeft = jo.getInt("movesLeft");
		cardsInDeck = jo.getInt("cardsInDeck");
		hints = jo.getInt("hints");
		flashs = jo.getInt("flashs");
		JSONArray trashArray = jo.getJSONArray("trash");
		trash.clear(); // TODO depends on protocol version
		for(int i=0; i<trashArray.length(); ++i){
			JSONObject cardObject = trashArray.getJSONObject(i);
			Card card = new Card(cardObject);
			trash.add(card);
		}

		JSONObject deckRow = jo.getJSONObject("deck");
		for(int i=0; i<deck.length; ++i){
			JSONObject deckCol = deckRow.getJSONObject(String.valueOf(i));
			for(int j=0; j<deck[i].length; ++j){
				deck[i][j] = new Card(deckCol.getJSONObject(String.valueOf(j)));
			}
		}

		JSONObject deckCounterObj = jo.getJSONObject("deckCounter");
		for(int i=0; i<deckCounter.length; ++i){
			deckCounter[i] = deckCounterObj.getInt(String.valueOf(i));
		}

		JSONArray playerArray = jo.getJSONArray("players");
		for(int i=0; i<players.length; ++i){
			players[i] = new BasePlayer(playerArray.getJSONObject(i));
		}
		currentPlayer = jo.getInt("currentPlayer");
	}
}
