package server;

import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;

import org.json.JSONObject;
import org.json.JSONException;

import java.util.Arrays;

public class VibeDumb implements Communicator {
	public static final byte[] MAGIC = new byte[]{ 1, 3, 3, 7 };

	private Connector conn;
	private DataInputStream inStream;
	private DataOutputStream outStream;

	public VibeDumb(Connector c) {
		conn = c;
	}

	public void sendMsg(Message m) throws IOException {
		if(outStream == null) {
			outStream = conn.getOutputStream();
		}
		byte version = 0;
		byte msgType = m.getMsgType();
		byte[] unused = new byte[]{ 0, 0 };
		outStream.write(MAGIC);
		outStream.write(version);
		outStream.write(msgType);
		outStream.write(unused);
		if(msgType == Message.MSG_MOVE) outStream.writeUTF(moveToJsonString(m.getMove()));
		else if(msgType == Message.MSG_CONNECTION_VALID) outStream.writeInt(m.getOwnId());
	}

	public Message receiveMsg() throws IOException {
		if(inStream == null) {
			inStream = conn.getInputStream();
		}
		byte[] magic = new byte[4];
		byte version = 0;
		byte msgType = 0;
		byte[] unused = new byte[2];
		inStream.read(magic);
		if(!isMagicCorrect(magic)) {
			throw new InternalHanabiError("Malformed message (wrong magic number).");
		}
		version = inStream.readByte();
		switch(version) {
		case 0: // TODO magic number?
			break;
		default:
			throw new InternalHanabiError("Version " + version + " of protocol doesn't exist.");
		}
		msgType = inStream.readByte();
		inStream.read(unused);
		Message m = new Message(msgType);
		if(msgType == Message.MSG_MOVE) {
			String msg = inStream.readUTF(); // TODO ddos?
			if(msg == null) throw new InternalHanabiError("Purported move doesn't exist.");
			try {
				m.setMove(jsonToMove(new JSONObject(msg)));
			} catch(JSONException ex) {
				throw new InternalHanabiError(ex);
			}
		} else if(msgType == Message.MSG_CONNECTION_VALID) {
			m.setOwnId(inStream.readInt());
		}
		return m;
	}
	
	private static String moveToJsonString(Move move) {
		try {
			JSONObject jo = new JSONObject();
			byte moveType = (byte) move.getMoveType();
			jo.put(Keys.VibeDumb.MOVE_TYPE, moveType);
			// TODO: dry moves
			switch(moveType) {
			case Move.HINT:
				int type = move.getHintType();
				jo.put(Keys.VibeDumb.HINT_TYPE, type);
				if(type == Move.HINT_COLOR) {
					jo.put(Keys.VibeDumb.COLOR, move.getColor());
				} else if(type == Move.HINT_VALUE) {
					jo.put(Keys.VibeDumb.VALUE, move.getValue());
				} else {
					throw new InternalHanabiError("Unknown hint type.");
				}
				break;
			case Move.PLACE_CARD:
			case Move.TRASH_CARD:
				jo.put(Keys.VibeDumb.COLOR, move.getColor());
				jo.put(Keys.VibeDumb.VALUE, move.getValue());
				break;
			default:
				throw new InternalHanabiError("Move is neither hint nor place nor trash.");
			}
			return jo.toString();
		} catch(JSONException ex) {
			throw new InternalHanabiError(ex);
		}
	}
	
	private static Move jsonToMove(JSONObject jo) {
		try {
			byte moveType = (byte)jo.getInt(Keys.VibeDumb.MOVE_TYPE);
			Move move = new Move(moveType);
			// TODO: dry moves
			switch(moveType) {
			case Move.HINT:
				int type = jo.getInt(Keys.VibeDumb.HINT_TYPE);
				move.setHintType(type);
				if(type == Move.HINT_COLOR) {
					move.setColor(jo.getInt(Keys.VibeDumb.COLOR));
				} else if(type == Move.HINT_VALUE) {
					move.setValue(jo.getInt(Keys.VibeDumb.VALUE));
				} else {
					throw new InternalHanabiError("Unknown hint type.");
				}
				break;
			case Move.PLACE_CARD:
			case Move.TRASH_CARD:
				move.setColor(jo.getInt(Keys.VibeDumb.COLOR));
				move.setValue(jo.getInt(Keys.VibeDumb.VALUE));
				break;
			default:
				throw new InternalHanabiError("Move is neither hint nor place nor trash.");
			}
			return move;
		} catch(JSONException ex) {
			throw new InternalHanabiError(ex);
		}
	}
	
	public void establish() throws IOException {
		// FIXME
		conn.establishConnection();
	}
	private static boolean isMagicCorrect(byte[] magic) {
		return Arrays.equals(MAGIC, magic);
	}

	@Override
	public Connector getConnector() {
		return conn;
	}
}
