package CH.ifa.draw.util;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileFilter;

import bibliothek.gui.dock.common.event.CVetoClosingEvent;
import bibliothek.gui.dock.common.event.CVetoClosingListener;
import bibliothek.gui.dock.common.intern.CDockable;

import CH.ifa.draw.DrawPlugin;
import CH.ifa.draw.IOHelper;
import CH.ifa.draw.application.NewDrawingDialog;
import CH.ifa.draw.framework.DrawingTypeManager;
import CH.ifa.draw.io.DrawingFileHelper;
import CH.ifa.draw.standard.StandardDrawingEditor;
import CH.ifa.draw.standard.StandardDrawingLookup;
import CH.ifa.draw.standard.StandardDrawingViewContainer;
import de.renew.draw.storables.ontology.Drawing;
import de.renew.ioontology.ExtensionFileFilter;
import de.renew.util.StringUtil;
import de.renew.windowmanagement.Workbench;


/**
 * This class provides dialogs and convenience methods for saving and loading drawings.
 * @deprecated This class is not to be used externally and will later be hidden.
 */
@Deprecated(since = "5.0", forRemoval = true)
public class DrawingIOManager implements CVetoClosingListener {
    /**
     * Logger for the class DrawingIOManager.
     * Only a single logger instance exists at any given time. If a logger for DrawingIOManager
     * already exists it is returned or else a new instance is created.
     */
    public static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(DrawingIOManager.class);

    private final StandardDrawingEditor _editor;
    private final Workbench _workbench;
    private boolean _isClosingOfDrawingSuccessful;

    /**
     * Constructs a drawing manager to provide dialogs and methods
     * for user interactions related to drawing management.
     *
     * @param editor the StandardDrawingEditor, which will be used to open, draw or load files
     * @param workbench the Workbench the DrawingIOManager will be created with
     */
    public DrawingIOManager(StandardDrawingEditor editor, Workbench workbench) {
        _editor = editor;
        _workbench = workbench;
        this._isClosingOfDrawingSuccessful = true;

        _workbench.registerVetoClosingListener(this);
    }

    /**
     * Creates a new drawing view with a new empty drawing of choosable
     * type. The user is presented with all known drawing types.
     *
     * @return the created drawing. Returns <code>null</code>, if the
     * dialog was cancelled by the user.
     **/
    public Drawing promptChooseNew() {
        DrawingTypeManager dtm = DrawingTypeManager.getInstance();
        Map<String, ExtensionFileFilter> drawingTypes = dtm.getDrawingTypes();
        Collection<String> typeNames = drawingTypes.keySet();
        String[] types = typeNames.toArray(new String[0]);
        String[] descriptions = new String[types.length];
        for (int i = 0; i < types.length; i++) {
            descriptions[i] = drawingTypes.get(types[i]).getDescription();
        }
        NewDrawingDialog dialog = new NewDrawingDialog(_workbench.getMainFrame(), descriptions, 0);
        int result = dialog.showDialog();
        if (result >= 0 && result < types.length) {
            Drawing drawing = DrawingTypeManager.getDrawingFromName(types[result]);
            _editor.openDrawing(drawing);
            return drawing;
        }
        return null;
    }

    /**
     * Updates the name of a drawing.
     *
     * @param drawing the drawing whose name will be updated
     * @param filename the updated filename
     */
    public void updateName(Drawing drawing, File filename) {
        StandardDrawingViewContainer container =
            _editor.getDrawingLookup().getViewContainerForDrawing(drawing);
        try {
            filename = filename.getCanonicalFile();
        } catch (IOException e) {
            LOGGER.error(e);
        }
        String nameOnly = StringUtil.getFilename(filename.getName());
        container.setTitleText(nameOnly);

        drawing.setName(nameOnly);
        drawing.setFilename(filename);
    }


    /**
     * Saves the given drawing to its file. If the drawing's file is not
     * known, the user is asked to supply a file name. In this case, the
     * drawing's name can change.
     *
     * @param drawing the drawing to save.
     * @return <code>true</code>,  if the drawing has been saved.<br>
     * <code>false</code>, if the action was cancelled for
     * some reason.
     * {@link #saveDrawingAs(Drawing)}
     **/
    public boolean saveDrawing(Drawing drawing) {
        File filename = drawing.getFilename();
        if (filename == null || !drawing.getDefaultFileFilter().accept(filename)) {
            if (!promptSaveAs(drawing)) {
                return false;
            }
            filename = drawing.getFilename();
        }
        saveDrawing(drawing, filename);
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(StandardDrawingLookup.class.getName() + ": saved drawing: " + filename);
        }
        Objects.requireNonNull(DrawPlugin.getCurrent())
            .updateRecentlySavedList(filename.getAbsolutePath());
        return true;
    }

    @Override
    public void closing(CVetoClosingEvent cVetoClosingEvent) {

        Iterator<CDockable> it = cVetoClosingEvent.iterator();

        while (it.hasNext() && !cVetoClosingEvent.isCanceled()) {
            CDockable dockable = it.next();
            if (dockable instanceof StandardDrawingViewContainer container) {

                Drawing drawing = container.getView().drawing();

                if (drawing.isModified()) {

                    container.toFront();

                    if (!handleModifiedDrawing(drawing)) {
                        cVetoClosingEvent.cancel();
                        this._isClosingOfDrawingSuccessful = !cVetoClosingEvent.isCanceled();
                    } else {
                        this._isClosingOfDrawingSuccessful = true;
                    }
                }
            }
        }
    }

    /**
     * Gets result of a closing operation of a drawing.
     *
     * @return {@code true} if the closing operation was successful, {@code false} otherwise.
     */
    public boolean getClosingResult() {
        return this._isClosingOfDrawingSuccessful;
    }

    /**
     * Handles the closing of a drawing.
     *
     * @param drawing the drawing to be closed
     * @return false if the user manually canceled the closing operation
     */
    public boolean handleModifiedDrawing(Drawing drawing) {

        Object[] options = { "Save now", "Close / Do not save", "Cancel" };
        int answer = JOptionPane.showOptionDialog(
            _workbench.getMainFrame(),
            "Renew: " + "The drawing \"" + drawing.getName() + "\"" + " you are about to close"
                + " has been modified." + "\n What do you want to do?",
            "Save drawing?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options,
            options[0]);

        switch (answer) {
            case 0:
                // User wants to save. Return the success of the
                // save operation, because he can still choose cancel.
                saveDrawing(drawing);
                break;
            case 1:
                // User wants to continue without save.
                break;
            case 2:
                // User wants to cancel.
                return false;
            case JOptionPane.CLOSED_OPTION:
                // User pressed escape or closed the window (probably wants to cancel)
                return false;
            default:
                assert false : "JOptionPane returned unexpected result: " + answer;
        }

        return true;
    }

    @Override
    public void closed(CVetoClosingEvent cVetoClosingEvent) {

    }

    /**
     * Shows a file dialog and opens a drawing.
     *
     * @param ff the file filter for the file dialog.
     */
    public void promptOpen(FileFilter ff) {
        File[] files = getIOHelper().getLoadPath(null, ff, true);
        if (files != null) {
            for (int i = 0; i < files.length; i++) {
                if (ff instanceof ExtensionFileFilter) {
                    files[i] =
                        DrawingFileHelper.checkAndAddExtension(files[i], (ExtensionFileFilter) ff);
                }
                _editor.openOrLoadDrawing(files[i]);
            }
        }
    }

    /**
     * Displays a prompt to import from URL.
     */
    public void promptOpenURL() {
        String header = """
            Type in URL:
            Accepted protocols are: http, file, jar.
            If not specified I will try to use http.
            Examples:
            http://my.server.tld/referencenet.rnw.
            file:///my/path/to/nets/net.rnw.
            jar:file:///my/path/to/jar/the.jar!/path/to/file/net.rnw""";
        String in = JOptionPane.showInputDialog(DrawPlugin.getGui().getFrame(), header);
        if (in != null) {
            // this is maybe to restrictive but rather be on the safe side
            if (!in.startsWith("jar:") && !in.startsWith("file://")) {
                if (!in.startsWith("http://")) {
                    in = "http://" + in; // if none of the ones above, try http
                }
            }

            // logger.debug(in);
            try {
                URL url = new URL(in);
                LOGGER.debug(url);
                getIOHelper().loadAndOpenDrawings(url);
            } catch (Exception e) {
                LOGGER.error(e.getMessage(), e);
            }
        }
    }

    private IOHelper getIOHelper() {
        return Objects.requireNonNull(DrawPlugin.getCurrent()).getIOHelper();
    }

    /**
     * Shows a file dialog and inserts the chosen drawing into the current
     * drawing.
     **/
    public void promptInsert() {
        ExtensionFileFilter ff = _editor.drawing().getDefaultFileFilter();
        File file = getIOHelper().getLoadPath(null, createFileFilter(ff));
        file = DrawingFileHelper.checkAndAddExtension(file, ff);
        if (file != null) {
            _editor.loadAndInsertDrawing(file);
        }
    }

    /**
     * Saves the given drawing to a file whose name is queried from the
     * user. The drawing's name can change during this process.
     *
     * @param drawing the drawing to save.
     * {@link #promptSaveAs}
     **/
    public void saveDrawingAs(Drawing drawing) {
        File filename = drawing.getFilename();
        if (!promptSaveAs(drawing)) {
            _editor.showStatus("Could not save File: " + filename);
        } else {
            filename = drawing.getFilename();
            saveDrawing(drawing, filename);
        }
    }

    /**
     * Saves the given drawing to the given file. There is no user
     * interaction.
     *
     * @param drawing  the drawing to save.
     * @param filename the name of the file to write to.
     **/
    public void saveDrawing(Drawing drawing, File filename) {
        String text = " drawing " + drawing.getName() + " as " + filename;
        _editor.showStatus("Saving" + text + "...");
        DrawingFileHelper.saveDrawing(drawing, filename, _editor);
        _editor.showStatus("Saved" + text + ".");
        _editor.autosaveManager.renameDrawing(drawing);
    }

    /**
     * Shows a file dialog for saving the given drawing. The default file
     * filter of the drawing is used. The chosen file name is checked to
     * end with one of the file filter's allowed extensions.
     * <p>
     * The chosen file name is not returned. Instead, if a file name is
     * determined, the drawing's name (and all related editor information)
     * is set accordingly.
     * </p>
     *
     * @param drawing the drawing that will be saved
     * @return <code>true</code>,  if a file name was chosen and set.<br>
     * <code>false</code>, if the action was cancelled for
     * some reason.
     **/
    public boolean promptSaveAs(Drawing drawing) {
        ExtensionFileFilter ff = drawing.getDefaultFileFilter();
        File file = drawing.getFilename();
        if (file == null) {
            file = new File(getIOHelper().getLastPath(), drawing.getName());
            file = DrawingFileHelper.checkAndAddExtension(file, ff);
        }
        _editor.toolDone();


        // get the save path to know where to store the drawing
        file = getIOHelper().getSavePath(file, ff);

        if (file == null) {
            return false;
        }
        file = DrawingFileHelper.checkAndAddExtension(file, ff);

        // If the file exists already, ask for permission to overwrite
        if (file.exists()) {
            // new Swing confirm dialog
            int answer = JOptionPane.showConfirmDialog(
                DrawPlugin.getGui().getFrame(),
                "The file \"" + file + "\"" + " does already exist." + "\nDo you want do proceed?",
                "Renew: Confirm overwrite.", JOptionPane.YES_NO_OPTION);
            if (answer >= 1) {
                return false;
            }
        }


        // Set the new file name for the drawing
        updateName(drawing, file);


        // OK: Make sure to create another backup.
        drawing.setBackupStatus(false);
        return true;
    }

    private static FileFilter createFileFilter(ExtensionFileFilter filter) {
        if (filter == null) {
            return null;
        }

        return new FileFilter() {

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

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