/*
 * @(#)ConnectionParameters.java
 *
 * Copyright (c) 2002-2003 JSCAPE
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JSCAPE. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JSCAPE.
 */
package com.jscape.inet.util;

import com.jscape.util.Assert;

import java.io.IOException;
import java.io.Serializable;
import java.net.Socket;
import java.net.SocketException;

import static com.jscape.util.Parameters.assertNotNull;

/**
 * SSH connection parameters container.
 *
 * @author Alex Usun
 */
public class ConnectionParameters
        implements Serializable {

    /**
     * Infinite timeout value.
     */
    public static final long INFINITE_TIMEOUT = 0L;

    private static final int DEFAULT_BUFFER_SIZE = -1;
    private static final int DEFAULT_PROXY_PORT = 1080;
    private static final String DEFAULT_PROXY_TYPE = "SOCKS5";

    private static final int DEFAULT_WINDOW_SIZE = 2 * 1024 * 1024;

    private String host;
    private int port;
    private long connectionTimeout;
    private long soTimeout;
    private int sendBuffer;
    private int receiveBuffer;
    private Integer trafficClass;
    private Boolean tcpNoDelay;
    private String proxyHost;
    private int proxyPort;
    private String proxyUserId;
    private String proxyPassword;
    private String proxyType;
    private long minIncomingWindowSize;
    private long maxIncomingWindowSize;

    public ConnectionParameters(String host,
                                int port,
                                long connectionTimeout,
                                long soTimeout,
                                int sendBuffer,
                                int receiveBuffer,
                                Integer trafficClass,
                                Boolean tcpNoDelay,
                                String proxyHost,
                                int proxyPort,
                                String proxyUserId,
                                String proxyPassword,
                                String proxyType,
                                long minIncomingWindowSize,
                                long maxIncomingWindowSize) {
        assertNotNull(host);
        this.host = host;

        this.port = port;
        this.connectionTimeout = connectionTimeout;
        this.soTimeout = soTimeout;
        this.sendBuffer = sendBuffer;
        this.receiveBuffer = receiveBuffer;
        this.trafficClass = trafficClass;
        this.tcpNoDelay = tcpNoDelay;
        this.proxyHost = proxyHost;
        this.proxyPort = proxyPort;
        this.proxyUserId = proxyUserId;
        this.proxyPassword = proxyPassword;
        this.proxyType = proxyType;
        this.minIncomingWindowSize = minIncomingWindowSize;
        this.maxIncomingWindowSize = maxIncomingWindowSize;
    }

    public ConnectionParameters(String host,
                                int port,
                                long connectionTimeout,
                                long soTimeout,
                                int sendBuffer,
                                int receiveBuffer,
                                Integer trafficClass,
                                Boolean tcpNoDelay,
                                String proxyHost,
                                int proxyPort,
                                String proxyUserId,
                                String proxyPassword,
                                String proxyType) {
        this(host,
             port,
             connectionTimeout,
             soTimeout,
             sendBuffer,
             receiveBuffer,
             trafficClass,
             tcpNoDelay,
             proxyHost,
             proxyPort,
             proxyUserId,
             proxyPassword,
             proxyType,
             DEFAULT_WINDOW_SIZE / 5,
             DEFAULT_WINDOW_SIZE);
    }

    public ConnectionParameters(String host,
                                int port,
                                long timeout,
                                String proxyHost,
                                int proxyPort,
                                String proxyUserId,
                                String proxyPassword,
                                String proxyType) {
        this(host,
             port,
             timeout,
             timeout,
             DEFAULT_BUFFER_SIZE,
             DEFAULT_BUFFER_SIZE,
             proxyHost,
             proxyPort,
             proxyUserId,
             proxyPassword,
             proxyType);
    }

    /**
     * Copy constructor.
     *
     * @param that instance to copy
     */
    public ConnectionParameters(ConnectionParameters that) {
        this(that.host,
             that.port,
             that.connectionTimeout,
             that.soTimeout,
             that.sendBuffer,
             that.receiveBuffer,
             that.trafficClass,
             that.tcpNoDelay,
             that.proxyHost,
             that.proxyPort,
             that.proxyUserId,
             that.proxyPassword,
             that.proxyType,
             that.minIncomingWindowSize,
             that.maxIncomingWindowSize);
    }

    public ConnectionParameters(String host,
                                int port,
                                long connectionTimeout,
                                long soTimeout,
                                int sendBuffer,
                                int receiveBuffer,
                                String proxyHost,
                                int proxyPort,
                                String proxyUserId,
                                String proxyPassword,
                                String proxyType) {
        this(host,
             port,
             connectionTimeout,
             soTimeout,
             sendBuffer,
             receiveBuffer,
             null,
             true,
             proxyHost,
             proxyPort,
             proxyUserId,
             proxyPassword,
             proxyType);
    }

    /**
     * Creates a new parameters instance.
     *
     * @param host          the server host
     * @param port          the server port
     * @param timeout       the connection timeout
     * @param sendBuffer    the send buffer size.
     * @param receiveBuffer the receive buffer size.
     */
    public ConnectionParameters(String host, int port, long timeout, int sendBuffer, int receiveBuffer) {
        this(host,
             port,
             timeout,
             timeout,
             sendBuffer,
             receiveBuffer,
             null,
             DEFAULT_PROXY_PORT,
             null,
             null,
             DEFAULT_PROXY_TYPE);
    }


    /**
     * Creates a new parameters instance.
     *
     * @param host    the server host
     * @param port    the server port
     * @param timeout the connection timeout
     */
    public ConnectionParameters(String host, int port, long timeout) {
        this(host,
             port,
             timeout,
             DEFAULT_BUFFER_SIZE,
             DEFAULT_BUFFER_SIZE);
    }


    /**
     * Creates a new parameters instance.
     *
     * @param host host
     * @param port port
     */
    public ConnectionParameters(String host, int port) {
        this(host, port, INFINITE_TIMEOUT);
    }

    /**
     * Creates a new parameters instance.
     *
     * @param host          host
     * @param port          port
     * @param sendBuffer    send buffer size.
     * @param receiveBuffer receive buffer size.
     */
    public ConnectionParameters(String host, int port, int sendBuffer, int receiveBuffer) {
        this(host, port, INFINITE_TIMEOUT, sendBuffer, receiveBuffer);
    }

    /**
     * Set minimum incoming window size.
     *
     * @param size
     */
    public void setMinIncomingWindowSize(long size) {
        this.minIncomingWindowSize = size;
    }

    /**
     * Get minimum incoming window size.
     *
     * @return size
     */
    public long getMinIncomingWindowSize() {
        return this.minIncomingWindowSize;
    }

    /**
     * Set maximum incoming window size.
     *
     * @param size
     */
    public void setMaxIncomingWindowSize(long size) {
        this.maxIncomingWindowSize = size;
    }

    /**
     * Get maximum incoming window size.
     *
     * @return size
     */
    public long getMaxIncomingWindowSize() {
        return this.maxIncomingWindowSize;
    }

    /**
     * Obtains target SSH host name.
     *
     * @return target SSH host name
     */
    public String getHost() {
        return this.host;
    }

    /**
     * Sets host.
     *
     * @param value host
     */
    public void setHost(String value) {
        Assert.isValidString(value);
        this.host = value;
    }

    /**
     * Obtains target SSH port.
     *
     * @return target SSH port
     */
    public int getPort() {
        return this.port;
    }

    /**
     * Sets port.
     *
     * @param value port
     */
    public void setPort(int value) {
        Assert.inRangeInclusive(value, 0, 65535, "Bad port value.");
        this.port = value;
    }

    /**
     * Obtains connection timeout value.
     *
     * @return connection timeout
     */
    public long getConnectionTimeout() {
        return this.connectionTimeout;
    }

    /**
     * Sets connection timeout.
     *
     * @param value connection timeout
     */
    public void setConnectionTimeout(long value) {
        Assert.greaterOrEqual(value, 0, "Bad connection timeout value.");
        this.connectionTimeout = value;
    }

    /**
     * Get socket traffic class.
     *
     * @return traffic class
     */
    public Integer getTrafficClass() {
        return this.trafficClass;
    }

    /**
     * Set traffic class.
     *
     * @param value
     */
    public void setTrafficClass(Integer value) {
        this.trafficClass = value;
    }

    /**
     * Returns reading timeout in milliseconds.
     *
     * @return reading timeout in milliseconds
     */
    public long getSoTimeout() {
        return this.soTimeout;
    }


    /**
     * Sets reading timeout.
     *
     * @param value reading timeout in milliseconds
     */
    public void setSoTimeout(long value) {
        Assert.greaterOrEqual(value, 0, "Bad SO timeout value.");
        this.soTimeout = value;
    }

    /**
     * Get TCP no delay.
     *
     * @return TCP no delay
     */
    public Boolean getTcpNoDelay() {
        return this.tcpNoDelay;
    }

    /**
     * Set TCP no delay.
     *
     * @param value
     */
    public void setTcpNoDelay(Boolean value) {
        this.tcpNoDelay = value;
    }

    /**
     * Gets the send buffer size.
     *
     * @return send buffer size
     */
    public int getSendBuffer() {
        return this.sendBuffer;
    }


    /**
     * Sets the send buffer size.
     *
     * @param value the buffer size.
     */
    public void setSendBuffer(int value) {
        this.sendBuffer = value;
    }

    /**
     * Gets the receive buffer size.
     *
     * @return receive buffer size
     */
    public int getReceiveBuffer() {
        return this.receiveBuffer;
    }

    /**
     * Sets the receive buffer size.
     *
     * @param value the buffer size.
     */
    public void setReceiveBuffer(int value) {
        this.receiveBuffer = value;
    }

    /**
     * Gets proxy hostname.
     *
     * @return The proxy hostname
     */
    public String getProxyHost() {
        return this.proxyHost;
    }

    /**
     * Sets the proxy hostname.
     *
     * @param value The proxy hostname
     */
    public void setProxyHost(String value) {
        this.proxyHost = value;
    }

    /**
     * Gets the proxy port.
     *
     * @return The proxy port.
     */
    public int getProxyPort() {
        return this.proxyPort;
    }

    /**
     * Sets the proxy port.
     *
     * @param value The proxy port.
     */
    public void setProxyPort(int value) {
        this.proxyPort = value;
    }

    /**
     * Gets the user authentication proxy.
     *
     * @return User authentication proxy.
     */
    public String getProxyUserId() {
        return this.proxyUserId;
    }


    /**
     * Sets user authentication proxy.
     *
     * @param value User authentication proxy.
     */
    public void setProxyUserId(String value) {
        this.proxyUserId = value;
    }

    /**
     * Gets the proxy authentication password.
     *
     * @return Proxy authentication password.
     */
    public String getProxyPassword() {
        return this.proxyPassword;
    }

    /**
     * Sets the proxy authentication password.
     *
     * @param value Initializes the proxy authentication password.
     */
    public void setProxyPassword(String value) {
        this.proxyPassword = value;
    }

    /**
     * Gets the proxy type: SOCKS5, HTTP.
     *
     * @return The proxy type
     */
    public String getProxyType() {
        return this.proxyType;
    }

    /**
     * Sets the proxy type
     *
     * @param value The proxy type.
     */
    public void setProxyType(String value) {
        this.proxyType = value;
    }

    /**
     * Creates a new connected socket.
     *
     * @return a new connected socket
     *
     * @throws IOException if an I/O error occured
     */
    public Socket createSocket()
            throws IOException {
        Socket socket = useProxy() ? createProxySocket() : createPlainSocket();
        initSocket(socket);
        return socket;
    }

    @Override
    public String toString() {
        return "ConnectionParameters {"
                + "host='" + this.host + '\''
                + ", port=" + this.port
                + ", connectionTimeout=" + this.connectionTimeout
                + ", soTimeout=" + this.soTimeout
                + ", sendBuffer=" + this.sendBuffer
                + ", receiveBuffer=" + this.receiveBuffer
                + ", trafficClass=" + this.trafficClass
                + ", tcpNoDelay=" + this.tcpNoDelay
                + ", proxyHost='" + this.proxyHost + '\''
                + ", proxyPort=" + this.proxyPort
                + ", proxyUserId='" + this.proxyUserId + '\''
                + ", proxyPassword='" + this.proxyPassword + '\''
                + ", proxyType='" + this.proxyType + '\''
                + '}';
    }

    private boolean useProxy() {
        return this.proxyHost != null;
    }

    private Socket createProxySocket()
            throws IOException {
        return TimedSocket.getSocket(this.host,
                                     this.port,
                                     this.connectionTimeout,
                                     this.proxyHost,
                                     this.proxyPort,
                                     this.proxyUserId,
                                     this.proxyPassword,
                                     this.proxyType);
    }

    private Socket createPlainSocket()
            throws IOException {
        return TimedSocket.getSocket(this.host,
                                     this.port,
                                     this.connectionTimeout,
                                     this.sendBuffer,
                                     this.receiveBuffer);
    }

    private void initSocket(Socket socket)
            throws SocketException {
        socket.setSoTimeout((int) this.soTimeout);

        if (this.trafficClass != null) {
            socket.setTrafficClass(this.trafficClass);
        }

        if (this.tcpNoDelay != null) {
            socket.setTcpNoDelay(this.tcpNoDelay);
        }
    }
}
