package com.runjva.sourceforge.jsocks.protocol;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * SOCKS4 Reply/Request message.
 */

class Socks4Message extends ProxyMessage {

	private byte[] msgBytes;
	private int msgLength;

	/**
	 * Server failed reply, cmd command for failed request
	 */
	public Socks4Message(final int cmd) {
		super(cmd, null, 0);
		this.user = null;

		msgLength = 2;
		msgBytes = new byte[2];

		msgBytes[0] = (byte) 0;
		msgBytes[1] = (byte) command;
	}

	/**
	 * Server successfull reply
	 */
	public Socks4Message(final int cmd, final InetAddress ip, final int port) {
		this(0, cmd, ip, port, null);
	}

	/**
	 * Client request
	 */
	public Socks4Message(final int cmd, final InetAddress ip, final int port,
			final String user) {
		this(SOCKS_VERSION, cmd, ip, port, user);
	}

	/**
	 * Most general constructor
	 */
	public Socks4Message(final int version, final int cmd,
			final InetAddress ip, final int port, final String user) {

		super(cmd, ip, port);
		this.user = user;
		this.version = version;

		msgLength = user == null ? 8 : 9 + user.length();
		msgBytes = new byte[msgLength];

		msgBytes[0] = (byte) version;
		msgBytes[1] = (byte) command;
		msgBytes[2] = (byte) (port >> 8);
		msgBytes[3] = (byte) port;

		byte[] addr;

		if (ip != null) {
			addr = ip.getAddress();
		} else {
			addr = new byte[4];
			addr[0] = addr[1] = addr[2] = addr[3] = 0;
		}
		System.arraycopy(addr, 0, msgBytes, 4, 4);

		if (user != null) {
			final byte[] buf = user.getBytes();
			System.arraycopy(buf, 0, msgBytes, 8, buf.length);
			msgBytes[msgBytes.length - 1] = 0;
		}
	}

	/**
	 * Initialise from the stream If clientMode is true attempts to read a
	 * server response otherwise reads a client request see read for more detail
	 */
	public Socks4Message(final InputStream in, final boolean clientMode)
			throws IOException {
		msgBytes = null;
		read(in, clientMode);
	}

	public void read(final InputStream in) throws IOException {
		read(in, true);
	}

	public void read(final InputStream in, final boolean clientMode)
			throws IOException {
		final DataInputStream d_in = new DataInputStream(in);
		version = d_in.readUnsignedByte();
		command = d_in.readUnsignedByte();
		if (clientMode && (command != REPLY_OK)) {
			String errMsg;
			// FIXME: Range should be replaced with cases.
			if ((command > REPLY_OK) && (command < REPLY_BAD_IDENTD)) {
				errMsg = replyMessage[command - REPLY_OK];
			} else {
				errMsg = "Unknown Reply Code";
			}
			throw new SocksException(command, errMsg);
		}
		port = d_in.readUnsignedShort();
		final byte[] addr = new byte[4];
		d_in.readFully(addr);
		ip = bytes2IP(addr);
		host = ip.getHostName();
		if (!clientMode) {
			int b = in.read();
			// FIXME: Hope there are no idiots with user name bigger than this
			final byte[] userBytes = new byte[256];
			int i = 0;
			for (i = 0; (i < userBytes.length) && (b > 0); ++i) {
				userBytes[i] = (byte) b;
				b = in.read();
			}
			user = new String(userBytes, 0, i);
		}
	}

	public void write(final OutputStream out) throws IOException {
		if (msgBytes == null) {
			final Socks4Message msg;
			msg = new Socks4Message(version, command, ip, port, user);
			msgBytes = msg.msgBytes;
			msgLength = msg.msgLength;
		}
		out.write(msgBytes);
	}

	// Class methods
	static InetAddress bytes2IP(final byte[] addr) {
		final String s = bytes2IPV4(addr, 0);
		try {
			return InetAddress.getByName(s);
		} catch (final UnknownHostException uh_ex) {
			return null;
		}
	}

	// Constants

	static final String[] replyMessage = { "Request Granted",
			"Request Rejected or Failed",
			"Failed request, can't connect to Identd",
			"Failed request, bad user name" };

	static final int SOCKS_VERSION = 4;

	public final static int REQUEST_CONNECT = 1;
	public final static int REQUEST_BIND = 2;

	public final static int REPLY_OK = 90;
	public final static int REPLY_REJECTED = 91;
	public final static int REPLY_NO_CONNECT = 92;
	public final static int REPLY_BAD_IDENTD = 93;

}
