package com.boydti.fawe.regions.general.plot;

import com.boydti.fawe.jnbt.anvil.MCAChunk;
import com.boydti.fawe.jnbt.anvil.MCAFile;
import com.boydti.fawe.jnbt.anvil.MCAFilter;
import com.boydti.fawe.jnbt.anvil.MCAQueue;
import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.MathMan;
import com.boydti.fawe.util.SetQueue;
import com.intellectualcrafters.plot.PS;
import com.intellectualcrafters.plot.object.ChunkLoc;
import com.intellectualcrafters.plot.object.Location;
import com.intellectualcrafters.plot.object.Plot;
import com.intellectualcrafters.plot.object.PlotArea;
import com.intellectualcrafters.plot.object.PlotPlayer;
import com.intellectualcrafters.plot.util.expiry.ExpireManager;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;


import static com.google.common.base.Preconditions.checkNotNull;

public class PlotTrim {
    private final MCAQueue queue;
    private final PlotArea area;
    private final PlotPlayer player;
    private final MCAQueue originalQueue;
    private final File root;
    private final File originalRoot;
    private byte[][] ids;
    private boolean deleteUnowned = true;

    public PlotTrim(PlotPlayer player, PlotArea area, String worldName, boolean deleteUnowned) {
        FaweQueue tmpQueue = SetQueue.IMP.getNewQueue(worldName, true, false);
        File saveFolder = tmpQueue.getSaveFolder();
        this.root = new File(saveFolder.getParentFile().getParentFile(), worldName + "-Copy" + File.separator + "region");
        this.originalRoot = saveFolder;
        this.originalQueue = new MCAQueue(worldName, originalRoot, true);
        this.queue = new MCAQueue(worldName + "-Copy", root, true);
        this.area = area;
        this.player = player;
        this.deleteUnowned = deleteUnowned;
    }

    public void setChunk(byte[][] ids) {
        checkNotNull(ids);
        this.ids = ids;
    }

    public void setChunk(int x, int z) {
        this.ids = ((MCAChunk) originalQueue.getFaweChunk(x, z)).ids;
    }

    private Map<Long, Object> chunks = new ConcurrentHashMap<>();
    private Object PRESENT = new Object();

    private void removeChunks(Plot plot) {
        Location pos1 = plot.getBottom();
        Location pos2 = plot.getTop();
        int ccx1 = pos1.getX() >> 4;
        int ccz1 = pos1.getZ() >> 4;
        int ccx2 = pos2.getX() >> 4;
        int ccz2 = pos2.getZ() >> 4;
        for (int x = ccx1; x <= ccx2; x++) {
            for (int z = ccz1; z <= ccz2; z++) {
                long pair = MathMan.pairInt(x, z);
                chunks.remove(pair);
            }
        }
    }

    public void run() {
        final Set<ChunkLoc> mcas = new HashSet<>();
        if (deleteUnowned && area != null) {
            originalQueue.filterWorld(new MCAFilter() {
                @Override
                public boolean appliesFile(int mcaX, int mcaZ) {
                    mcas.add(new ChunkLoc(mcaX, mcaZ));
                    return false;
                }
            });
            ArrayList<Plot> plots = new ArrayList<>();
            plots.addAll(PS.get().getPlots(area));
            if (ExpireManager.IMP != null) {
                plots.removeAll(ExpireManager.IMP.getPendingExpired());
            }
            for (Plot plot : plots) {
                Location pos1 = plot.getBottom();
                Location pos2 = plot.getTop();
                int ccx1 = pos1.getX() >> 9;
                int ccz1 = pos1.getZ() >> 9;
                int ccx2 = pos2.getX() >> 9;
                int ccz2 = pos2.getZ() >> 9;
                for (int x = ccx1; x <= ccx2; x++) {
                    for (int z = ccz1; z <= ccz2; z++) {
                        ChunkLoc loc = new ChunkLoc(x, z);
                        mcas.remove(loc);
                    }
                }
            }
            for (ChunkLoc mca : mcas) {
                int bx = mca.x << 5;
                int bz = mca.z << 5;
                for (int x = 0; x < 32; x++) {
                    for (int z = 0; z < 32; z++) {
                        long pair = MathMan.pairInt(bx + x, bz + z);
                        chunks.put(pair, PRESENT);
                    }
                }
            }
            for (Plot plot : plots) {
                removeChunks(plot);
            }
        }
        originalQueue.filterWorld(new MCAFilter() {
            @Override
            public boolean appliesFile(int mcaX, int mcaZ) {
                ChunkLoc loc = new ChunkLoc(mcaX, mcaZ);
                if (mcas.contains(loc)) {
                    return false;
                }
                return true;
            }

            @Override
            public MCAFile applyFile(MCAFile mca) {
                int mcaX = mca.getX();
                int mcaZ = mca.getZ();
                ChunkLoc loc = new ChunkLoc(mcaX, mcaZ);
                if (mcas.contains(loc)) {
                    player.sendMessage("Delete MCA " + mca);
                    mca.setDeleted(true);
                    return null;
                }
                try {
                    File copy = new File(root, mca.getFile().getName());
                    if (!copy.exists()) {
                        copy = MainUtil.copyFile(mca.getFile(), copy);
                        player.sendMessage("Filter copy -> " + copy);
                    } else {
                        player.sendMessage("Filter existing: " + mcaX + "," + mcaZ);
                    }
                    return new MCAFile(mca.getParent(), copy);
                } catch (IOException e) {
                    e.printStackTrace();
                    throw new RuntimeException(e);
                }
            }

            @Override
            public boolean appliesChunk(int cx, int cz) {
                return true;
            }

            @Override
            public MCAChunk applyChunk(MCAChunk chunk, Object ignore) {
                long pair = MathMan.pairInt(chunk.getX(), chunk.getZ());
                if (chunks.containsKey(pair)) {
                    chunk.setDeleted(true);
                    return null;
                }
                if (ids != null) {
                    for (int i = 0; i < ids.length; i++) {
                        if (!isEqual(ids[i], chunk.ids[i])) {
                            return null;
                        }
                    }
                    chunk.setDeleted(true);
                }
                return null;
            }
        });
        player.sendMessage("Done!");
    }

    private int count = 0;

    private boolean isEqual(byte[] a, byte[] b) {
        if (a == b) {
            return true;
        }
        if (a != null) {
            if (b != null) {
                return Arrays.equals(a, b);
            }
            return isEmpty(a);
        }
        return isEmpty(b);
    }

    private boolean isEmpty(byte[] a) {
        for (byte b : a) {
            if (b != 0) {
                return false;
            }
        }
        return true;
    }
}
