package server;

import com.fasterxml.jackson.core.JsonProcessingException;
import de.dhbwstuttgart.core.JavaTXCompiler;
import de.dhbwstuttgart.parser.SourceLoc;
import de.dhbwstuttgart.server.packet.DebugPacket;
import de.dhbwstuttgart.server.packet.IPacket;
import de.dhbwstuttgart.server.packet.InvalidPacket;
import de.dhbwstuttgart.server.packet.PacketContainer;
import de.dhbwstuttgart.server.packet.dataContainers.ISerializableData;
import de.dhbwstuttgart.server.packet.dataContainers.KeyStorage;
import de.dhbwstuttgart.server.packet.dataContainers.serialized.ISerialNode;
import de.dhbwstuttgart.server.packet.dataContainers.serialized.SerialList;
import de.dhbwstuttgart.server.packet.dataContainers.serialized.SerialMap;
import de.dhbwstuttgart.server.packet.dataContainers.serialized.SerialUUID;
import de.dhbwstuttgart.server.packet.dataContainers.serialized.SerialValue;
import de.dhbwstuttgart.typeinference.constraints.Constraint;
import de.dhbwstuttgart.typeinference.constraints.ConstraintSet;
import de.dhbwstuttgart.typeinference.unify.UnifyContext;
import de.dhbwstuttgart.typeinference.unify.model.UnifyPair;
import java.util.function.BiFunction;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class PacketTest {

  @Test
  public void serializeUnifyPair() throws JsonProcessingException {
    UnifyContext unifyContext = PacketExampleData.createTestContext();

    var original = PacketExampleData.getExampleUnifyPair(unifyContext, "de.test.");
    var reconstruction = serializeAndDeserialize(original, unifyContext,
      (o,k) -> UnifyPair.fromSerial((SerialUUID) o,unifyContext,k));

    assertEquals(original.getClass(), reconstruction.getClass());
    assertEquals(original.toString(), reconstruction.toString());
    assertEquals(original, reconstruction);
  }

  @Test
  public void serializeUnifyPairConstraint() throws JsonProcessingException {
    UnifyContext unifyContext = PacketExampleData.createTestContext();

    var original = PacketExampleData.getExampleUnifyPairConstraint(unifyContext, "de.", true);
    var reconstruction = serializeAndDeserialize(original, unifyContext,
      (o,k) -> Constraint.fromSerial((SerialUUID) o, unifyContext, UnifyPair.class, k));

    assertEquals(original.getClass(), reconstruction.getClass());
    assertEquals(original.toString(), reconstruction.toString());
    assertEquals(original, reconstruction);
  }

  @Test
  public void serializeUnifyPairConstraintSet() throws JsonProcessingException {
    UnifyContext unifyContext = PacketExampleData.createTestContext();

    var original = PacketExampleData.getExampleUnifyPairConstraintSet(unifyContext, "de.", 1, 2);
    var reconstruction = serializeAndDeserialize(original, unifyContext,
      (o,k) -> ConstraintSet.fromSerial((SerialMap) o, unifyContext, UnifyPair.class, k));

    assertEquals(original.getClass(), reconstruction.getClass());
    assertEquals(original.toString(), reconstruction.toString());
    assertEquals(original, reconstruction);
  }


  /**
   *  Helper method for serializing an ISerialNode into JSON, then deserializing it
   */
  private <T extends ISerialNode, R extends ISerializableData> R serializeAndDeserialize(
    ISerializableData object, UnifyContext unifyContext, BiFunction<T, KeyStorage, R> fromSerial
  ) throws JsonProcessingException {

    DebugPacket packet = new DebugPacket();
    KeyStorage keyStorage = new KeyStorage();
    var serializedObject = object.toSerial(keyStorage);
    if (serializedObject instanceof SerialUUID sObject)     packet.a1 = sObject;
    if (serializedObject instanceof SerialMap sObject)      packet.b1 = sObject;
    if (serializedObject instanceof SerialList<?> sObject)  packet.c1 = sObject;
    if (serializedObject instanceof SerialValue<?> sObject) packet.d1 = sObject;
    packet.b2 = keyStorage.toSerial(keyStorage);

    DebugPacket reconstructedPacket = serializeAndDeserializePacket(packet);

    KeyStorage reconstructedKeyStorage = KeyStorage.fromSerial(reconstructedPacket.b2, unifyContext);
    ISerialNode reconstructedData = null;
    if (serializedObject instanceof SerialUUID)     reconstructedData = packet.a1;
    if (serializedObject instanceof SerialMap)      reconstructedData = packet.b1;
    if (serializedObject instanceof SerialList<?>)  reconstructedData = packet.c1;
    if (serializedObject instanceof SerialValue<?>) reconstructedData = packet.d1;

    assertNotNull(reconstructedData);
    return fromSerial.apply( (T) reconstructedData, reconstructedKeyStorage);
  }

  private <T extends IPacket> T serializeAndDeserializePacket(T packet) throws JsonProcessingException {
    String json = PacketContainer.serialize(packet);
    IPacket reconstructedPacket = PacketContainer.deserialize(json);
    assertNotNull(reconstructedPacket);
    assertSame(packet.getClass(), reconstructedPacket.getClass());
    return (T) reconstructedPacket;
  }
}
