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

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;

import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Uses ASM to patch class bytecode according to a list of access widener entries.
 *
 * Returns {@code null} if the class is not targeted by any entry (no-op signal
 * to the caller so it can skip the write).
 */
public class ClassPatcher {

    private static final Logger LOG = Logger.getLogger("ClassPatcher");

    private final List<AccessWidenerEntry> entries;

    /** Pre-computed set of targeted internal names for fast filtering. */
    private final Set<String> targets;

    private final Set<String> targetsPublicConstructor;

    public ClassPatcher(List<AccessWidenerEntry> entries) {
        this.entries = entries;
        this.targets = entries.stream()
                .map(AccessWidenerEntry::target)
                .flatMap(s -> {
                    if (!s.contains("$")) return Stream.of(s);
                    int index = s.lastIndexOf('$');
                    return Stream.of(s, s.substring(0, index));
                })
                .collect(Collectors.toSet());
        this.targetsPublicConstructor = entries.stream()
                .filter(entry -> entry.directive().equals("transitive-extendable"))
                .map(AccessWidenerEntry::target)
                .collect(Collectors.toSet());
    }

    /**
     * Patches {@code classBytes} if {@code className} is targeted.
     *
     * @return patched bytes, or {@code null} if no changes were needed
     */
    public byte[] patch(String className, byte[] classBytes) {
        if (!targets.contains(className)) return null;

        try {
            ClassReader cr = new ClassReader(classBytes);
            ClassWriter cw = new ClassWriter(cr, 0);
            cr.accept(new ClassTransformer(cw, className, entries, targetsPublicConstructor.contains(className)), ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
            return cw.toByteArray();
        } catch (Exception e) {
            LOG.warning("[AccessWidener] Failed to transform " + className + ": " + e.getMessage());
            return null;
        }
    }
}

