/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.lance.namespace;

import org.lance.JniLoader;

import java.io.Closeable;
import java.util.Map;

/**
 * REST adapter server for testing namespace implementations.
 *
 * <p>This class wraps a namespace backend (e.g., DirectoryNamespace) and exposes it via a REST API.
 * It's primarily used for testing RestNamespace implementations.
 *
 * <p>Example usage:
 *
 * <pre>{@code
 * Map<String, String> backendConfig = new HashMap<>();
 * backendConfig.put("root", "/tmp/test-data");
 *
 * // Use port 0 to let OS assign an available port
 * try (RestAdapter adapter = new RestAdapter("dir", backendConfig)) {
 *     adapter.start();
 *
 *     // Get the actual port assigned by the OS
 *     int port = adapter.getPort();
 *
 *     // Now you can connect with RestNamespace
 *     Map<String, String> clientConfig = new HashMap<>();
 *     clientConfig.put("uri", "http://127.0.0.1:" + port);
 *     RestNamespace client = new RestNamespace();
 *     client.initialize(clientConfig, allocator);
 *
 *     // Use the client...
 * }
 * }</pre>
 */
public class RestAdapter implements Closeable, AutoCloseable {
  static {
    JniLoader.ensureLoaded();
  }

  private long nativeRestAdapterHandle;
  private boolean serverStarted = false;

  /**
   * Creates a new REST adapter with the given backend namespace.
   *
   * @param namespaceClientImpl The namespace client implementation type (e.g., "dir" for
   *     DirectoryNamespace)
   * @param backendConfig Configuration properties for the backend namespace client
   * @param host Host to bind the server to, or null for default (127.0.0.1)
   * @param port Port to bind the server to. Use 0 to let the OS assign an available port, or null
   *     for default (2333).
   */
  public RestAdapter(
      String namespaceClientImpl, Map<String, String> backendConfig, String host, Integer port) {
    if (namespaceClientImpl == null || namespaceClientImpl.isEmpty()) {
      throw new IllegalArgumentException("namespace client implementation cannot be null or empty");
    }
    if (backendConfig == null) {
      throw new IllegalArgumentException("backend config cannot be null");
    }
    if (port != null && (port < 0 || port > 65535)) {
      throw new IllegalArgumentException("port must be between 0 and 65535");
    }

    this.nativeRestAdapterHandle = createNative(namespaceClientImpl, backendConfig, host, port);
  }

  /**
   * Creates a new REST adapter with default host and port.
   *
   * @param namespaceClientImpl The namespace client implementation type
   * @param backendConfig Configuration properties for the backend namespace client
   */
  public RestAdapter(String namespaceClientImpl, Map<String, String> backendConfig) {
    this(namespaceClientImpl, backendConfig, null, null);
  }

  /**
   * Creates a new REST adapter with the given backend namespace.
   *
   * @param namespaceImpl The namespace implementation type (e.g., "dir" for DirectoryNamespace)
   * @param backendConfig Configuration properties for the backend namespace
   * @param host Host to bind the server to, or null for default (127.0.0.1)
   * @param port Port to bind the server to. Use 0 to let the OS assign an available port, or null
   *     for default (2333).
   */

  /**
   * Start the REST server in the background.
   *
   * <p>This method returns immediately after starting the server. The server runs in a background
   * thread until {@link #stop()} is called or the adapter is closed.
   */
  public void start() {
    if (nativeRestAdapterHandle == 0) {
      throw new IllegalStateException("RestAdapter not initialized");
    }
    if (serverStarted) {
      throw new IllegalStateException("Server already started");
    }

    start(nativeRestAdapterHandle);
    serverStarted = true;
  }

  /**
   * Get the actual port the server is listening on.
   *
   * <p>This is useful when port 0 was specified to get an OS-assigned port.
   *
   * @return The actual port, or 0 if the server hasn't been started
   */
  public int getPort() {
    if (nativeRestAdapterHandle == 0) {
      return 0;
    }
    return getPort(nativeRestAdapterHandle);
  }

  /**
   * Stop the REST server.
   *
   * <p>This method is idempotent - calling it multiple times has no effect.
   */
  public void stop() {
    if (nativeRestAdapterHandle != 0 && serverStarted) {
      stop(nativeRestAdapterHandle);
      serverStarted = false;
    }
  }

  @Override
  public void close() {
    stop();
    if (nativeRestAdapterHandle != 0) {
      releaseNative(nativeRestAdapterHandle);
      nativeRestAdapterHandle = 0;
    }
  }

  // Native methods
  private native long createNative(
      String namespaceClientImpl, Map<String, String> backendConfig, String host, Integer port);

  private native void start(long handle);

  private native int getPort(long handle);

  private native void stop(long handle);

  private native void releaseNative(long handle);
}
