import java.io.*;
import java.net.Socket;		// IOException

public class GkClient implements Runnable {
	private GkGUI gui;
	private String host;
	private int port;
	private String user;
	private String password;
	
	// socket IO to communicate with the gatekeeper
	private Socket socket;
	private InputStream istream;
	private OutputStream ostream;

	public GkClient() {
	}

	public void init(GkGUI _gui, String _host, int _port, String _user, String _password) {
		gui = _gui;
		host = _host;
		port = _port;
		user = _user;
		password = _password;
	}
	
	public void stop() {
		try {
			// cleanup
			if (istream != null)
				istream.close();
			istream = null;
			if (ostream != null)
				ostream.close();
			ostream = null;
			if (socket != null)
				socket.close();
			socket = null;
		}
		catch(IOException e) { return; };
	};

	public synchronized void run() {
		if (istream != null) {
			return;
		}
		try {
			gui.appendLog("Connecting to gatekeeper at " + host + ":" + port + "\n");
			socket = new Socket(host, port);
		}
		catch(Exception e) { gui.appendLog("Error: " + e + "\n"); return; };

		try {
			ostream = socket.getOutputStream();
			istream = socket.getInputStream();
			BufferedReader in = new BufferedReader(new InputStreamReader(istream));

			login();	// if user/password was provided
			refresh();	// get initial gk status
            gui.connected();
			String inputLine;
			while ((inputLine = in.readLine()) != null) {
				// append the event to log
				gui.appendLog(inputLine + "\n");
				
				// update endpoint buttons
				if (inputLine.startsWith("RCF")) {
					String epid = extractField(inputLine, 5);
					String alias = extractField(inputLine, 3);
					gui.addEndpoint(epid, extractAliasName(alias));
				}
				else if (inputLine.startsWith("UCF")) {
					String epid = extractField(inputLine, 3);
					gui.removeEndpoint(epid);
				}
				else if (inputLine.startsWith("URQ")) {
					String epid = extractField(inputLine, 3);
					gui.removeEndpoint(epid);
				}
				else if (inputLine.startsWith("ACF")) {
					String epid = extractField(inputLine, 3);
					gui.setEndpointState(epid, Endpoint.TALKING);
				}
				else if (inputLine.startsWith("DCF")) {
					String epid = extractField(inputLine, 3);
					gui.setEndpointState(epid, Endpoint.AVAILABLE);
				}
			};

			stop();			
		}
		catch(IOException e) { };
        gui.disconnected();
		gui.appendLog("Disconnecting from gatekeeper\n");
	}
	
	/**
     * extract a field (counting from one) from a pipe-separated string ("field 1|field 2|field 3")
     */
	private String extractField(String _msg, int _no) {
		String field = "";
		int start = 0;
		int end = 0;
		if (_msg.endsWith(";"))
			_msg = _msg.substring(0, _msg.length() - 1);
		_msg += "|";
		for (int i = 0; i < (_no - 1); i++) {
			start = _msg.indexOf("|", start) + 1;
		}
		for (int i = 0; i < _no; i++) {
			end = _msg.indexOf("|", end + 1);
		}
		try {
			field = _msg.substring(start, end);
			// gui.appendLog("Field " + _no + " = " + field + "\n");
		}
		finally {
			return field;
		}
	}

	/**
     * remove the type from the alias string
     */
	private String extractAliasName(String _alias) {
		String aliasName = _alias;
		if (_alias.indexOf(":") > 0) {
			aliasName = _alias.substring(0, _alias.indexOf(":"));
		}
		return aliasName;
	}

    /**
     * do the login
     */
	public void login() {
		if ((user != null) && (user.length() > 0)) {
			sendGkCommand(user);
			sendGkCommand(password);
		}
	}

    /**
     * request current state from GnuGk.
     */
	public void refresh() {
		sendGkCommand("PrintAllRegistrations");
		sendGkCommand("PrintCurrentCalls");
	}

    public void reloadConfig() {
        sendGkCommand("Reload");
    }

    /**
     * Send DisconnectEndpoint command to GnuGk.
     * @param _epid endpoint ID
     */
	public void disconnectEndpoint(String _epid) {
		sendGkCommand("DisconnectEndpoint " + _epid);
	}

    /**
     * Send DisconnectAlias command to GnuGk.
     * @param _alias endpoint alias
     */
	public void disconnectAlias(String _alias) {
	    sendGkCommand("DisconnectAlias " + extractAliasName(_alias));
	}

    /**
     * Send UnregisterAlias command to GnuGk.
     * @param _alias endpoint alias
     */
	public void unregisterAlias(String _alias) {
	    sendGkCommand("UnregisterAlias " + extractAliasName(_alias));
	}

    /**
     * Send MakeCall command to GnuGk.
     * @param _epid         source alias
     * @param _destination  destination alias
     */
	public void makeCall(String _source, String _destination) {
	    sendGkCommand("MakeCall " + _source + " " + _destination);
	}

    public void transferCall(String _alias, String _destination) {
        gui.appendLog("TransferCall: " + extractAliasName(_alias) + " -> " + _destination + "\n");
        sendGkCommand("TransferCall " + extractAliasName(_alias) + " " + _destination);
    }

    /**
     * send RouteToAlias response to GnuGk.
     * @param _alias        endpoint alias
     * @param callingEpId   endpoint ID of calling endpoint
     * @param callRef       call reference
     */
    public void routeToAlias(String _alias, String callingEpId, String callRef) {
        sendGkCommand("RouteToAlias " + extractAliasName(_alias) + " " + callingEpId + " " + callRef);
    }

    /**
     * send RouteReject response to GnuGk.
     * @param callingEpId   endpoint ID of calling endpoint
     * @param callRef       call reference
     */
    public void routeReject(String callingEpId, String callRef) {
        sendGkCommand("RouteReject " + callingEpId + " " + callRef);
    }

    /**
     * helper function to send bytes to GnuGk.
     * @param cmd
     */
    private void sendGkCommand(String cmd) {
        if (ostream != null) {
            try {
                ostream.write((cmd + "\r\n").getBytes());
            }
            catch (IOException e) { gui.appendLog("Exception: " + e); }
        }
    }
}
