/*
    GNU GENERAL PUBLIC LICENSE
    Copyright (C) 2006 The Lobo Project

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public
    License as published by the Free Software Foundation; either
    verion 2 of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.

    You should have received a copy of the GNU General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    Contact info: [email protected]
 */
/*
 * Created on Jun 18, 2005
 */
package org.lobobrowser.reuse;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;

import org.lobobrowser.LoboBrowser;
import org.eclipse.jdt.annotation.NonNull;
import org.cobraparser.ua.NavigatorFrame;

/**
 * The local-bound server that allows the browser JVM to be reused.
 */
public class ReuseServer implements Runnable {
  /**
   *
   */
  public ReuseServer() {
    final Thread t = new Thread(this, "ReuseServer");
    t.setDaemon(true);
    t.start();
  }

  private static final int MIN_PORT = 55000;
  private static final int TOP_PORT = 65000;
  private static final Random RAND = new Random(System.currentTimeMillis());

  private static int getRandomPort() {
    return (Math.abs(RAND.nextInt()) % (TOP_PORT - MIN_PORT)) + MIN_PORT;
  }

  private ServerSocket serverSocket;

  public int start(final InetAddress bindAddr) {
    // Should be called with bindAddr=127.0.0.1 only.
    synchronized (this) {
      if (this.serverSocket != null) {
        throw new IllegalStateException("Already started");
      }
      for (int tries = 0; tries < 100; tries++) {
        final int rport = getRandomPort();
        try {
          final ServerSocket ss = new ServerSocket(rport, 100, bindAddr);
          this.serverSocket = ss;
          this.notify();
          return rport;
        } catch (final IOException ioe) {
          ioe.printStackTrace(System.err);
        }
      }
    }
    throw new IllegalStateException("Unable to bind reuse server after many tries.");
  }

  public void stop() {
    synchronized (this) {
      if (this.serverSocket != null) {
        try {
          this.serverSocket.close();
        } catch (final IOException ioe) {
          // ignore
        }
        this.serverSocket = null;
      }
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see java.lang.Runnable#run()
   */
  public void run() {
    for (;;) {
      try {
        ServerSocket ss;
        synchronized (this) {
          while (this.serverSocket == null) {
            this.wait();
          }
          ss = this.serverSocket;
        }
        try (
          final Socket s = ss.accept()) {
          s.setSoTimeout(10000);
          s.setTcpNoDelay(true);
          try (
            final InputStream in = s.getInputStream()) {
            final Reader reader = new InputStreamReader(in);
            final BufferedReader br = new BufferedReader(reader);
            String line;
            while ((line = br.readLine()) != null) {
              final int blankIdx = line.indexOf(' ');
              final String command = blankIdx == -1 ? line : line.substring(0, blankIdx).trim();
              if ("LAUNCH".equals(command)) {
                if (blankIdx == -1) {
                  LoboBrowser.getInstance().launch();
                } else {
                  final String path = line.substring(blankIdx + 1).trim();
                  LoboBrowser.getInstance().launch(path);
                }
              } else if ("LAUNCH_BLANK".equals(command)) {
                LoboBrowser.getInstance().launch();
              } else if ("GRINDER".equals(command)) {
                final DataOutputStream dos = new DataOutputStream(s.getOutputStream());
                if (blankIdx != -1) {
                  @SuppressWarnings("null")
                  final @NonNull String key = line.substring(blankIdx + 1).trim();
                  if (LoboBrowser.getInstance().verifyAuth(ss.getLocalPort(), key)) {
                    final NavigatorFrame frame = LoboBrowser.getInstance().launch("cobra:blank");
                    final GrinderServer gs = new GrinderServer(frame);
                    final int gsPort = gs.getPort();
                    dos.writeInt(gsPort);
                    dos.flush();
                  } else {
                    dos.writeInt(-1);
                    dos.flush();
                  }
                } else {
                  dos.writeInt(-1);
                  dos.flush();
                }
                // Wait for ACK
                br.readLine();
              }
            }
          }
        }
      } catch (final Exception t) {
        t.printStackTrace(System.err);
      }
    }
  }
}