package com.jscape.inet.ipclientssh;

import com.jscape.inet.ssh.util.SshParameters;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Ssh Tunneler connection service.
 *
 * @author Alvaro
 */
public class SshTunnelerConnection
        extends Thread {

    private IpClientSsh ipClient;

    private InputStream clientReader;
    private InputStream serverReader;
    private OutputStream clientWriter;
    private OutputStream serverWriter;

    private Socket clientSocket;

    private boolean running = true;

    private boolean fromClientEnd = false;
    private boolean fromServerEnd = false;

    private List listener = new ArrayList();

    /**
     * Creates a new  Ssh tunneler connection.
     *
     * @param socket     The socket local connection.
     * @param params     Ssh parameters.
     * @param remoteIp   Remote IP.
     * @param remotePort Remote port.
     */
    public SshTunnelerConnection(Socket socket,
                                 SshParameters params, String remoteIp, int remotePort) {
        clientSocket = socket;
        ipClient = new IpClientSsh(params, remoteIp, remotePort);
    }

    /**
     * Disconnect current ssh connection and current local connection.
     *
     * @throws Exception If an error occurs.
     */
    public void disconnect()
            throws Exception {

        if (this.running) {

            this.running = false;

            this.clientReader.close();
            this.serverWriter.close();
            this.ipClient.disconnect();

            this.serverReader.close();
            this.clientWriter.close();
            this.ipClient.disconnect();
        }
    }

    /**
     * Starts the service.
     */
    public void run() {
        try {
            startService();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Starts service.
     *
     * @throws Exception If an error occurs.
     */
    public void startService()
            throws Exception {
        this.ipClient.connect();


        this.clientWriter = clientSocket.getOutputStream();
        this.clientReader = clientSocket.getInputStream();

        this.serverWriter = this.ipClient.getOutputStream();
        this.serverReader = this.ipClient.getInputStream();


        Thread fromServer = new Thread() {
            public void run() {
                try {

                    transferFromServer();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };

        Thread fromClient = new Thread() {
            public void run() {
                try {
                    transferFromClient();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };

        fromServer.start();
        fromClient.start();
    }

    /**
     * Transfers data from client to ssh server.
     *
     * @throws Exception If an error occurs.
     */
    private void transferFromClient()
            throws Exception {
        byte[] buffer = new byte[4096];
        int read = -1;
        while ((read = this.clientReader.read(buffer)) != -1 && this.running) {
            this.serverWriter.write(buffer, 0, read);
        }

        this.ipClient.disconnect();
        transferEnd(false, true);
    }

    /**
     * Transfer data from server to client.
     *
     * @throws Exception If an error occurs.
     */
    private void transferFromServer()
            throws Exception {
        byte[] buffer = new byte[4096];
        int read = -1;
        while ((read = this.serverReader.read(buffer)) != -1 && this.running) {
            this.clientWriter.write(buffer, 0, read);
        }

        this.ipClient.disconnect();
        transferEnd(true, false);

    }

    /**
     * Set the end flags.
     *
     * @param fromServer From server end flag.
     * @param fromClient From client end flag.
     *
     * @throws Exception If an error occurs.
     */
    private synchronized void transferEnd(boolean fromServer, boolean fromClient)
            throws Exception {
        if (fromServer) {
            this.fromServerEnd = true;
        }
        if (fromClient) {
            this.fromClientEnd = true;
        }

        if (this.fromServerEnd && this.fromClientEnd) {
            disconnect();
            fireServiceEnded();
        }
    }

    /**
     * Fires services ended event.
     */
    public void fireServiceEnded() {
        if (listener != null) {
            Iterator i = listener.iterator();
            while (i.hasNext()) {
                SshServiceEndListener current = (SshServiceEndListener) i.next();
                current.serviceEnded(new SshTunnelerEvent(this));
            }
        }
    }

    /**
     * Adds services ended listener.
     *
     * @param listener SshServiceEndListener instance.
     */
    public void addServiceEndedListener(SshServiceEndListener listener) {
        if (this.listener != null) {
            this.listener.add(listener);
        }
    }

    /**
     * Removes serviceEndedListener.
     *
     * @param listener Listener will be removed.
     */
    public void removeServiceEndedListener(SshServiceEndListener listener) {
        if (this.listener != null) {
            this.listener.remove(listener);
        }
    }

    /**
     * Ssh service end listener.
     *
     * @author Alvaro
     */
    interface SshServiceEndListener {
        /**
         * Service ended.
         *
         * @param event Ssh tunneler event.
         */
        void serviceEnded(SshTunnelerEvent event);
    }

    /**
     * Ssh Tunneler event.
     *
     * @author Alvaro
     */
    class SshTunnelerEvent {
        private SshTunnelerConnection source;

        /**
         * Creates a new SshTunneler event.
         *
         * @param source Source service.
         */
        SshTunnelerEvent(SshTunnelerConnection source) {
            this.source = source;
        }

        /**
         * Gets source service.
         *
         * @return Source service instance.
         */
        public SshTunnelerConnection getSource() {
            return source;
        }
    }


}
