/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2025 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.features.tracer.rendering;

import de.steamwar.bausystem.features.tracer.TNTPoint;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.util.Vector;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * A settable flag that changes how a trace is rendered
 */
public abstract class ViewFlag {
    public static final Vector GRAVATY = new Vector(0.0, -0.04, 0.0);
    public static final Vector DRAG_FACTOR = new Vector(0.98, 0.98, 0.98);

    /**
     * Static registry of static flags
     */
    public static final List<ViewFlag> flags = new ArrayList<>();

    /**
     * Inverse flags are used by trace render by default, as long as they are not explicitly added as argument
     */
    public static final List<ViewFlag> inverseFlags = new ArrayList<>();

    public static ViewFlag EXPLOSION = new ViewFlag(true, false, "explosion", "e") {
        @Override
        public Stream<TNTPoint> filter(Stream<TNTPoint> records) {
            return records.filter(TNTPoint::isExplosion);
        }
    };

    public static ViewFlag IGNITE = new ViewFlag(true, true, "ignite", "i") {
        @Override
        public Stream<TNTPoint> filter(Stream<TNTPoint> records) {
            return records.filter(record -> record.isAfterFirstExplosion());
        }
    };

    public static ViewFlag SOURCE = new ViewFlag(true, false, IGNITE, "source", "s") {
        @Override
        public Stream<TNTPoint> filter(Stream<TNTPoint> records) {
            return records.filter(record -> record.getFuse() == 80);
        }
    };

    public static ViewFlag BUILD_DESTROY_ONLY = new ViewFlag(true, false, "build-destroy-only") {
        @Override
        public Stream<TNTPoint> filter(Stream<TNTPoint> records) {
            return records.filter(record -> record.getHistory().get(record.getHistory().size() - 1).isDestroyedBuildArea());
        }
    };

    public static ViewFlag TESTBLOCK_DESTROY_ONLY = new ViewFlag(true, false, "testblock-destroy-only") {
        @Override
        public Stream<TNTPoint> filter(Stream<TNTPoint> records) {
            return records.filter(record -> record.getHistory().get(record.getHistory().size() - 1).isDestroyedTestBlock());
        }
    };

    public static ViewFlag MICROMOTION = new ViewFlag(true, false, "micromotion", "m") {
        @Override
        public Stream<TNTPoint> filter(Stream<TNTPoint> stream) {
            List<TNTPoint> records = stream.collect(Collectors.toList());
            ;
            Set<Integer> seen = new HashSet<>();
            Set<TNTPoint> toRemove = new HashSet<>();

            for (TNTPoint uniqueRecord : records) {
                if (seen.contains(uniqueRecord.getTntId())) continue;

                boolean hasMicromotion = false;
                for (TNTPoint record : uniqueRecord.getHistory()) {
                    Vector velocity = record.getVelocity();
                    if (velocity.getY() == 0 && (Math.abs(velocity.getX()) < 0.001 || Math.abs(velocity.getZ()) < 0.001)) {
                        hasMicromotion = true;
                        break;
                    }
                }

                if (!hasMicromotion) toRemove.add(uniqueRecord);

                seen.add(uniqueRecord.getTntId());
            }

            for (TNTPoint record : toRemove) {
                records.removeAll(record.getHistory());
            }

            return records.stream();
        }
    };

    public static ViewFlag ADVANCED = new ViewFlag(true, false, "advanced", "a") {
        @Override
        public void modify(REntityServer server, List<TraceEntity> entities) {
            for (TraceEntity entity : entities) {
                TNTPoint representative = entity.getRecords().get(0);
                Optional<TNTPoint> prev = representative.getPrevious();
                if (prev.isEmpty()) continue;

                TNTPoint previous = prev.get();
                Location delta = representative.getLocation().clone().subtract(previous.getLocation());
                Vector previousVelocity = previous.isAfterFirstExplosion() ? previous.getVelocity() : delta.toVector().clone().divide(DRAG_FACTOR).subtract(GRAVATY);

                Location yLocation = previous.getLocation().clone().add(0, delta.getY(), 0);
                if (yLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && yLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) {
                    RFallingBlockEntity y = new RFallingBlockEntity(server, yLocation, Material.WHITE_STAINED_GLASS);
                    y.setNoGravity(true);
                }

                Location secoundLocation;
                if (Math.abs(previousVelocity.getX()) >= Math.abs(previousVelocity.getZ())) {
                    secoundLocation = previous.getLocation().clone().add(delta.getX(), delta.getY(), 0);
                } else {
                    secoundLocation = previous.getLocation().clone().add(0, delta.getY(), delta.getZ());
                }

                if (secoundLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && secoundLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) {
                    RFallingBlockEntity second = new RFallingBlockEntity(server, secoundLocation, Material.WHITE_STAINED_GLASS);
                    second.setNoGravity(true);
                }
            }
        }
    };

    public static ViewFlag COUNT = new ViewFlag(true, false, "count", "c") {
        @Override
        public void modify(REntityServer server, List<TraceEntity> entities) {
            for (TraceEntity entity : entities) {
                entity.setDisplayName(String.valueOf(entity.getRecords().size()));
            }
        }
    };

    public static ViewFlag FUSE = new ViewFlag(true, false, "fuse", "f") {
        @Override
        public void modify(REntityServer server, List<TraceEntity> entities) {
            for (TraceEntity entity : entities) {
                List<String> fuses = entity.getRecords()
                        .stream()
                        .map(TNTPoint::getFuse)
                        .distinct()
                        .sorted()
                        .map(i -> i + "")
                        .collect(Collectors.toList());
                if (fuses.size() <= 5) {
                    entity.setDisplayName(String.join(",", fuses));
                } else {
                    entity.setDisplayName(fuses.stream().limit(5).collect(Collectors.joining(",")) + ", +" + (fuses.size() - 5));
                }
            }
        }
    };

    public static ViewFlag TIME = new ViewFlag(true, false, "time", "t") {
        @Override
        public void modify(REntityServer server, List<TraceEntity> entities) {
            for (TraceEntity entity : entities) {
                List<String> time = entity.getRecords()
                        .stream()
                        .map(TNTPoint::getTicksSinceStart)
                        .distinct()
                        .sorted()
                        .map(i -> i + "")
                        .collect(Collectors.toList());
                if (time.size() <= 5) {
                    entity.setDisplayName(String.join(",", time));
                } else {
                    entity.setDisplayName(time.stream().limit(5).collect(Collectors.joining(",")) + ", +" + (time.size() - 5));
                }
            }
        }
    };

    public static ViewFlag HIGHLIGHT = new ViewFlag(true, false, "highlight", "h") {
        @Override
        public void modify(REntityServer server, List<TraceEntity> entities) {
            for (TraceEntity entity : entities) {
                entity.setGlowing(true);
            }
        }

    };

    /**
     * Name of the flag
     */
    public final String name;

    /**
     * Aliases of the flag
     */
    public final String[] aliases;

    /**
     * A flag that is used whenever this flag is used
     */
    public final ViewFlag[] required;

    protected ViewFlag(boolean isStatic, boolean isInverse, String name, String... aliases) {
        this(isStatic, isInverse, new ViewFlag[0], name, aliases);
    }

    protected ViewFlag(boolean isStatic, boolean isInverse, ViewFlag required, String name, String... aliases) {
        this(isStatic, isInverse, new ViewFlag[]{required}, name, aliases);
    }

    protected ViewFlag(boolean isStatic, boolean isInverse, ViewFlag[] required, String name, String... aliases) {
        this.name = name;
        this.aliases = aliases;
        if (isStatic) flags.add(this);
        if (isInverse) inverseFlags.add(this);
        this.required = required;
    }

    /**
     * Filters the given records for a given condition
     *
     * @param records Records to be filtered
     * @return Filtered records
     */
    public Stream<TNTPoint> filter(Stream<TNTPoint> records) {
        return records;
    }

    /**
     * Modifies the trace rendering
     *
     * @param server   the server the trace is rendered on
     * @param entities the entities representing tnts
     */
    public void modify(REntityServer server, List<TraceEntity> entities) {
    }
}
