/*
 * @(#)SocketConnection.java
 *
 * Copyright (c) 2004 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.ftp;

import com.jscape.inet.util.ServerSocketUtil;
import com.jscape.inet.util.TimedSocket;
import com.jscape.util.IOTools;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;

/**
 * Socket FTP connection.
 */
public class SocketConnection
        implements FtpConnection {

    private Socket socket;
    private ServerSocket server;
    private boolean compression = false;

    private DeflaterOutputStream deflaterOut = null;
    private InflaterInputStream inflaterIn = null;

    private int sendBufferSize = -1;
    private int receiveBufferSize = -1;

    //--------------------------------------------------------------------------

    /**
     * Prepares incoming connection.
     *
     * @param range data port range
     * @param timeout       connection timeout.
     *
     * @throws FtpException if an I/O error occurs.
     */
    public void prepareIncoming(ServerSocketUtil.PortRange range, int timeout)
            throws FtpException {
        ensureClosed();

        try {
            this.server = ServerSocketUtil.createServerSocket(range);
            this.server.setSoTimeout(timeout);
        } catch (Exception ex) {
            throw new FtpException(ex.getMessage(), ex);
        }
    }

    public ServerSocket getServerSocket() {
        return server;
    }

    public Socket getSocket() {
        return socket;
    }


    //--------------------------------------------------------------------------

    /**
     * Opens incoming connection.
     *
     * @param timeout   connection timeout.
     * @param keepAlive keep alive.
     *
     * @throws FtpException if an I/O error occurs.
     */
    public void openIncoming(int timeout, int linger, boolean keepAlive)
            throws FtpException {
        ensurePrepared();
        ensureClosed();

        try {
            this.socket = this.server.accept();
            this.socket.setReuseAddress(true);
            this.socket.setSoTimeout(timeout);
            this.socket.setKeepAlive(keepAlive);
            if (linger > 0) {
                this.socket.setSoLinger(true, linger);
            } else {
                this.socket.setSoLinger(false, 0);
            }
        } catch (IOException ex) {
            throw new FtpException(ex.getMessage(), ex);
        }
    }

    /**
     * Opens incoming connection.
     *
     * @param timeout connection timeout.
     *
     * @throws FtpException if an I/O error occurs.
     */
    public void openIncoming(int timeout, int linger)
            throws FtpException {
        ensurePrepared();
        ensureClosed();

        try {
            this.socket = this.server.accept();
            this.socket.setReuseAddress(true);
            this.socket.setSoTimeout(timeout);
            if (linger > 0) {
                this.socket.setSoLinger(true, linger);
            } else {
                this.socket.setSoLinger(false, 0);
            }
        } catch (IOException ex) {
            throw new FtpException(ex.getMessage(), ex);
        }
    }

    //--------------------------------------------------------------------------

    /**
     * Opens outgoing connection.
     *
     * @param host      host to connect
     * @param port      port to connect
     * @param timeout   connection timeout
     * @param keepAlive keep alive
     *
     * @throws FtpException if an I/O error occurs.
     */
    public void openOutgoing(String host, int port, int timeout, int linger, boolean keepAlive, boolean tcpNoDelay)
            throws FtpException {
        ensureClosed();

        try {
            this.socket = TimedSocket.getSocket(host, port, timeout,
                                                this.sendBufferSize, this.receiveBufferSize);
            this.socket.setReuseAddress(true);
            this.socket.setSoTimeout(timeout);
            this.socket.setKeepAlive(keepAlive);
            this.socket.setTcpNoDelay(tcpNoDelay);
            
            if (linger > 0) {
                this.socket.setSoLinger(true, linger);
            } else {
                this.socket.setSoLinger(false, 0);
            }
        } catch (Exception ex) {
            throw new FtpException(ex.getMessage(), ex);
        }
    }

    /**
     * Opens outgoing connection.
     *
     * @param host    host to connect
     * @param port    port to connect
     * @param timeout connection timeout
     *
     * @throws FtpException if an I/O error occurs.
     */
    public void openOutgoing(String host, int port, int timeout, int linger)
            throws FtpException {
        ensureClosed();

        try {
            this.socket = TimedSocket.getSocket(host, port, timeout,
                                                this.sendBufferSize, this.receiveBufferSize);
            this.socket.setReuseAddress(true);
            this.socket.setSoTimeout(timeout);
            if (linger > 0) {
                this.socket.setSoLinger(true, linger);
            } else {
                this.socket.setSoLinger(false, 0);
            }
        } catch (Exception ex) {
            throw new FtpException(ex.getMessage(), ex);
        }
    }

//  --------------------------------------------------------------------------

    /**
     * Opens outgoing connection.
     *
     * @param host          host to connect
     * @param port          port to connect
     * @param timeout       connection timeout
     * @param keepAlive     keep alive
     * @param linger        how long to linger for
     * @param proxyHost     Proxy host server
     * @param proxyPort     Proxy port
     * @param proxyUserId   Proxy user name
     * @param proxyPassword Proxy user password
     * @param proxyType     Valid proxy type: HTTP, SOCKS5
     *
     * @throws FtpException if an I/O error occurs.
     */
    public void openOutgoing(String host,
                             int port,
                             int timeout,
                             boolean keepAlive,
                             boolean tcpNoDelay,
                             int linger,
                             String proxyHost,
                             int proxyPort,
                             String proxyUserId,
                             String proxyPassword,
                             String proxyType)
            throws FtpException {
        ensureClosed();

        try {
            this.socket = TimedSocket.getSocket(host, port, timeout,
                                                proxyHost, proxyPort, proxyUserId,
                                                proxyPassword, proxyType);
            this.socket.setReuseAddress(true);
            this.socket.setSoTimeout(timeout);
            this.socket.setKeepAlive(keepAlive);
            this.socket.setTcpNoDelay(tcpNoDelay);
            
            if (linger > 0) {
                this.socket.setSoLinger(true, linger);
            } else {
                this.socket.setSoLinger(false, 0);
            }
        } catch (Exception ex) {
            throw new FtpException(ex.getMessage(), ex);
        }
    }

    /**
     * Opens outgoing connection.
     *
     * @param host          host to connect
     * @param port          port to connect
     * @param timeout       connection timeout
     * @param linger        how long to linger for
     * @param proxyHost     Proxy host server
     * @param proxyPort     Proxy port
     * @param proxyUserId   Proxy user name
     * @param proxyPassword Proxy user password
     * @param proxyType     Valid proxy type: HTTP, SOCKS5
     *
     * @throws FtpException if an I/O error occurs.
     */
    public void openOutgoing(String host,
                             int port,
                             int timeout,
                             int linger,
                             String proxyHost,
                             int proxyPort,
                             String proxyUserId,
                             String proxyPassword,
                             String proxyType)
            throws FtpException {
        ensureClosed();

        try {
            this.socket = TimedSocket.getSocket(host,
                                                port,
                                                timeout,
                                                proxyHost,
                                                proxyPort,
                                                proxyUserId,
                                                proxyPassword,
                                                proxyType);
            this.socket.setReuseAddress(true);
            this.socket.setSoTimeout(timeout);
            
            if (linger > 0) {
                this.socket.setSoLinger(true, linger);
            } else {
                this.socket.setSoLinger(false, 0);
            }
            
            
        } catch (Exception ex) {
            throw new FtpException(ex.getMessage(), ex);
        }
    }

    //--------------------------------------------------------------------------

    /**
     * Closes open connection.
     */
    public void close() {
        closeZLibStreams();
        closeServer();
        closeSocket();
    }

    //--------------------------------------------------------------------------

    /**
     * Checks if connection is open.
     *
     * @return <code>true</code> if connection is open.
     */
    public boolean isOpen() {
        return (this.socket != null);
    }

    //--------------------------------------------------------------------------

    /**
     * Returns connection input stream.
     *
     * @return connection input stream.
     *
     * @throws FtpException an I/O error occurs.
     */
    public InputStream getInputStream()
            throws FtpException {
        ensureOpen();

        try {
            if (!this.compression) {
                return this.socket.getInputStream();
            } else {
                this.inflaterIn = new InflaterInputStream(this.socket.getInputStream());
                return inflaterIn;
            }
        } catch (IOException ex) {
            throw new FtpException(ex.getMessage());
        }
    }

    //--------------------------------------------------------------------------

    /**
     * Returns connection output stream.
     *
     * @return connection output stream.
     *
     * @throws FtpException an I/O error occurs.
     */
    public OutputStream getOutputStream()
            throws FtpException {
        ensureOpen();

        try {
            if (!this.compression) {
                return this.socket.getOutputStream();
            } else {
                this.deflaterOut = new DeflaterOutputStream(this.socket.getOutputStream());
                return this.deflaterOut;
            }
        } catch (IOException ex) {
            throw new FtpException(ex.getMessage());
        }
    }

    //--------------------------------------------------------------------------

    /**
     * Returns current host name.
     *
     * @return current host name.
     */
    public String getHost() {
        try {

            return InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException ex) {
            return "";
        }
    }

    /**
     * Sets whether the sockets stream will be wrapped into Deflatered
     * and Inflatered streams.
     */
    public void setCompression(boolean compression) {
        this.compression = compression;
    }

    /**
     * Sets the send buffer size option of the ftp connection.
     *
     * @param sendBufferSize Send buffer size.
     */
    public void setSendBufferSize(int sendBufferSize) {
        this.sendBufferSize = sendBufferSize;
    }

    /**
     * Sets the receive buffer size option of the ftp connection.
     *
     * @param receiveBufferSize Receive buffer size.
     */
    public void setReceiveBufferSize(int receiveBufferSize) {
        this.receiveBufferSize = receiveBufferSize;
    }

    @Override
    public String toString() {
        return "SocketConnection {"
                + "socket=" + this.socket
                + ", server=" + this.server
                + ", compression=" + this.compression
                + ", deflaterOut=" + this.deflaterOut
                + ", inflaterIn=" + this.inflaterIn
                + ", sendBufferSize=" + this.sendBufferSize
                + ", receiveBufferSize=" + this.receiveBufferSize
                + '}';
    }

//--------------------------------------------------------------------------

    /**
     * Closes server socket for incoming connection.
     */
    private void closeServer() {
        IOTools.close(this.server);
        this.server = null;
    }

    //--------------------------------------------------------------------------

    /**
     * Closes socket.
     */
    private void closeSocket() {
        IOTools.close(this.socket);
        this.socket = null;
    }

    /**
     * Closes ZLIB streams when compression is truth
     */
    private void closeZLibStreams() {
        IOTools.close(this.deflaterOut);
        this.deflaterOut = null;

        IOTools.close(this.inflaterIn);
        this.inflaterIn = null;
    }

    //--------------------------------------------------------------------------

    /**
     * Ensures that incoming connection is prepared.
     */
    private void ensurePrepared() {
        if (this.server == null) {
            throw new IllegalStateException(
                    "incoming connection is not prepared");
        }
    }

    //--------------------------------------------------------------------------

    /**
     * Ensures that connection is open.
     */
    private void ensureOpen() {
        if (!isOpen()) {
            throw new IllegalStateException("connection closed");
        }
    }

    //--------------------------------------------------------------------------

    /**
     * Ensures that connection is closed.
     */
    private void ensureClosed() {
        if (isOpen()) {
            throw new IllegalStateException("connection open");
        }
    }

}