package CH.ifa.draw.io.exportFormats;

import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;

import de.renew.draw.storables.ontology.Drawing;
import de.renew.ioontology.FileFilter;

/**
 * Class that allows mapping export formats objects which are of the old CH type {@link ExportFormat}
 * to the new type {@link de.renew.ioontology.exporting.ExportFormat}.
 * Every mapped object is held in a Map to allow returning the same object when mapping the same input.
 * @deprecated This type offers a temporary solution to allow using old export format types. It will later be removed.
 */
@Deprecated(since = "5.0", forRemoval = true)
@SuppressWarnings("removal")
public final class ExportFormatMapper {

    /** Map containing all export formats that were previously already mapped.*/
    private static final Map<ExportFormat, de.renew.ioontology.exporting.ExportFormat<Drawing>> EXPORT_FORMAT_MAP;

    static {
        EXPORT_FORMAT_MAP = new HashMap<>();
    }

    private ExportFormatMapper() {}

    /**
     * Maps the given export format.
     *
     * @param oldFormat the format to be mapped
     * @return the result of the mapping
     */
    public static de.renew.ioontology.exporting.ExportFormat<Drawing> mapExportFormat(
        ExportFormat oldFormat)
    {
        if (EXPORT_FORMAT_MAP.containsKey(oldFormat)) {
            return EXPORT_FORMAT_MAP.get(oldFormat);
        }

        if (oldFormat instanceof ExportFormatMulti oldMultiFormat) {
            de.renew.ioontology.exporting.ExportFormatMulti<Drawing> newFormat =
                mapMultiFormat(oldMultiFormat);
            EXPORT_FORMAT_MAP.put(oldFormat, newFormat);
            return newFormat;
        }

        de.renew.ioontology.exporting.ExportFormat<Drawing> format = mapSingleFormat(oldFormat);
        EXPORT_FORMAT_MAP.put(oldFormat, format);
        return format;
    }

    /**
     * Maps the given export format.
     *
     * @param newFormat the format to be mapped
     * @return the result of the mapping
     */
    public static ExportFormat mapExportFormat(
        de.renew.ioontology.exporting.ExportFormat<Drawing> newFormat)
    {
        if (EXPORT_FORMAT_MAP.containsValue(newFormat)) {
            return EXPORT_FORMAT_MAP.entrySet().stream().filter(e -> e.getValue().equals(newFormat))
                .findFirst().get().getKey();
        }

        if (newFormat instanceof de.renew.ioontology.exporting.ExportFormatMulti<Drawing> newMultiFormat) {
            return mapMultiFormat(newMultiFormat);
        }

        return mapSingleFormat(newFormat);
    }

    private static de.renew.ioontology.exporting.ExportFormat<Drawing> mapSingleFormat(
        ExportFormat oldFormat)
    {
        return new de.renew.ioontology.exporting.ExportFormat<>() {
            @Override
            public String formatName() {
                return oldFormat.formatName();
            }

            @Override
            public FileFilter fileFilter() {
                if (oldFormat.fileFilter() instanceof FileFilter ff) {
                    return ff;
                }
                return new InternalFileFilter(oldFormat.fileFilter());
            }

            @Override
            public File export(Drawing object, File file) throws Exception {
                return oldFormat.export(object, file);
            }

            @Override
            public File export(Drawing[] objects, File file) throws Exception {
                return oldFormat.export(objects, file);
            }

            @Override
            public File[] exportAll(Drawing[] objects, File[] files) throws Exception {
                return oldFormat.exportAll(objects, files);
            }

            @Override
            public boolean canExportNto1() {
                return oldFormat.canExportNto1();
            }

            @Override
            public de.renew.ioontology.exporting.ExportFormat<Drawing>[] canExport(File path) {
                return ExportFormatMapper.canExportFile(path, oldFormat);
            }

            @Override
            public boolean canExportObject(Object object) {
                return object instanceof Drawing && oldFormat.canExportDrawing((Drawing) object);
            }

            @Override
            public int getShortCut() {
                return oldFormat.getShortCut();
            }

            @Override
            public int getModifier() {
                return oldFormat.getModifier();
            }

            @Override
            public boolean forceGivenName() {
                return oldFormat.forceGivenName();
            }
        };
    }

    private static de.renew.ioontology.exporting.ExportFormatMulti<Drawing> mapMultiFormat(
        ExportFormatMulti oldFormat)
    {

        return new de.renew.ioontology.exporting.ExportFormatMulti<>() {
            @Override
            public void addExportFormat(
                de.renew.ioontology.exporting.ExportFormat<Drawing> format)
            {
                oldFormat.addExportFormat(mapExportFormat(format));
            }

            @Override
            public void removeExportFormat(
                de.renew.ioontology.exporting.ExportFormat<Drawing> format)
            {
                oldFormat.removeExportFormat(mapExportFormat(format));
            }

            @Override
            public List<de.renew.ioontology.exporting.ExportFormat<Drawing>> getExportFormats() {
                return Arrays.stream(oldFormat.allExportFormats())
                    .map(ExportFormatMapper::mapExportFormat).toList();
            }

            @Override
            public String formatName() {
                return oldFormat.formatName();
            }

            @Override
            public FileFilter fileFilter() {
                if (oldFormat.fileFilter() instanceof FileFilter ff) {
                    return ff;
                }
                return new InternalFileFilter(oldFormat.fileFilter());
            }

            @Override
            public File export(Drawing object, File file) throws Exception {
                return oldFormat.export(object, file);
            }

            @Override
            public File export(Drawing[] objects, File file) throws Exception {
                return oldFormat.export(objects, file);
            }

            @Override
            public File[] exportAll(Drawing[] objects, File[] files) throws Exception {
                return oldFormat.exportAll(objects, files);
            }

            @Override
            public boolean canExportNto1() {
                return oldFormat.canExportNto1();
            }

            @Override
            public de.renew.ioontology.exporting.ExportFormat<Drawing>[] canExport(File path) {
                return ExportFormatMapper.canExportFile(path, oldFormat);
            }

            @Override
            public boolean canExportObject(Object object) {
                return object instanceof Drawing && oldFormat.canExportDrawing((Drawing) object);
            }

            @Override
            public int getShortCut() {
                return oldFormat.getShortCut();
            }

            @Override
            public int getModifier() {
                return oldFormat.getModifier();
            }

            @Override
            public boolean forceGivenName() {
                return oldFormat.forceGivenName();
            }
        };

    }

    private static de.renew.ioontology.exporting.ExportFormat<Drawing>[] canExportFile(
        File path, ExportFormat oldFormat)
    {
        ExportFormat[] formats = oldFormat.canExport(path);
        de.renew.ioontology.exporting.ExportFormat<Drawing>[] result =
            new de.renew.ioontology.exporting.ExportFormat[formats.length];
        for (int i = 0; i < formats.length; i++) {
            result[i] = mapExportFormat(formats[i]);
        }
        return result;
    }

    private static ExportFormat[] canExportFile(
        File path, de.renew.ioontology.exporting.ExportFormat<Drawing> newFormat)
    {
        de.renew.ioontology.exporting.ExportFormat<Drawing>[] formats = newFormat.canExport(path);
        ExportFormat[] result = new ExportFormat[formats.length];
        for (int i = 0; i < formats.length; i++) {
            if (formats[i] instanceof de.renew.ioontology.exporting.ExportFormatMulti<Drawing> multiFormat) {
                result[i] = mapMultiFormat(multiFormat);
            } else {
                result[i] = mapSingleFormat(formats[i]);
            }
        }
        return result;
    }



    private static ExportFormat mapSingleFormat(
        de.renew.ioontology.exporting.ExportFormat<Drawing> newFormat)
    {
        return new ExportFormat() {

            @Override
            public String formatName() {
                return newFormat.formatName();
            }

            @Override
            public javax.swing.filechooser.FileFilter fileFilter() {
                return new javax.swing.filechooser.FileFilter() {

                    @Override
                    public boolean accept(File f) {
                        return newFormat.fileFilter().accept(f);
                    }

                    @Override
                    public String getDescription() {
                        return newFormat.fileFilter().getDescription();
                    }
                };
            }

            @Override
            public File export(Drawing drawing, File file) throws Exception {
                return newFormat.export(drawing, file);
            }

            @Override
            public File export(Drawing[] drawings, File file) throws Exception {
                return newFormat.export(drawings, file);
            }

            @Override
            public File[] exportAll(Drawing[] drawings, File[] files) throws Exception {
                return newFormat.exportAll(drawings, files);
            }

            @Override
            public boolean canExportNto1() {
                return newFormat.canExportNto1();
            }

            @Override
            public ExportFormat[] canExport(File path) {
                return canExportFile(path, newFormat);
            }

            @Override
            public boolean canExportDrawing(Drawing drawing) {
                return newFormat.canExportObject(drawing);
            }

            @Override
            public int getShortCut() {
                return newFormat.getShortCut();
            }

            @Override
            public int getModifier() {
                return newFormat.getModifier();
            }

            @Override
            public boolean forceGivenName() {
                return newFormat.forceGivenName();
            }
        };
    }

    private static ExportFormatMulti mapMultiFormat(
        de.renew.ioontology.exporting.ExportFormatMulti<Drawing> newFormat)
    {
        return new ExportFormatMulti() {

            @Override
            public void addExportFormat(ExportFormat format) {
                newFormat.addExportFormat(mapExportFormat(format));
            }

            @Override
            public void removeExportFormat(ExportFormat format) {
                newFormat.removeExportFormat(mapExportFormat(format));
            }

            @Override
            public ExportFormat[] allExportFormats() {
                return newFormat.getExportFormats().stream()
                    .map(ExportFormatMapper::mapExportFormat).toArray(ExportFormat[]::new);
            }

            @Override
            public String formatName() {
                return newFormat.formatName();
            }

            @Override
            public javax.swing.filechooser.FileFilter fileFilter() {
                return new javax.swing.filechooser.FileFilter() {

                    @Override
                    public boolean accept(File f) {
                        return newFormat.fileFilter().accept(f);
                    }

                    @Override
                    public String getDescription() {
                        return newFormat.fileFilter().getDescription();
                    }
                };
            }

            @Override
            public File export(Drawing drawing, File file) throws Exception {
                return newFormat.export(drawing, file);
            }

            @Override
            public File export(Drawing[] drawings, File file) throws Exception {
                return newFormat.export(drawings, file);
            }

            @Override
            public File[] exportAll(Drawing[] drawings, File[] files) throws Exception {
                return newFormat.exportAll(drawings, files);
            }

            @Override
            public boolean canExportNto1() {
                return newFormat.canExportNto1();
            }

            @Override
            public ExportFormat[] canExport(File path) {
                return canExportFile(path, newFormat);
            }

            @Override
            public boolean canExportDrawing(Drawing drawing) {
                return newFormat.canExportObject(drawing);
            }

            @Override
            public int getShortCut() {
                return newFormat.getShortCut();
            }

            @Override
            public int getModifier() {
                return newFormat.getModifier();
            }

            @Override
            public boolean forceGivenName() {
                return newFormat.forceGivenName();
            }
        };
    }

    private static class InternalFileFilter extends javax.swing.filechooser.FileFilter
        implements FileFilter
    {
        private final Predicate<File> _acceptFunction;
        private String _description;

        public InternalFileFilter(javax.swing.filechooser.FileFilter fileFilter) {
            _acceptFunction = fileFilter::accept;
            this._description = fileFilter.getDescription();
        }

        @Override
        public boolean accept(File file) {
            return _acceptFunction.test(file);
        }

        @Override
        public String getDescription() {
            return _description;
        }

        @Override
        public void setDescription(String description) {
            _description = description;
        }
    }

}
