package de.steamwar.bausystem.features.tracer;

import de.steamwar.bausystem.region.Region;
import lombok.Cleanup;
import lombok.SneakyThrows;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.util.Vector;

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

public class TraceRepository {

    /**
     * Increment this when changing serialisation format
     */
    public static final int SERIALISATION_VERSION = 1;
    public static File tracesFolder = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "traces");

    @SneakyThrows
    protected static int getVersion(File metadataFile) {
        @Cleanup
        ObjectInputStream reader = new ObjectInputStream(new FileInputStream(metadataFile));
        reader.readUTF();
        reader.readUTF();
        reader.readObject();
        try {
            int version = reader.readInt();
            return version;
        } catch (EOFException e) {
            return 0;
        }
    }

    @SneakyThrows
    public static Trace readTrace(File metadataFile) {
        @Cleanup
        ObjectInputStream reader = new ObjectInputStream(new FileInputStream(metadataFile));
        UUID uuid = UUID.fromString(reader.readUTF());
        Region region = Region.getREGION_MAP().get(reader.readUTF());
        Date date = (Date) reader.readObject();
        File recordsFile = new File(tracesFolder,uuid + ".records");
        int serialisationVersion = reader.readInt();
        int recordsCount = reader.readInt();

        return new Trace(uuid, region, date, metadataFile, recordsFile, recordsCount);
    }

    @SneakyThrows
    protected static void writeTrace(Trace trace, List<TNTPoint> records) {
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(trace.getMetadataSaveFile()));
        outputStream.writeUTF(trace.getUuid().toString());
        outputStream.writeUTF(trace.getRegion().getName());
        outputStream.writeObject(trace.getDate());
        outputStream.writeInt(SERIALISATION_VERSION);
        outputStream.writeInt(records.size());
        outputStream.flush();
        outputStream.close();


        writeTraceRecords(trace.getRecordsSaveFile(), records);
    }

    @SneakyThrows
    protected static void writeTraceRecords(File recordsFile, List<TNTPoint> records) {
        DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(recordsFile));
        for (TNTPoint record : records) {
            outputStream.writeInt(record.getTntId());
            outputStream.writeBoolean(record.isExplosion());
            outputStream.writeBoolean(record.isInWater());
            outputStream.writeBoolean(record.isAfterFirstExplosion());
            outputStream.writeBoolean(record.isDestroyedBuildArea());
            outputStream.writeBoolean(record.isDestroyedTestBlock());
            outputStream.writeLong(record.getTicksSinceStart());
            outputStream.writeInt(record.getFuse());
            Location location = record.getLocation();
            outputStream.writeDouble(location.getX());
            outputStream.writeDouble(location.getY());
            outputStream.writeDouble(location.getZ());
            Vector velocity = record.getVelocity();
            outputStream.writeDouble(velocity.getX());
            outputStream.writeDouble(velocity.getY());
            outputStream.writeDouble(velocity.getZ());
        }
        outputStream.flush();
        outputStream.close();
    }

    @SneakyThrows
    protected static TNTPoint readTraceRecord(DataInputStream objectInput) {

        int tntId = objectInput.readInt();
        boolean explosion = objectInput.readBoolean();
        boolean inWater = objectInput.readBoolean();
        boolean afterFirstExplosion = objectInput.readBoolean();
        boolean destroyedBuildArea = objectInput.readBoolean();
        boolean destroyedTestBlock = objectInput.readBoolean();
        long ticksSinceStart = objectInput.readLong();
        int fuse = objectInput.readInt();

        double locX = objectInput.readDouble();
        double locY = objectInput.readDouble();
        double locZ = objectInput.readDouble();
        Location location = new Location(Bukkit.getWorlds().get(0), locX, locY, locZ);

        double velX = objectInput.readDouble();
        double velY = objectInput.readDouble();
        double velZ = objectInput.readDouble();
        Vector velocity = new Vector(velX, velY, velZ);

        return new TNTPoint(tntId, explosion, inWater, afterFirstExplosion, destroyedBuildArea, destroyedTestBlock, ticksSinceStart, fuse, location, velocity, Collections.emptyList());
    }

    @SneakyThrows
    protected static List<TNTPoint> readTraceRecords(Trace trace) {
        File recordsFile = trace.getRecordsSaveFile();
        @Cleanup
        DataInputStream inputStream = new DataInputStream(new FileInputStream(recordsFile));

        List<TNTPoint> records = new ArrayList<>();
        for (int i = 0; i < trace.getRecordsCount(); i++) {
            records.add(readTraceRecord(inputStream));
        }

        Map<Integer, List<TNTPoint>> histories = new HashMap<>();
        for (TNTPoint record : records) {
            int tntId = record.getTntId();
            List<TNTPoint> history = histories.computeIfAbsent(tntId, id -> new ArrayList<>());
            history.add(record);
            record.setHistory(history);
        }

        return records;
    }
}
