package zombie.vehicles;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import zombie.characters.IsoGameCharacter;
import zombie.characters.IsoPlayer;
import zombie.core.physics.CarController;

/**
 * Resolves the effective driver for constraint auth in chained towing.
 * For middle vehicles in a chain, scan through links so long trains still resolve authority.
 */
public final class LandtrainConstraintAuthHelper {
    private LandtrainConstraintAuthHelper() {
    }

    public static IsoGameCharacter resolveConstraintDriver(BaseVehicle vehicle) {
        if (vehicle == null) {
            return null;
        }

        IsoGameCharacter driver = findDriverAlongChain(vehicle, true);
        if (driver != null) return driver;
        return findDriverAlongChain(vehicle, false);
    }

    private static IsoGameCharacter findDriverAlongChain(BaseVehicle start, boolean towardFront) {
        BaseVehicle cursor = start;
        Set<Integer> visited = new HashSet<>();

        while (cursor != null) {
            int id = cursor.getId();
            if (!visited.add(id)) {
                // Safety: malformed link graph; stop scanning.
                return null;
            }

            IsoGameCharacter driver = cursor.getDriver();
            if (driver != null) {
                return driver;
            }

            cursor = towardFront ? cursor.getVehicleTowedBy() : cursor.getVehicleTowing();
        }

        return null;
    }

    public static boolean isEnterBlockedLandtrain(
            BaseVehicle vehicle, IsoGameCharacter chr, int seat) {
        if (isVehicleBeingTowedInLandtrain(vehicle)) {
            return true;
        }
        return vehicle != null && vehicle.isExitBlocked(chr, seat);
    }

    public static boolean isEnterBlocked2Landtrain(BaseVehicle vehicle, int seat) {
        if (isVehicleBeingTowedInLandtrain(vehicle)) {
            return true;
        }
        return vehicle != null && vehicle.isExitBlocked2(seat);
    }

    private static boolean isVehicleBeingTowedInLandtrain(BaseVehicle vehicle) {
        return vehicle != null && vehicle.getVehicleTowedBy() != null;
    }

    public static void authorizationChangedLandtrain(BaseVehicle vehicle, IsoGameCharacter character) {
        if (vehicle == null) {
            return;
        }

        if (character != null) {
            applyAuthorizationAcrossChain(
                    vehicle, BaseVehicle.Authorization.Local, character.getOnlineID(), false);
        } else {
            applyAuthorizationAcrossChain(vehicle, BaseVehicle.Authorization.Server, -1, false);
        }
    }

    public static void authorizationClientCollideLandtrain(BaseVehicle vehicle, IsoPlayer driver) {
        if (vehicle == null || driver == null || vehicle.getDriver() != null) {
            return;
        }

        applyAuthorizationAcrossChain(
                vehicle, BaseVehicle.Authorization.LocalCollide, driver.getOnlineID(), true);
    }

    public static void authorizationServerCollideLandtrain(
            BaseVehicle vehicle, short playerID, boolean isCollide) {
        if (vehicle == null) {
            return;
        }
        if (vehicle.isNetPlayerAuthorization(BaseVehicle.Authorization.Local)) {
            return;
        }

        if (isCollide) {
            applyAuthorizationAcrossChain(
                    vehicle, BaseVehicle.Authorization.LocalCollide, playerID, false);
            return;
        }

        BaseVehicle.Authorization auth =
                playerID == -1 ? BaseVehicle.Authorization.Server : BaseVehicle.Authorization.Local;
        applyAuthorizationAcrossChain(vehicle, auth, playerID, false);
    }

    public static void authorizationServerOnSeatLandtrain(
            BaseVehicle vehicle, IsoPlayer player, boolean enter) {
        if (vehicle == null || player == null) {
            return;
        }

        BaseVehicle vehicleA = vehicle.getVehicleTowing();
        BaseVehicle vehicleB = vehicle.getVehicleTowedBy();
        if (vehicle.isNetPlayerId((short) -1) && enter) {
            if (vehicleA != null && vehicleA.getDriver() == null) {
                vehicle.addPointConstraint(
                        null, vehicleA, vehicle.getTowAttachmentSelf(), vehicleA.getTowAttachmentSelf());
            } else if (vehicleB != null && vehicleB.getDriver() == null) {
                vehicle.addPointConstraint(
                        null, vehicleB, vehicle.getTowAttachmentSelf(), vehicleB.getTowAttachmentSelf());
            } else {
                applyAuthorizationAcrossChain(
                        vehicle, BaseVehicle.Authorization.Local, player.getOnlineID(), false);
            }
        } else if (vehicle.isNetPlayerId(player.getOnlineID()) && !enter) {
            if (vehicleA != null && vehicleA.getDriver() != null) {
                vehicleA.addPointConstraint(
                        null, vehicle, vehicleA.getTowAttachmentSelf(), vehicle.getTowAttachmentSelf());
            } else if (vehicleB != null && vehicleB.getDriver() != null) {
                vehicleB.addPointConstraint(
                        null, vehicle, vehicleB.getTowAttachmentSelf(), vehicle.getTowAttachmentSelf());
            } else {
                applyAuthorizationAcrossChain(vehicle, BaseVehicle.Authorization.Server, -1, false);
            }
        }
    }

    private static List<BaseVehicle> collectConnectedChain(BaseVehicle start) {
        ArrayList<BaseVehicle> chain = new ArrayList<>();
        if (start == null) {
            return chain;
        }

        ArrayDeque<BaseVehicle> pending = new ArrayDeque<>();
        Set<Integer> visitedIds = new HashSet<>();
        pending.add(start);

        while (!pending.isEmpty()) {
            BaseVehicle cursor = pending.removeFirst();
            if (cursor == null) {
                continue;
            }

            int id = cursor.getId();
            if (!visitedIds.add(id)) {
                continue;
            }

            chain.add(cursor);
            BaseVehicle front = cursor.getVehicleTowedBy();
            BaseVehicle rear = cursor.getVehicleTowing();
            if (front != null && front != cursor) {
                pending.add(front);
            }
            if (rear != null && rear != cursor) {
                pending.add(rear);
            }
        }

        return chain;
    }

    private static void applyAuthorizationAcrossChain(
            BaseVehicle start,
            BaseVehicle.Authorization authorization,
            int playerId,
            boolean refreshSimulation) {
        long now = System.currentTimeMillis();
        for (BaseVehicle vehicle : collectConnectedChain(start)) {
            if (vehicle == null) {
                continue;
            }
            vehicle.setNetPlayerAuthorization(authorization, playerId);
            if (refreshSimulation) {
                vehicle.authSimulationTime = now;
                if (vehicle.interpolation != null) {
                    vehicle.interpolation.clear();
                }
            }
        }
    }

    public static void safeParkControllerLandtrain(CarController controller) {
        if (controller != null) {
            controller.park();
        }
    }
}
