package org.basex.examples.server;

import java.io.*;
import java.util.*;

import org.basex.*;
import org.basex.api.client.*;

/**
 * This class connects multiple clients to one server instance.
 * It sets up three clients with read/write access to the database.
 * Database information will be shown before and after the
 * clients have been run.
 *
 * @author BaseX Team 2005-22, BSD License
 */
public final class ServerConcurrency {
  /**
   * Main method of the example class.
   * @param args (ignored) command-line arguments
   * @throws Exception exception
   */
  public static void main(final String... args) throws Exception {
    new ServerConcurrency().run();
  }

  /**
   * Runs the example code.
   * @throws Exception on error.
   */
  void run() throws Exception {
    System.out.println("=== ServerConcurrency ===");

    // Start server
    System.out.println("\n* Start server.");

    BaseXServer server = new BaseXServer();

    // Create a client session with host name, port, user name and password
    System.out.println("\n* Create a client session.");

    try(ClientSession session =
      new ClientSession("localhost", 1984, "admin", "admin", System.out)) {

      // Create a database
      System.out.println("\n* Create a database.");

      send("CREATE DB input src/main/resources/xml/input.xml", session);

      // Setup some clients that simultaneously read and write from the database
      System.out.println("\n* Run one reader and two writers threads.");

      ClientExample reader1 = new ClientExample("//li");
      ClientExample writer1 = new ClientExample("insert node " +
          "<li>new node</li> as last into /html/body//ul");
      ClientExample writer2 = new ClientExample("insert node " +
          "<new>One more</new> as last into /html/body");

      // Let the clients run their query several times:
      Thread.sleep(2000);

      // Stop the clients
      System.out.println("\n* Stop client threads.");

      reader1.stop();
      writer1.stop();
      writer2.stop();

      Thread.sleep(1000);

      // Show modified database contents
      System.out.println("\n* Show modified database contents:");

      send("XQUERY //li", session);

      // Drop the database
      System.out.println("\n* Drop the database.");

      send("DROP DB input", session);

      // Close the client session
      System.out.println("\n* Close the client session.");
    }

    // Stop the server
    System.out.println("\n* Stop server.");

    server.stop();
  }

  /**
   * Processes the specified command on the server and returns the output
   * or command info.
   * @param cmd command to be executed
   * @param cs client session reference
   * @throws IOException I/O exception
   */
  static void send(final String cmd, final ClientSession cs) throws IOException {
    // Execute the command
    cs.execute(cmd);
  }

  /**
   * This class simulates a database client.
   * Within a thread, a query is run several times until the thread is stopped.
   */
  private final class ClientExample {
    /** Set to false to stop the Client from running.*/
    volatile boolean running = true;
    /** Random sleep time generator.*/
    final Random r = new Random();

    /**
     * Constructor.
     * @param query query to be run
     * @throws IOException I/O exception
     */
    ClientExample(final String query) throws IOException {
      // Create a client session and open database
      final ClientSession session = new ClientSession("localhost", 1984, "admin", "admin");
      send("OPEN input", session);

      // Create and run the query thread

      new Thread() {
        @Override
        public void run() {
          try {
            // Loop until the running flag has been invalidated
            while(running) {
              // Run the specified query
              send("XQUERY " + query, session);

              // Wait for a random time
              Thread.sleep(r.nextInt(500));
            }
          } catch(Exception ex) {
            ex.printStackTrace();
          } finally {
            // Close the client session
            try {
              session.close();
            } catch(Exception ex) {
              ex.printStackTrace();
            }
          }
        }
      }.start();
    }

    /**
     * Quits the client thread.
     */
    void stop() {
      running = false;
    }
  }
}
