/*
 * This file is a part of the SteamWar software.
 *
 * Copyright (C) 2020  SteamWar.de-Serverteam
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package de.steamwar.bausystem.world.regions;

import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import de.steamwar.bausystem.WorldeditWrapper;
import de.steamwar.bausystem.world.Color;
import de.steamwar.sql.NoClipboardException;
import de.steamwar.sql.SchematicData;
import de.steamwar.sql.SchematicNode;
import org.bukkit.Location;
import org.bukkit.configuration.ConfigurationSection;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class Prototype {
    static final Map<String, Prototype> prototypes = new HashMap<>();

    private final int sizeX;
    private final int sizeY;
    private final int sizeZ;

    private final int offsetX;
    private final int offsetY;
    private final int offsetZ;

    private final int extensionPositiveZ;
    private final int extensionNegativeZ;
    private final int extensionPositiveY;
    private final int extensionAxisX;
    final boolean extensionPrototypeArea;

    private final int waterLevel;

    private final String schematic;
    private final boolean rotate;

    final Prototype testblock; //nullable
    final Prototype buildArea; //nullable

    private final String protectSchematic; //nullable

    Prototype(ConfigurationSection config) {
        sizeX = config.getInt("sizeX");
        sizeY = config.getInt("sizeY");
        sizeZ = config.getInt("sizeZ");
        schematic = config.getString("schematic");
        offsetX = config.getInt("offsetX", 0);
        offsetY = config.getInt("offsetY", 0);
        offsetZ = config.getInt("offsetZ", 0);
        extensionPositiveZ = config.getInt("extensionPositiveZ", 0);
        extensionNegativeZ = config.getInt("extensionNegativeZ", 0);
        extensionPositiveY = config.getInt("extensionPositiveY", 0);
        extensionAxisX = config.getInt("extensionAxisX", 0);
        if (config.contains("extensionPositiveZ") || config.contains("extensionNegativeZ") || config.contains("extensionPositiveY") || config.contains("extensionAxisX")) {
            Region.extensionArea = true;
        }
        extensionPrototypeArea = extensionNegativeZ != 0 || extensionPositiveZ != 0 || extensionPositiveY != 0 || extensionAxisX != 0;
        waterLevel = config.getInt("waterLevel", 0);
        rotate = config.getBoolean("rotate", false);

        ConfigurationSection testblockSection = config.getConfigurationSection("testblock");
        testblock = testblockSection != null ? new Prototype(testblockSection) : null;

        ConfigurationSection buildAreaSection = config.getConfigurationSection("buildArea");
        buildArea = buildAreaSection != null ? new Prototype(buildAreaSection) : null;
        if (buildArea != null) {
            Region.buildArea = true;
        }

        protectSchematic = config.getString("protection", null);

        if (!config.getName().equals("testblock") && !config.getName().equals("buildArea"))
            prototypes.put(config.getName(), this);
    }

    public Point getMinPoint(Region region, RegionExtensionType regionExtensionType) {
        switch (regionExtensionType) {
            case EXTENSION:
                return new Point(
                        region.minPoint.getX() + offsetX - extensionAxisX,
                        region.minPoint.getY() + offsetY,
                        region.minPoint.getZ() + offsetZ - extensionNegativeZ
                );
            default:
            case NORMAL:
                return new Point(
                        region.minPoint.getX() + offsetX,
                        region.minPoint.getY() + offsetY,
                        region.minPoint.getZ() + offsetZ
                );
        }
    }

    public Point getMaxPoint(Region region, RegionExtensionType regionExtensionType) {
        switch (regionExtensionType) {
            case EXTENSION:
                return new Point(
                        region.minPoint.getX() + offsetX - extensionAxisX + (sizeX + extensionAxisX * 2) - 1,
                        region.minPoint.getY() + offsetY + sizeY + extensionPositiveY - 1,
                        region.minPoint.getZ() + offsetZ - extensionNegativeZ + (sizeZ + extensionNegativeZ + extensionPositiveZ) - 1
                );
            default:
            case NORMAL:
                return new Point(
                        region.minPoint.getX() + offsetX + sizeX - 1,
                        region.minPoint.getY() + offsetY + sizeY - 1,
                        region.minPoint.getZ() + offsetZ + sizeZ - 1
                );
        }
    }

    public boolean inRegion(Region region, Location l, RegionExtensionType regionExtensionType) {
        switch (regionExtensionType) {
            case EXTENSION:
                return inRange(l.getX(), region.minPoint.getX() + offsetX - extensionAxisX, sizeX + extensionAxisX * 2) &&
                        inRange(l.getY(), region.minPoint.getY() + offsetY, sizeY + extensionPositiveY) &&
                        inRange(l.getZ(), region.minPoint.getZ() + offsetZ - extensionNegativeZ, sizeZ + extensionNegativeZ + extensionPositiveZ);
            default:
            case NORMAL:
                return inRange(l.getX(), region.minPoint.getX() + offsetX, sizeX) &&
                        inRange(l.getY(), region.minPoint.getY() + offsetY, sizeY) &&
                        inRange(l.getZ(), region.minPoint.getZ() + offsetZ, sizeZ);
        }
    }

    public EditSession reset(Region region, SchematicNode schem, boolean ignoreAir, Color color) throws IOException, NoClipboardException {
        return reset(region, schem, ignoreAir, color, false);
    }

    public EditSession reset(Region region, SchematicNode schem, boolean ignoreAir, Color color, boolean reset) throws IOException, NoClipboardException {
        PasteOptions pasteOptions;
        if (reset) {
            pasteOptions = new PasteOptions(rotate ^ (schem != null && (schem.getSchemtype().fightType() || schem.getSchemtype().check())), ignoreAir, color, true, getMinPoint(region, RegionExtensionType.EXTENSION), getMaxPoint(region, RegionExtensionType.EXTENSION), waterLevel);
        } else {
            pasteOptions = new PasteOptions(rotate ^ (schem != null && (schem.getSchemtype().fightType() || schem.getSchemtype().check())), ignoreAir, color);
        }

        int x = region.minPoint.getX() + offsetX + sizeX / 2;
        int y = region.minPoint.getY() + offsetY;
        int z = region.minPoint.getZ() + offsetZ + sizeZ / 2;
        if (schem == null) {
            return paste(new File(schematic), x, y, z, pasteOptions);
        } else {
            return paste(new SchematicData(schem).load(), x, y, z, pasteOptions);
        }
    }

    public boolean hasProtection() {
        return protectSchematic != null;
    }

    public EditSession protect(Region region, SchematicNode schem) throws IOException, NoClipboardException {
        int x = region.minPoint.getX() + offsetX + sizeX / 2;
        int y = region.minPoint.getY() + testblock.offsetY - 1;
        int z = region.minPoint.getZ() + offsetZ + sizeZ / 2;
        if (schem == null) {
            return paste(new File(protectSchematic), x, y, z, new PasteOptions(rotate, false, Color.YELLOW));
        } else {
            return paste(new SchematicData(schem).load(), x, y, z, new PasteOptions(rotate, false, Color.YELLOW));
        }
    }

    public boolean hasTestblock() {
        return testblock != null;
    }

    public EditSession resetTestblock(Region region, SchematicNode schem, Color color, boolean reset) throws IOException, NoClipboardException {
        return testblock.reset(region, schem, false, color, reset && waterLevel == 0);
    }

    private static boolean inRange(double l, int min, int size) {
        return min <= l && l < min + size;
    }

    private static EditSession paste(File file, int x, int y, int z, PasteOptions pasteOptions) { //Type of protect
        return WorldeditWrapper.impl.paste(file, x, y, z, pasteOptions);
    }

    private static EditSession paste(Clipboard clipboard, int x, int y, int z, PasteOptions pasteOptions) {
        return WorldeditWrapper.impl.paste(clipboard, x, y, z, pasteOptions);
    }
}
