package CH.ifa.draw.util;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.Hashtable;

/**
 * Class that implements patching by replacing the class names of moved classes during loading.
 * @deprecated This class is not to be used externally. Please use the {@link de.renew.draw.storables.api.StorableApi} instead.
 */
@Deprecated(since = "5.0", forRemoval = true)
public class PatchingStorableInput extends CH.ifa.draw.util.StorableInput {

    /**
     * enables patching when a string attribute is read.
     */
    private final boolean stringReadPatching = true;

    /**
     * maps prefixes which must be replaced by another prefix.
     */
    private static final Hashtable<String, String> patchPrefixes = new Hashtable<>();

    static {
        patchPrefixes.put("CH.ifa.draw.cpn.", "de.renew.gui.");
    }

    /**
     * maps classes which must be replaced by other classes.
     */
    private static final Hashtable<String, String> patchMap = new Hashtable<>();

    static {
        ReplaceHelper toGui = new ReplaceHelper("de.renew.gui");
        toGui.registerAll(
            "de.renew.gui.fs.AssocArrowTip", "de.renew.gui.fs.IsaArrowTip",
            "de.renew.diagram.AssocArrowTip", "de.renew.fa.figures.AssocArrowTip");
        ReplaceHelper toDiagramFigures = new ReplaceHelper("de.renew.diagram.figures");
        toDiagramFigures.registerAll(
            "de.renew.diagram.ActionTextFigure", "de.renew.diagram.DCServiceTextFigure",
            "de.renew.diagram.DestructionFigure", "de.renew.diagram.DiagramFigure",
            "de.renew.diagram.DiagramFrameFigure", "de.renew.diagram.DiagramTextFigure",
            "de.renew.diagram.HSplitFigure", "de.renew.diagram.MiniDiamondFigure",
            "de.renew.diagram.RoleDescriptorFigure", "de.renew.diagram.TailFigure",
            "de.renew.diagram.TaskFigure", "de.renew.diagram.TriangleIterationConnectorFigure",
            "de.renew.diagram.VJoinFigure", "de.renew.diagram.VSplitFigure",
            "de.renew.diagram.IteratorFigure");
        ReplaceHelper toDiagramConnections = new ReplaceHelper("de.renew.diagram.connections");
        toDiagramConnections.registerAll(
            "de.renew.diagram.AbstractMessageConnection", "de.renew.diagram.LifeLineConnection",
            "de.renew.diagram.MessageConnection", "de.renew.diagram.SynchronousMessageConnection",
            "de.renew.diagram.SynchronousReplyConnection",
            "de.renew.diagram.DiagramLineConnection");
        ReplaceHelper toDiagramConnectors = new ReplaceHelper("de.renew.diagram.connectors");
        toDiagramConnectors.registerAll(
            "de.renew.diagram.BottomConnector", "de.renew.diagram.HBottomConnector",
            "de.renew.diagram.HCenterConnector", "de.renew.diagram.HorizontalConnector",
            "de.renew.diagram.HSplitCenterConnector", "de.renew.diagram.HTopConnector",
            "de.renew.diagram.LeftBottomConnector", "de.renew.diagram.LeftConnector",
            "de.renew.diagram.RightBottomConnector", "de.renew.diagram.RightConnector",
            "de.renew.diagram.SplitConnector", "de.renew.diagram.TopConnector",
            "de.renew.diagram.VerticalConnector", "de.renew.diagram.VSplitCenterConnector");
        ReplaceHelper toDiagramDecorations = new ReplaceHelper("de.renew.diagram.decorations");
        toDiagramDecorations.registerAll(
            "de.renew.diagram.ANDDecoration", "de.renew.diagram.DestructionDecoration",
            "de.renew.diagram.FigureDecoration", "de.renew.diagram.ORDecoration",
            "de.renew.diagram.SplitDecoration", "de.renew.diagram.SynchronousMessageArrowTip",
            "de.renew.diagram.XORDecoration", "de.renew.diagram.IteratorDecoration");
        ReplaceHelper toWfnet = new ReplaceHelper("de.renew.wfnet");
        toWfnet.registerAll("de.renew.workflow.TaskFigure");

    }

    /**
     * Creates a StorableInput with the given input stream and encoding information.
     *
     * @param stream the stream to be read for the storable input
     * @param useUTF whether UTF-8 ({@code true}) or the default encoding ({@code false} is to be used
     * @deprecated This constructor is not to be used externally.
     * Please use the method {@link de.renew.draw.storables.api.StorableApi#createPatchingStorableInput(InputStream)} instead.
     */
    @Deprecated(since = "5.0", forRemoval = true)
    public PatchingStorableInput(InputStream stream, boolean useUTF) {
        super(stream, useUTF);
    }

    /**
     * @deprecated This constructor is currently not used and will be removed in a later version.
     */
    @Deprecated(since = "5.0", forRemoval = true)
    PatchingStorableInput(File file, boolean useUTF) throws FileNotFoundException {
        super(file, useUTF);
    }

    /**
     * Initializes a Storable input with the given string.
     * @deprecated This constructor is currently not used and will be removed in a later version.
     */
    @Deprecated(since = "5.0", forRemoval = true)
    public PatchingStorableInput(String stringStream) {
        super(stringStream);
    }

    /**
     * Creates a StorableInput with the given location and encoding information.
     *
     * @param location URL of the file
     * @param useUTF whether UTF-8 ({@code true}) or the default encoding ({@code false} is to be used
     * @deprecated This constructor is not to be used externally.
     * Please use the method {@link de.renew.draw.storables.api.StorableApi#createPatchingStorableInput(URL, boolean)} instead.
     */
    @Deprecated(since = "5.0", forRemoval = true)
    public PatchingStorableInput(URL location, boolean useUTF) throws IOException {
        super(location, useUTF);
    }

    @Override
    protected Object makeInstance(String className) throws IOException {
        return super.makeInstance(patchString(className));
    }

    @Override
    public String readString() throws IOException {
        String s = super.readString();

        if (stringReadPatching) {
            s = patchString(s);
        }

        return s;
    }

    protected String patchString(String s) {
        String original = s;
        boolean patched = false;
        Enumeration<String> e = patchPrefixes.keys();
        while (e.hasMoreElements() && !patched) {
            String patchPrefix = e.nextElement();
            if (s.startsWith(patchPrefix)) {
                s = patchPrefixes.get(patchPrefix) + s.substring(patchPrefix.length());
                patched = true;
            }
        }
        String patch = patchMap.get(s);
        if (patch != null) {
            s = patch;
        }

        if (!original.equals(s)) {
            LOGGER.info("Patched class name: " + original + " -> " + s);
        }

        return s;
    }

    /**
     * Helper class to allow registering several classes to the {@code patchMap} at once.
     * @param newPackage the new package's name, with no trailing dot
     */
    private record ReplaceHelper(String newPackage) {

        /**
         * Adds all fully classified listed classes to the {@code patchMap}, to be patched to {@code newPackage}.
         * @param classNames the fully classified class names to be registered
         */
        public void registerAll(String... classNames) {
            for (String className : classNames) {
                patchMap.put(className, convert(className));
            }
        }

        /**
         * Adds all classes in the given package to the {@code patchMap}, to be patched to {@code newPackage}.
         * @param packageName the old package name, with no trailing dot
         * @param classNames the class names to be registered, without the package name/path
         */
        public void registerAllInPackage(String packageName, String... classNames) {
            if (packageName.endsWith("."))
                packageName = packageName.substring(0, packageName.length() - 1);
            for (String className : classNames) {
                String fullClassName = packageName + "." + className;
                patchMap.put(fullClassName, convert(fullClassName));
            }
        }

        /**
         * Converts a fully classified class name to the new package.
         * @param fullClassName the fully classified class name
         * @return the converted, fully classified class name
         */
        public String convert(String fullClassName) {
            if (newPackage == null || newPackage.isEmpty()) {
                throw new NullPointerException("newPackage is null or empty");
            }
            String className = fullClassName.substring(fullClassName.lastIndexOf('.') + 1);
            return newPackage + "." + className;
        }
    }
}