/**
 *  Copyright © 2025, Luis Andrés Lange <https://javacomm.net>
 *
 *  Previously released under Apache License 2.0; now licensed under MPL 2.0.
 *
 *  This Source Code Form is subject to the terms of the Mozilla Public
 *  License, v. 2.0. If a copy of the MPL was not distributed with this
 *  file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 *  ----------------------------------------------------------------------------
 *
 *  Exhibit B - "Incompatible With Secondary Licenses" Notice
 *
 *  This Source Code Form is "Incompatible With Secondary Licenses",
 *  as defined by the Mozilla Public License, v. 2.0.
 *
 *  In short:
 *  - This file may be used, modified, and distributed under MPL 2.0 only.
 *  - It may NOT be relicensed under GPL, LGPL, AGPL, or any other Secondary License.
 *
 *  Rationale:
 *  - Ensures that the code remains MPL-2.0.
 *  - Avoids legal conflicts with GPL-licensed libraries (e.g., VideoLAN).
 *  - Maximizes usability for commercial and security-critical applications.
 *
 */
package net.javacomm.restserver;

import static net.javacomm.protocol.HEADER.REQUEST;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.glassfish.jersey.media.multipart.FormDataParam;
import net.javacomm.database.DatabaseException;
import net.javacomm.database.WebdatabaseImpl;
import net.javacomm.protocol.Command;
import net.javacomm.protocol.NEWIMAGE;
import net.javacomm.protocol.NEWMOUSEPOINTER;
import net.javacomm.protocol.Protocol;
import net.javacomm.server.Webserver;



@Path("/screenshot")
public class Screenshot {

  // Cache: userid → letzte Bilddaten als AtomicReference
  private static final Map<String, AtomicReference<byte[]>> imageCache = new ConcurrentHashMap<>();

  private final static Logger log = LogManager.getLogger(Screenshot.class);

  private final WebdatabaseImpl database;

  public Screenshot() {
    database = (WebdatabaseImpl) WebdatabaseImpl.getInstance();
  }



  public static void removeScreenshot(String pseudoid) {
    imageCache.remove(pseudoid);
  }



  @GET
  @Path("/running/{receiver}")
  @Produces(MediaType.TEXT_PLAIN)
  public int isTransmissionRunning(@PathParam("receiver") String receiverNickname) {
    try {
      String receiverUserid = database.fetchUserByNickname(receiverNickname);
      return database.isVideoRunning(receiverUserid);
    }
    catch (DatabaseException e) {
      log.info(e.getMessage());
      throw new WebApplicationException();
    }
  }



  /**
   * Lädt den aktuellsten Screenshot. Der Leseprozess arbeitet auf einer Kopie,
   * neue Bilder überschreiben nur die Referenz.
   */
  @GET
  @Path("/load")
  @Produces(MediaType.APPLICATION_OCTET_STREAM)
  public Response loadScreenshotHeader(@HeaderParam("pseudoid") String pseudoid) {
    AtomicReference<byte[]> ref = imageCache.get(pseudoid);
    if (ref == null || ref.get() == null) {
      log.warn("Kein Screenshot für {} gefunden", pseudoid);
      return Response.status(Response.Status.NOT_FOUND).build();
    }

    // Kopie erstellen, damit spätere Updates den laufenden Leseprozess nicht stören
    byte[] imageCopy = ref.get();
    return Response.ok(imageCopy).build();
  }



  /**
   * Speichert ein neues Bild. Überschreibt die alte Referenz, ohne laufende
   * Leseprozesse zu beeinflussen.
   */
  @POST
  @Path("/save")
  public Response saveScreenshot(@FormDataParam("pseudoid") String pseudoId,
      @FormDataParam("image") InputStream imageStream, @FormDataParam("conferencekey") String conferencekey) {

    if (pseudoId == null || pseudoId.isEmpty()) {
      return Response.status(Status.BAD_REQUEST).entity("pseudoid fehlt").build();
    }
    CopyOnWriteArrayList<String> originalList = Webserver.conferenceAudiomap.get(conferencekey);
    if (originalList == null) {
      log.warn("conferencekey ohne sessionlist");
      return Response.status(Status.GONE).entity("conferencekey ohne sessionlist").build();
    }
//    originalList.forEach(id -> log.info("In original enthaltene Session: {}", id));
    CopyOnWriteArrayList<String> receivers = new CopyOnWriteArrayList<>(originalList);

    int vorher = receivers.size();
    // ich sende eine Bild zu viel, deswegen removeIf
    receivers.removeIf(session -> session.equals(Webserver.onlineMap.get(pseudoId)));
    int nachher = receivers.size();
    if (vorher == 2 && nachher == 0) {
      // Für Selbsttest
      receivers = new CopyOnWriteArrayList<>(originalList);
      receivers.remove(0);
    }

    try(InputStream in = imageStream) {
      // InputStream vollständig in ein byte[] laden
      byte[] pngBytes = in.readAllBytes();

      // AtomicReference erzeugen oder aktualisieren
      imageCache.computeIfAbsent(pseudoId, k -> new AtomicReference<>()).set(pngBytes);

      // Benachrichtigung an Empfänger senden
      NEWIMAGE newimage = new NEWIMAGE();
      newimage.setCommand(Command.NEWIMAGE);
      newimage.setHeader(REQUEST);
      newimage.setDataset(Protocol.DATASET);
      newimage.setPseudoid(pseudoId);
      Webserver.sendMessage(newimage, receivers);

      return Response.ok().build();
    }
    catch (IOException e) {
      log.error("Fehler beim Verarbeiten des PNG von {}", pseudoId, e);
      throw new WebApplicationException();
    }
  }



  /**
   * Die Mausposition wurde an den Server übertragen. Diese Position wird den
   * anderen Konferenzteilnehmern mitgeteilt.
   * 
   * 
   * @param pseudoid
   *                      von diesem Anwender stammt der Mousepointer
   * @param x
   *                      Koordinate
   * @param y
   *                      Koordinate
   * @param receiversJson
   *                      alle Empfänger
   */
  @PUT
  @Path("/mousepointer")
  public Response mousepointer(@FormDataParam("pseudoid") String pseudoid, @FormDataParam("x") int x,
      @FormDataParam("y") int y, @FormDataParam("conferencekey") String conferencekey) {

//    // ich sende eine Bild zu viel, deswegen removeIf
//    // conferencekey auflösen

    // 1. Original-Referenz holen
    CopyOnWriteArrayList<String> originalList = Webserver.conferenceAudiomap.get(conferencekey);

    if (originalList == null) {
      log.warn("mousepointer, conferencekey ohne sessionlist");
      return Response.status(Status.GONE).entity("conferencekey ohne sessionlist").build();
    }

    // 2. Eine EIGENSTÄNDIGE Kopie erstellen
    CopyOnWriteArrayList<String> receivers = new CopyOnWriteArrayList<>(originalList);

    int vorher = receivers.size();
    // ich sende eine Bild zu viel, deswegen removeIf
    // 3. Jetzt filterst du NUR auf der lokalen Kopie. Die Map bleibt unberührt!
    receivers.removeIf(session -> session.equals(Webserver.onlineMap.get(pseudoid)));
    int nachher = receivers.size();
    if (vorher == 2 && nachher == 0) {
      // Für Selbsttest
      receivers = new CopyOnWriteArrayList<>(originalList);
      receivers.remove(0);
    }

    NEWMOUSEPOINTER mousepointer = new NEWMOUSEPOINTER();
    mousepointer.setCommand(Command.NEWMOUSEPOINTER);
    mousepointer.setHeader(REQUEST);
    mousepointer.setDataset(Protocol.DATASET);
    mousepointer.setX(x);
    mousepointer.setY(y);

    Webserver.sendMessage(mousepointer, receivers);

    return Response.noContent().build();
  }
}
