package server;

import card.Card;

public class Message {
	public static final byte[] MAGIC = { 1, 3, 3, 7 };

	/* MUST start with 0 and MUST end with MSG_LAST_MSG, and MUST be contiguous */
	public static final byte MSG_YOURTURN = 0;
	public static final byte MSG_READY = 1;
	public static final byte MSG_UNREADY = 2;
	public static final byte MSG_INVALID = 3; //FIXME first, fix all other errors (META_MOVE_INVALID)
//	public static final byte MSG_VALID = 4; //FIXME first, fix all other errors (META_MOVE_VALID)
	public static final byte MSG_WON = 5;
	public static final byte MSG_LOST = 6;
	public static final byte MSG_LEAVE = 7;
	public static final byte MSG_LEFT = 8;
	public static final byte MSG_CONNECTION_VALID = 9;
	public static final byte MSG_CONNECTION_INVALID = 10;
//	public static final byte MSG_UPDATE = 11;
	public static final byte MSG_MOVE = 12;
	public static final byte MSG_CARD = 13;
	public static final byte MSG_INITIAL_GAME = 14;
	public static final byte MSG_LAST_MSG = MSG_INITIAL_GAME;
	
	private static void throwOnInvalidMsgType(byte b) {
		if(b < 0 || b > MSG_LAST_MSG) throw new RuntimeException("Invalid message Type " + b + ".");
	}

	private byte msgType;

	private Move move;

	/* ClientGame fields: */
	private int ownId;
	private int leftId;
	private InitialPlayer[] initialPlayers;
	private NewCard newCard;
	
	/* ServerGame fields: */
	private int id;

	public Message(byte msgType) {
		throwOnInvalidMsgType(msgType);
		setMsgType(msgType);
	}

	public Message(Move m) {
		setMsgType(MSG_MOVE);
		move = m; // TODO: clone?
	}

	public static class InitialPlayer {
		private int id;
		private String name;
		private Card[] cards;
		InitialPlayer(int i, String n, Card[] cs) {
			id = i;
			name = n;
			cards = cs;
		}
		int getId() { return id; }
		String getName() { return name; }
		Card[] getCards() { return cards; }
	}

	public static class NewCard {
		private int id;
		private Card c;
		NewCard(int id, Card c) {
			this.id = id;
			this.c = c;
		}
		int getId() { return id; }
		Card getCard() { return c; }
	}

	public Message(InitialPlayer[] players) {
		this(MSG_INITIAL_GAME);
		initialPlayers = players;
	}
	
	public Message(int id, Card c) { this(new NewCard(id, c)); }
	public Message(NewCard c) { this(MSG_CARD); newCard = c; }

	public Message(byte b, int leftId) {
		this(b);
		if(b != MSG_LEFT) {
			throw new InternalHanabiError("Attempt to create non-LEFT message with leftId.");
		}
		this.leftId = leftId;
	}

	public static Message makeInvalidMoveAnswer(Message m) {
		return new Message(MSG_INVALID);
	}

	public static Message makeItsYourTurnMessage() {
		return new Message(MSG_YOURTURN);
	}

	//public Message(JSONObject jo) throws JSONException {
		//this.jo = (JSONObject)jo.clone();
		//this.jo = jo; // TODO: clone?
//		moveType = (byte)jo.getInt("msgType");
	//}

//	public static Message newMove(Card c) {
//		try {
//			return new Message(c.toJson());
//		} catch(JSONException ex) {
//			throw new InternalHanabiError(ex);
//		}
//	}

	public Message(byte[] bytes) {
		throw new RuntimeException("Not implemented. This is a bug.");
	}

	public byte[] toBytes() {
		return toBytes(ServerGame.PROTO_VIBE_DUMB);
	}

	public byte[] toBytes(byte proto) {
		switch(proto) {
		case ServerGame.PROTO_VIBE_DUMB:
			throw new RuntimeException("Not implemented. This is a bug.");
		default:
			throw new InternalHanabiError("Message.toBytes(byte) called with invalid protocol code " + proto + ".");
		}
	}

//	public JSONObject getJson() {
//		return jo;
//	}
//	public String toJsonString() {
//		if(jo == null) return "";
//		return jo.toString();
//	}

	public byte getMsgType() { return msgType; }

	public int getOwnId() {
		if(msgType != MSG_CONNECTION_VALID) {
			throw new RuntimeException("Internal Error: ownId of non-connection valid message requested.");
		}
		return ownId;
	}
	public void setOwnId(int id) {
		if(msgType != MSG_CONNECTION_VALID) {
			throw new RuntimeException("Internal Error: Attempt to set ownId of non-connection valid message.");
		}
		ownId = id;
	}

	public int getOrigin() {
		// TODO: Should we throw if we're called by ClientGame? (if so, how?)
		return id;
	}
	public void setOrigin(int newId) {
		// TODO: cf. getOrigin()
		id = newId;
	}

	public boolean isNonMove() {
		return msgType != MSG_MOVE;
	}

	public void setMsgType(byte msgType) {
		this.msgType = msgType;
	}

	public Move getMove() {
		return move;
	}

	public void setMove(Move move) {
		this.move = move; // TODO clone?
	}

	public InitialPlayer[] getInitialPlayers() {
		if(msgType != MSG_INITIAL_GAME) {
			throw new InternalHanabiError("Attempt to get initial players from non-INITIAL_GAME message.");
		}
		return initialPlayers;
	}

	public int getLeftId() {
		if(msgType != MSG_LEFT) {
			throw new InternalHanabiError("Attempt to get leftId of non-LEFT message.");
		}
		return leftId;
	}

	public Card getNewCard() {
		if(msgType != MSG_CARD) {
			throw new InternalHanabiError("Attempt to get card of new card of non-CARD message.");
		}
		return newCard.getCard();
	}
	public int getNewCardPlayerId() {
		if(msgType != MSG_CARD) {
			throw new InternalHanabiError("Attempt to get player id of new card of non-CARD message.");
		}
		return newCard.getId();
	}

//	public int getOrigin() {
//		return origin;
////		try {
////			return jo.getInt("origin");
////		} catch(JSONException ex) {
////			throw new InternalHanabiError(ex);
////		}
//	}

//	public void setOrigin(int orig) { origin = orig; }

//	public int getHintType() {
//		if(msgType != MSG_MOVE) {
//			throw new RuntimeException("Internal Error: Hint type of non-hint move requested.");
//		}
//		return move.getHintType();
//	}
//	
//	public int getValue() {
//		if(msgType != Move.HINT) {
//			throw new RuntimeException("Internal Error: Hint value of non-hint message requested.");
//		}
//		try {
//			return jo.getInt("value");
//		} catch(JSONException ex) {
//			throw new InternalHanabiError(ex);
//		}
//	}
//	public int getHintRecipient() {
//		if(msgType != Move.HINT) {
//			throw new RuntimeException("Internal Error: Target player ID of non-hint message requested.");
//		}
//		try {
//			return jo.getInt("playerId");
//		} catch(JSONException ex) {
//			throw new InternalHanabiError(ex);
//		}
//	}
}