package org.jboss.resteasy.test.providers.sse;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;

import org.jboss.logging.Logger;

/**
 * A SimpleProxyServer.
 * <p>
 * Implementation of the simple one threaded proxy which allows
 * to debug and block communication on the demand.
 *
 * @author <a href="pslavice@jboss.com">Pavel Slavicek</a>
 * @version $Revision: 1.1 $
 */
public class SimpleProxyServer
{
   // Logger
   private static final Logger log = Logger.getLogger(SimpleProxyServer.class);

   // Target host
   private String host;

   // Remote port
   private int remotePort;

   // Local port where client will connect
   private int localPort;

   // Is there request for termination?
   private volatile boolean terminateRequest = false;

   // Main thread was terminated?
   private volatile boolean terminated = true;

   private ServerSocket ss = null;

   /**
    * Create a new SimpleProxyServer.
    *
    * @param host
    * @param remotePort
    * @param localPort
    * @param debugCommunication
    */

   public SimpleProxyServer(final String host, final int remotePort, final int localPort)
   {
      this.host = host;
      this.remotePort = remotePort;
      this.localPort = localPort;
   }

   public boolean isStarted() {
      return ss != null;
   }

   /**
    * Start proxy
    */
   public void start()
   {
      log.info("Starting proxy " + this);

      if (this.terminated)
      {
         this.terminateRequest = false;
         new Thread(new Runnable()
         {
            public void run()
            {
               runServer();
            }
         }, "ProxyServer").start();
      }
   }

   /**
    * Stop proxy
    */
   public void stop()
   {
      log.info("Stop proxy " + this);

      this.terminateRequest = true;
      if (this.ss != null)
      {
         try
         {
            ss.close();
         }
         catch (IOException e)
         {
            // TODO Auto-generated catch block
            log.error("Error:", e);
         }
      }
      while (!this.terminated)
      {
         try
         {
            Thread.yield();
         }
         catch (Exception e)
         {
         }
      }
   }

   /**
    * Method runs thread with proxy which waits for connection
    */
   private void runServer()
   {

      try
      {
         ss = new ServerSocket(this.localPort);
         log.info("Running proxy server on localport " + String.valueOf(this.localPort));
         while (!this.terminateRequest)
         {
            ss.setSoTimeout(500);
            try
            {
               new ProxyThread(ss.accept(), this.host, this.remotePort, this).start();
            }
            catch (SocketTimeoutException ex)
            {
               // Just ignore this exception
            }
         }
      }
      catch (Exception e)
      {
         log.info("Proxy server is closed");
      }
      finally
      {
         if (ss != null)
         {
            try
            {
               ss.close();
            }
            catch (IOException e)
            {
               log.error(e.getMessage(), e);
            }
         }
         this.terminated = true;
      }
   }

   public void setTerminateRequest()
   {
      this.terminateRequest = true;
   }

   public boolean isTerminateRequest()
   {
      return this.terminateRequest;
   }

   /**
    * Connection thread
    *
    * @author <a href="pslavice@jboss.com">Pavel Slavicek</a>
    * @version $Revision: 1.1 $
    */
   private static class ProxyThread extends Thread
   {

      private static final int BUFFER_SIZE = 32768;

      private Socket clientSocket = null;

      private Socket serverSocket = null;

      private String host;

      private int remotePort;

      private SimpleProxyServer controllableProxy;

      /**
       * Create a new ProxyThread.
       *
       * @param socket
       * @param host
       * @param remotePort
       */
      ProxyThread(final Socket socket, final String host, final int remotePort, final SimpleProxyServer controllableProxy)
      {
         super("ProxyThread");
         this.clientSocket = socket;
         this.host = host;
         this.remotePort = remotePort;
         this.controllableProxy = controllableProxy;
      }

      /**
       * @see {@link Thread#run()}
       */
      @Override
      public void run()
      {
         final byte[] request = new byte[BUFFER_SIZE];
         byte[] reply = new byte[BUFFER_SIZE];

         if (this.clientSocket == null)
         {
            log.error("Cannot open connection for null client socket!");
            return;
         }
         try
         {
            final InputStream from_client = clientSocket.getInputStream();
            final OutputStream to_client = clientSocket.getOutputStream();

            try
            {
               serverSocket = new Socket(host, this.remotePort);
               serverSocket.setKeepAlive(true);
            }
            catch (IOException e)
            {
               String msg = "Proxy server cannot connect to " + host + ":" + this.remotePort + ":\n" + e + "\n";
               PrintWriter out = new PrintWriter(to_client);
               out.print(msg);
               out.flush();
               this.clientSocket.close();
               this.controllableProxy.setTerminateRequest();
               log.error(msg);
               return;
            }

            // Get server streams.
            final InputStream from_server = serverSocket.getInputStream();
            final OutputStream to_server = serverSocket.getOutputStream();
            Thread t = new Thread()
            {

               @Override
               public void run()
               {
                  int bytes_read;
                  try
                  {
                     while ((bytes_read = from_client.read(request)) != -1 && !controllableProxy.isTerminateRequest())
                     {
                        try
                        {

                           to_server.write(request, 0, bytes_read);
                           to_server.flush();

                        }
                        catch (IOException e)
                        {
                           log.error(e.getMessage(), e);
                        }
                     }
                  }
                  catch (IOException e)
                  {
                     log.info("connection is closed");
                  }

                  try
                  {
                     if (!clientSocket.isClosed())
                     {
                        clientSocket.close();
                     }
                     if (!serverSocket.isClosed())
                     {
                        serverSocket.close();
                     }

                  }
                  catch (IOException e)
                  {
                     log.error(e.getMessage(), e);
                  }
               }
            };
            t.start();

            int bytes_read;
            while ((bytes_read = from_server.read(reply)) != -1 && !controllableProxy.isTerminateRequest())
            {
               to_client.write(reply, 0, bytes_read);
               to_client.flush();
            }
            if (!clientSocket.isClosed())
            {
               clientSocket.close();
            }
            if (!serverSocket.isClosed())
            {
               serverSocket.close();
            }
         }
         catch (IOException e)
         {
            log.info("connection is closed");
         }
      }
   }
}
