/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.harmony.luni.net;

import java.nio.charset.Charsets;

class Socks4Message {
    static final int COMMAND_CONNECT = 1;

    static final int COMMAND_BIND = 2;

    static final int RETURN_SUCCESS = 90;

    static final int RETURN_FAILURE = 91;

    static final int RETURN_CANNOT_CONNECT_TO_IDENTD = 92;

    static final int RETURN_DIFFERENT_USER_IDS = 93;

    static final int REPLY_LENGTH = 8;

    static final int INDEX_VERSION = 0;

    private static final int SOCKS_VERSION = 4;

    private static final int INDEX_COMMAND = 1;

    private static final int INDEX_PORT = 2;

    private static final int INDEX_IP = 4;

    private static final int INDEX_USER_ID = 8;

    private static final int BUFFER_LENGTH = 256;

    private static final int MAX_USER_ID_LENGTH = BUFFER_LENGTH - INDEX_USER_ID;

    protected byte[] buffer;

    public Socks4Message() {
        super();
        buffer = new byte[BUFFER_LENGTH];
        setVersionNumber(SOCKS_VERSION);
    }

    /**
     * Get the request's command or result.
     */
    public int getCommandOrResult() {
        return buffer[INDEX_COMMAND];
    }

    /**
     * Set the request's command or result.
     */
    public void setCommandOrResult(int command) {
        buffer[INDEX_COMMAND] = (byte) command;
    }

    /**
     * Returns the request's port number.
     */
    public int getPort() {
        return getInt16(INDEX_PORT);
    }

    /**
     * Set the request's port number.
     */
    public void setPort(int port) {
        setInt16(INDEX_PORT, port);
    }

    /**
     * Returns the IP address of the request as an integer.
     */
    public int getIP() {
        return getInt32(INDEX_IP);
    }

    /**
     * Set the IP address. This expects an array of four bytes in host order.
     */
    public void setIP(byte[] ip) {
        buffer[INDEX_IP] = ip[0];
        buffer[INDEX_IP + 1] = ip[1];
        buffer[INDEX_IP + 2] = ip[2];
        buffer[INDEX_IP + 3] = ip[3];
    }

    /**
     * Returns the user id for authentication.
     */
    public String getUserId() {
        return getString(INDEX_USER_ID, MAX_USER_ID_LENGTH);
    }

    /**
     * Set the user id for authentication.
     */
    public void setUserId(String id) {
        setString(INDEX_USER_ID, MAX_USER_ID_LENGTH, id);
    }

    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder(50);
        buf.append("Version: ");
        buf.append(Integer.toHexString(getVersionNumber()));
        buf.append(" Command: ");
        buf.append(Integer.toHexString(getCommandOrResult()));
        buf.append(" Port: ");
        buf.append(getPort());
        buf.append(" IP: ");
        buf.append(Integer.toHexString(getIP()));
        buf.append(" User ID: ");
        buf.append(getUserId());
        return buf.toString();
    }

    /**
     * Returns the total number of bytes used for the request. This method
     * searches for the end of the user id, then searches for the end of the
     * password and returns the final index as the requests length.
     */
    public int getLength() {
        int index = 0;

        // Look for the end of the user id.
        for (index = INDEX_USER_ID; buffer[index] != 0; index++) {
            /*
             * Finds the end of the user id by searching for the null
             * termination of the user id string.
             */
        }

        // Increment the index to include the NULL character in the length;
        index++;
        return index;
    }

    /**
     * Returns an error string corresponding to the given error value.
     */
    public String getErrorString(int error) {
        switch (error) {
        case RETURN_FAILURE:
            return "Failure to connect to SOCKS server";
        case RETURN_CANNOT_CONNECT_TO_IDENTD:
            return "Unable to connect to identd to verify user";
        case RETURN_DIFFERENT_USER_IDS:
            return "Failure - user ids do not match";
        default:
            return "Success";
        }
    }

    /**
     * Returns the message's byte buffer.
     */
    public byte[] getBytes() {
        return buffer;
    }

    /**
     * Get a 16 bit integer from the buffer at the offset given.
     */
    private int getInt16(int offset) {
        return (((buffer[offset] & 0xFF) << 8) + (buffer[offset + 1] & 0xFF));
    }

    /**
     * Get a 32 bit integer from the buffer at the offset given.
     */
    private int getInt32(int offset) {
        return ((buffer[offset + 3] & 0xFF)
                + ((buffer[offset + 2] & 0xFF) << 8)
                + ((buffer[offset + 1] & 0xFF) << 16) + ((buffer[offset + 0] & 0xFF) << 24));
    }

    /**
     * Get a String from the buffer at the offset given. The method reads until
     * it encounters a null value or reaches the maxLength given.
     */
    private String getString(int offset, int maxLength) {
        int index = offset;
        int lastIndex = index + maxLength;
        while (index < lastIndex && (buffer[index] != 0)) {
            index++;
        }
        return new String(buffer, offset, index - offset, Charsets.ISO_8859_1);
    }

    /**
     * Returns the SOCKS version number. Should always be 4.
     */
    private int getVersionNumber() {
        return buffer[INDEX_VERSION];
    }

    /**
     * Put a 16 bit integer into the buffer at the offset given.
     */
    private void setInt16(int offset, int value) {
        buffer[offset] = (byte) (value >>> 8 & 0xFF);
        buffer[offset + 1] = (byte) (value & 0xFF);
    }

    /**
     * Put a string into the buffer at the offset given.
     */
    private void setString(int offset, int maxLength, String theString) {
        byte[] stringBytes = theString.getBytes(Charsets.ISO_8859_1);
        int length = Math.min(stringBytes.length, maxLength);
        System.arraycopy(stringBytes, 0, buffer, offset, length);
        buffer[offset + length] = 0;
    }

    /**
     * Set the SOCKS version number. This should always be 4.
     */
    private void setVersionNumber(int number) {
        buffer[INDEX_VERSION] = (byte) number;
    }
}
