package de.renew.draw.ui.impl.menus;

import java.awt.Component;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;

import CH.ifa.draw.util.CommandMenu;
import de.renew.draw.storables.impl.drawings.DrawingImportFormatHolder;
import de.renew.draw.storables.ontology.Drawing;
import de.renew.draw.ui.api.ApplicationApi;
import de.renew.draw.ui.api.CommandApi;
import de.renew.draw.ui.api.DialogApi;
import de.renew.draw.ui.api.EditorApi;
import de.renew.draw.ui.ontology.AbstractCommand;
import de.renew.ioontology.ExtensionFileFilter;
import de.renew.ioontology.FileFilter;
import de.renew.ioontology.MultiExtensionFileFilter;
import de.renew.ioontology.MultiExtensionFileFilterImpl;
import de.renew.ioontology.importing.ImportFormat;
import de.renew.ioontology.importing.ImportFormatListener;
import de.renew.ioontology.importing.ImportFormatMulti;

/**
 * Manages the import format menu.
 */
public class ImportMenuHolder {
    /**
     * Creates log4j Logger for this class to represent logging information.
     */
    private static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(ImportMenuHolder.class);

    private final CommandMenu _importMenu;

    /**
     * Constructs and sets up the ImportMenuHolder.
     */
    public ImportMenuHolder() {
        _importMenu = new CommandMenu("Import");
        AbstractCommand command =
            CommandApi.createCommand("Import (any type)", this::executeImportAll);
        _importMenu.add(command);

        DrawingImportFormatHolder.getInstance()
            .addImportFormatListener(createImportFormatListener());
    }

    /**
     * Returns the associated import menu.
     *
     * @return The import menu
     */
    public JMenu getImportMenu() {
        return _importMenu;
    }

    private List<ImportFormat<Drawing>> getImportFormats() {
        return DrawingImportFormatHolder.getInstance().getImportFormats();
    }

    private FileFilter getImportFileFilter() {
        List<ImportFormat<Drawing>> formats = getImportFormats();
        MultiExtensionFileFilter fileFilter = new MultiExtensionFileFilterImpl("All");
        for (ImportFormat<Drawing> format : formats) {
            if (format.fileFilter() instanceof MultiExtensionFileFilter multi) {
                fileFilter.addAll(multi.getFileFilters());
            } else if (format.fileFilter() instanceof ExtensionFileFilter ext) {
                fileFilter.add(ext);
            }
        }

        return fileFilter;
    }

    private void executeImportAll() {
        ApplicationApi.showStatus("import...");
        File[] files = DialogApi.showSelectFilesDialog(getImportFileFilter());

        if (files != null) {
            for (File currentFile : files) {
                List<ImportFormat<Drawing>> list = new LinkedList<>();
                ImportFormat<Drawing> impFormat = null;
                for (int posFormat = 0; posFormat < getImportFormats().size(); posFormat++) {
                    try {
                        if (getImportFormats().get(posFormat)
                            .canImport(currentFile.toURI().toURL())) {
                            list.add(getImportFormats().get(posFormat));
                        }
                    } catch (MalformedURLException e) {
                        LOGGER.error(e.getMessage(), e);
                        ApplicationApi.showStatus(e.toString());
                    }
                }
                if (list.size() == 1) {
                    impFormat = list.get(0);
                } else if (list.size() > 1) {
                    Object choice = JOptionPane.showInputDialog(
                        null, "Choose", "ExportFormats", JOptionPane.OK_CANCEL_OPTION, null,
                        list.toArray(), list.get(0));
                    if (choice != null) {
                        impFormat = (ImportFormat<Drawing>) choice;
                    }
                }
                if (impFormat != null) {
                    try {
                        loadDrawings(new URL[] { currentFile.toURI().toURL() }, impFormat);
                    } catch (MalformedURLException e) {
                        LOGGER.error(e.getMessage(), e);
                        ApplicationApi.showStatus(e.toString());
                    }
                } else {
                    ApplicationApi.showStatus("no import Format");
                }

            }
        }
    }

    /**
     * Loads an array of files with the help of format.
     * @param files the array of files to be imported
     * @param format the format
     */
    private void loadDrawings(URL[] files, ImportFormat<Drawing> format) {
        try {
            if (files != null && files.length > 0) {
                List<Drawing> drawings = format.importFiles(files);
                if (drawings != null) {
                    for (Drawing d : drawings) {
                        EditorApi.openDrawing(d);
                        ApplicationApi.showStatus("import");
                    }
                }
            }
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
            ApplicationApi.showStatus(e.toString());
        }
    }


    /**
     * Constructs a menu item for this importFormat in importMenu.
     * @param importFormat the ImportFormat for the new menu item
     */
    private void buildImportFormat(ImportFormat<Drawing> importFormat, CommandMenu parent) {
        ImportFormatCommand command;
        if (importFormat instanceof ImportFormatMulti<Drawing> multi) {
            List<ImportFormat<Drawing>> formats = multi.getImportFormats();
            CommandMenu menu = new CommandMenu(multi.formatName());
            for (ImportFormat<Drawing> format : formats) {
                buildImportFormat(format, menu);
            }
            parent.add(menu);
        } else {
            command = generateImportCommand(importFormat);
            parent.add(command);
        }
    }

    /**
     * Returns a FormatCommand.
     * @param format the ID of the ImportFormat
     * @return FormatCommand the menu item
     */
    private ImportFormatCommand generateImportCommand(ImportFormat<Drawing> format) {
        return new ImportFormatCommand(format) {
            @Override
            public void execute() {
                ApplicationApi.showStatus("import ...");
                File[] files = DialogApi.showSelectFilesDialog(format().fileFilter());
                URL[] paths = new URL[files.length];
                for (int pos = 0; pos < paths.length; pos++) {
                    try {
                        paths[pos] = files[pos].toURI().toURL();
                    } catch (MalformedURLException e) {
                        LOGGER.error(e.getMessage(), e);
                        ApplicationApi.showStatus(e.toString());
                    }
                }
                loadDrawings(paths, format());
                EditorApi.toolDone();
                ApplicationApi.showStatus("import");
            }
        };
    }

    /**
     * Adds the given import Format to the import menus.
     *
     * @param importFormat the import format to be added to the import menus
     */
    void addImportFormat(ImportFormat<Drawing> importFormat) {
        LOGGER.debug(getClass() + ": adding import format " + importFormat);
        buildImportFormat(importFormat, _importMenu);
    }

    /**
     * Removes the given import Format to the import menus.
     *
     * @param importFormat the import format to be removed from the import menus
     */
    void removeImportFormat(ImportFormat<Drawing> importFormat) {
        Component[] ele = _importMenu.getMenuComponents();
        for (Component component : ele) {
            if (component instanceof JMenuItem item) {
                if (item.getText().equals(importFormat.formatName())) {
                    _importMenu.remove(item);
                }
            }
        }
    }

    private static abstract class ImportFormatCommand extends AbstractCommand {
        private final ImportFormat<Drawing> _format;

        /**
         * Constructs Import format command with a given import format.
         *
         * @param format the given import format
         */
        private ImportFormatCommand(ImportFormat<Drawing> format) {
            super(format.formatName());
            _format = format;
        }

        // Methods

        /**
         * Returns the import format.
         *
         * @return the import format
         */
        public ImportFormat<Drawing> format() {
            return _format;
        }
    }

    private ImportFormatListener<Drawing> createImportFormatListener() {
        return new ImportFormatListener<>() {
            @Override
            public void importFormatAdded(ImportFormat<Drawing> addedFormat) {
                addImportFormat(addedFormat);
            }

            @Override
            public void importFormatRemoved(ImportFormat<Drawing> removedFormat) {
                removeImportFormat(removedFormat);
            }
        };
    }
}