package CH.ifa.draw.standard;

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetListener;
import java.awt.event.KeyEvent;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import javax.swing.AbstractButton;
import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;

import org.apache.log4j.Logger;

import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.event.CControlListener;
import bibliothek.gui.dock.common.event.CFocusListener;
import bibliothek.gui.dock.common.intern.CDockable;

import CH.ifa.draw.DrawPlugin;
import CH.ifa.draw.IOHelper;
import CH.ifa.draw.application.AbstractFileDragDropListener;
import CH.ifa.draw.contrib.DiamondFigure;
import CH.ifa.draw.contrib.PolygonTool;
import CH.ifa.draw.contrib.TriangleFigure;
import CH.ifa.draw.figures.ConnectedTextTool;
import CH.ifa.draw.figures.ElbowConnection;
import CH.ifa.draw.figures.EllipseFigure;
import CH.ifa.draw.figures.ImageFigure;
import CH.ifa.draw.figures.ImageFigureCreationTool;
import CH.ifa.draw.figures.LineConnection;
import CH.ifa.draw.figures.LineFigure;
import CH.ifa.draw.figures.PieFigure;
import CH.ifa.draw.figures.RectangleFigure;
import CH.ifa.draw.figures.RoundRectangleFigure;
import CH.ifa.draw.figures.ScribbleTool;
import CH.ifa.draw.figures.TargetTool;
import CH.ifa.draw.figures.TextFigure;
import CH.ifa.draw.figures.TextTool;
import CH.ifa.draw.gui.ToolButtonPanel;
import CH.ifa.draw.gui.ToolButtonScrollPane;
import CH.ifa.draw.io.DrawingFileHelper;
import CH.ifa.draw.util.AutosaveManager;
import CH.ifa.draw.util.AutosaveSaver;
import CH.ifa.draw.util.CommandMenu;
import CH.ifa.draw.util.CommandMenuItem;
import CH.ifa.draw.util.PaletteListener;
import de.renew.draw.storables.ontology.Drawing;
import de.renew.draw.storables.ontology.Figure;
import de.renew.draw.storables.ontology.FigureEnumeration;
import de.renew.draw.storables.ontology.TextHolder;
import de.renew.draw.ui.api.EditorApi;
import de.renew.draw.ui.ontology.DrawingEditor;
import de.renew.draw.ui.ontology.DrawingView;
import de.renew.draw.ui.ontology.DrawingViewContainer;
import de.renew.draw.ui.ontology.StatusDisplayer;
import de.renew.draw.ui.ontology.Tool;
import de.renew.draw.ui.ontology.Toolbar;
import de.renew.draw.ui.ontology.undoredo.UndoRedoManager;
import de.renew.windowmanagement.RenewShutdownHook;
import de.renew.windowmanagement.UnknownMenuSectionException;
import de.renew.windowmanagement.Workbench;
import de.renew.windowmanagement.WorkbenchImpl;


public class StandardDrawingEditor implements DrawingEditor, PaletteListener, CControlListener,
    CFocusListener, AutosaveSaver, StatusDisplayer
{

    public static final Logger LOGGER = Logger.getLogger(StandardDrawingEditor.class);

    /**
     * The image resource path
     */
    private static final String DRAW_PATH = "CH/ifa/draw/";

    /**
     * The image folder path
     */
    public static final String IMAGES = DRAW_PATH + "images/";

    /**
     * The minimum time interval that has to elapse before a drawing is saved
     * automatically.
     */
    private static final int AUTOSAVE_INTERVAL = 120000;

    /**
     * The autosave manager that is associated to this DrawingEditor.
     */
    public final AutosaveManager autosaveManager = new AutosaveManager(this, AUTOSAVE_INTERVAL);

    /**
     * The id of the factory used to create {@link DrawingView}s.
     */
    public static final String FACTORY_ID = "StandardDrawingViewFactory";

    private final Map<Class<?>, String> _factoryIdLookup = new LinkedHashMap<>();

    private final StandardDrawingLookup _lookup;
    private ToolButton _currentToolButton;
    private final ToolButton _defaultToolButton;
    private final CH.ifa.draw.framework.UndoRedoManager _undoRedoManager;
    protected final Workbench fWorkbench;
    private StandardDrawingViewFactory _factory;
    private final List<SelectionChangeListener> _selectionChangeListeners = new ArrayList<>();
    private ToolButton _textToolButton;
    private ToolButton _conntextToolButton;
    private boolean _stickyTools = false;
    private final ToolButtonScrollPane _toolbar;

    public StandardDrawingEditor(Workbench workbench) {

        _defaultToolButton =
            createToolButton(IMAGES + "SEL", "Selection Tool", createSelectionTool());
        _currentToolButton = _defaultToolButton;
        _undoRedoManager = new CH.ifa.draw.framework.UndoRedoManager(this);
        _lookup = new StandardDrawingLookup();
        fWorkbench = workbench;

        fWorkbench.registerControlListener(this);
        fWorkbench.registerFocusListener(this);
        _factory = new StandardDrawingViewFactory(this);
        fWorkbench.registerEditorFactory(_factory, FACTORY_ID);

        _toolbar = new ToolButtonScrollPane(new ToolButtonPanel());
        _toolbar.setAutoscrolls(true);
        _toolbar.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        _toolbar.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        fWorkbench.addViewWindow(
            new DefaultSingleCDockable("Tools", _toolbar), WorkbenchImpl.UPPER_LEFT_GROUP);
        createToolPanel();
        fWorkbench.registerShutdownHook(new RenewShutdownHook() {
            @Override
            public boolean canClose() {
                _toolbar.saveLayout("Last Layout");
                _toolbar.storeLayoutsToDisk();
                return true;
            }

            @Override
            public void exit() {}
        });

        DropTargetListener dragDropListener = new AbstractFileDragDropListener() {
            @Override
            protected void handleFilesDrawing(final File[] files, Point loc) {
                for (File file : files) {
                    if (!file.isDirectory()) {
                        openOrLoadDrawing(file);
                    }
                }
            }

            @Override
            protected void handleFilesImage(File[] files, Point loc) {
                if (files.length >= 1) {
                    Vector<Figure> imagesToAdd = new Vector<>();
                    DrawingView view = view();
                    for (File file : files) {
                        Image image = ImageFigureCreationTool
                            .createImage(file.getAbsolutePath(), fWorkbench.getMainFrame());

                        ImageFigure imageFigure =
                            new ImageFigure(image, file.getAbsolutePath(), loc);
                        Rectangle displayBox = imageFigure.displayBox();
                        imageFigure.moveBy(-(displayBox.width / 2), -(displayBox.height / 2));
                        imagesToAdd.add(imageFigure);
                    }
                    view.addAll(imagesToAdd);
                    view.checkDamage();
                }
            }
        };
        new DropTarget(fWorkbench.getMainFrame(), dragDropListener);

    }

    /**
     * Register a {@link SelectionChangeListener} to be notified when the selection has changed.
     *
     * @param listener
     */
    public void registerSelectionChangeListener(SelectionChangeListener listener) {
        _selectionChangeListeners.add(listener);
    }

    /**
     * Creates the standard selection tool.
     *
     * @return
     */
    protected Tool createSelectionTool() {
        return new SelectionTool(this);
    }

    private void createToolPanel() {
        final LinkedHashSet<AbstractButton> set = new LinkedHashSet<>();
        ToolButton button;

        set.add(_defaultToolButton.button());

        Tool tool = new CreationTool(this, new RectangleFigure());
        button = createToolButton(IMAGES + "RECT", "Rectangle Tool", tool);
        set.add(button.button());
        button.addAdditionalComponent(((CreationTool) tool).initialAttributesMenu());

        tool = new CreationTool(this, new RoundRectangleFigure());
        button = createToolButton(IMAGES + "RRECT", "Round Rectangle Tool", tool);
        set.add(button.button());
        button.addAdditionalComponent(((CreationTool) tool).initialAttributesMenu());

        tool = new CreationTool(this, new EllipseFigure());
        button = createToolButton(IMAGES + "ELLIPSE", "Ellipse Tool", tool);
        set.add(button.button());
        button.addAdditionalComponent(((CreationTool) tool).initialAttributesMenu());

        tool = new CreationTool(this, new PieFigure());
        button = createToolButton(IMAGES + "PIE", "Elliptical Arc/Pie Tool", tool);
        set.add(button.button());
        button.addAdditionalComponent(((CreationTool) tool).initialAttributesMenu());

        tool = new CreationTool(this, new DiamondFigure());
        button = createToolButton(IMAGES + "DIAMOND", "Diamond Tool", tool);
        set.add(button.button());
        button.addAdditionalComponent(((CreationTool) tool).initialAttributesMenu());

        tool = new CreationTool(this, new TriangleFigure());
        button = createToolButton(IMAGES + "TRIANGLE", "Triangle Tool", tool);
        set.add(button.button());
        button.addAdditionalComponent(((CreationTool) tool).initialAttributesMenu());

        tool = new CreationTool(this, new LineFigure());
        button = createToolButton(IMAGES + "LINE", "Line Tool", tool);
        set.add(button.button());
        button.addAdditionalComponent(((CreationTool) tool).initialAttributesMenu());

        tool = new ConnectionTool(this, new LineConnection());
        button = createToolButton(IMAGES + "CONN", "Connection Tool", tool);
        set.add(button.button());
        button.addAdditionalComponent(new JMenuItem("Test"));
        button.setAlternativeMode(ToolButton.POPUP_MENU);

        tool = new ConnectionTool(this, new ElbowConnection());
        button = createToolButton(IMAGES + "OCONN", "Elbow Connection Tool", tool);
        set.add(button.button());

        tool = new ScribbleTool(this);
        button = createToolButton(IMAGES + "SCRIBBL", "Scribble Tool", tool);
        set.add(button.button());
        // Add option menu for scribble tool
        button.addAdditionalComponent(new ScribbleOptionPanel((ScribbleTool) tool));

        tool = new PolygonTool(this);
        button = createToolButton(IMAGES + "POLYGON", "Polygon Tool", tool);
        set.add(button.button());

        tool = new ImageFigureCreationTool(this, fWorkbench.getMainFrame());
        button = createToolButton(IMAGES + "IMAGE", "Image Tool", tool);
        set.add(button.button());

        TextFigure prototype = new TextFigure(false);
        prototype.setAlignment(TextFigure.LEFT);
        tool = new TextTool(this, prototype);
        _textToolButton = createToolButton(IMAGES + "TEXT", "Text Tool", tool);
        set.add(_textToolButton.button());

        prototype = new TextFigure();
        prototype.setAlignment(TextFigure.CENTER);
        tool = new ConnectedTextTool(this, prototype);
        _conntextToolButton = createToolButton(IMAGES + "ATEXT", "Connected Text Tool", tool);
        set.add(_conntextToolButton.button());

        tool = new TargetTool(this);
        button = createToolButton(IMAGES + "TARGETTEXT", "Target Tool", tool);
        set.add(button.button());

        String description = "<html><b>Standard Tools</b><br>" + "The standard drawing tools<br>"
            + "used to create basic shapes.</html>";
        _toolbar.addTools(set, "Standard Tools", description);
    }

    /**
     * Returns the current toolbar.
     *
     * @return
     */
    public Toolbar getToolbar() {
        return _toolbar;
    }

    /**
     * Creates a tool button with the given image, tool, and text
     * @deprecated this is not to be used, please use {@link StandardDrawingEditor#createToolButton(String, String, Tool, String)}
     *
     * @param iconName the icon name for the tool button
     * @param toolName the name of the tool button
     * @param tool     the tool associated with the tool button
     * @return the created tool button
     */
    @Deprecated
    public ToolButton createToolButton(String iconName, String toolName, Tool tool) {
        return new ToolButton(this, iconName, toolName, tool);
    }

    /**
     * Creates a tool button with the given image, tool, and text
     *
     * @param iconName the icon name for the tool button
     * @param toolName the name of the tool button
     * @param tool     the tool associated with the tool button
     * @param pluginName the name of the plugin the Tool belongs to
     * @return the created tool button
     */
    public ToolButton createToolButton(
        String iconName, String toolName, Tool tool, String pluginName)
    {
        return new ToolButton(this, iconName, toolName, tool, pluginName);
    }

    /**
     * create a button for with the given icons, name and tool
     * @deprecated this is not to be used, please use {@link StandardDrawingEditor#createToolButton(Icon, Icon, String, Tool, String)}
     *
     * @param icon the icon for the button
     * @param selectedIcon the icon for the pressed button
     * @param toolName the name of the tool
     * @param tool the tool to be triggered with this button
     */
    @Deprecated
    public ToolButton createToolButton(Icon icon, Icon selectedIcon, String toolName, Tool tool) {
        // logger.debug("Creating a ToolButton with icon " + iconName + ", toolName " + toolName);
        LOGGER.debug("the delegator is " + this);
        return new ToolButton(this, icon, selectedIcon, toolName, tool);
    }

    /**
     * create a button for with the given icons, name and tool
     * @param icon the icon for the button
     * @param selectedIcon the icon for the pressed button
     * @param toolName the name of the tool
     * @param tool the tool to be triggered with this button
     * @param pluginName the name of the plugin the tool belongs to
     */
    public ToolButton createToolButton(
        Icon icon, Icon selectedIcon, String toolName, Tool tool, String pluginName)
    {
        // logger.debug("Creating a ToolButton with icon " + iconName + ", toolName " + toolName);
        LOGGER.debug("the delegator is " + this);
        return new ToolButton(this, icon, selectedIcon, toolName, tool, pluginName);
    }

    /**
     * Set the {@link StandardDrawingViewFactory} which is responsible for creating
     * new view containers for drawings.
     *
     * @param factory
     */
    public void setDrawingViewFactory(StandardDrawingViewFactory factory, String factoryID) {

        _factory = factory;
        // We have to keep the old factory registered, in case we have already used it
        // to create drawings.
        fWorkbench.registerEditorFactory(factory, factoryID);
    }

    @Override
    public DrawingView view() {
        DrawingView view = _lookup.getCurrentView();
        if (view != null) {
            return view;
        } else {
            return EditorApi.getNullDrawingView();
        }
    }

    @Override
    public DrawingView previousView() {
        return _lookup.getPreviousView();
    }

    /**
     * @deprecated This method is not to be used externally. Please use
     * the method {@link EditorApi#getCurrentDrawing()} instead.
     */
    @Deprecated
    @Override
    public Drawing drawing() {
        return view().drawing();
    }

    @Override
    public Tool tool() {
        return _currentToolButton.tool();
    }

    @Override
    public Tool defaultTool() {
        return _defaultToolButton.tool();
    }

    /**
     * Set if tools are sticky.
     * Usually we always want sticky tools, but some tools, like the image creation tool, require non-sticky tools to be set.
     *
     * @param sticky
     */
    @Override
    public void setStickyTools(boolean sticky) {
        _stickyTools = sticky;
    }

    /**
     * The concept of sticky tools was not really intuitive, so we just return true
     * here and use sticky tools all the time.
     *
     * @return true
     */
    @Override
    public boolean isStickyTools() {
        return _stickyTools;
    }

    @Override
    public Image getIconImage() {

        Image icon = fWorkbench.getMainFrame().getIconImage();

        assert icon != null : "Icon of mainframe may not be null.";

        return icon;
    }

    @Override
    public void dispatchEvent(KeyEvent evt) {
        fWorkbench.getMainFrame().dispatchEvent(evt);

    }

    /**
     * Gets tool button dependent of the type of the given figure.
     *
     * @param figure the figure for which the tool button is searched.
     * @return the tool button for the figure.
     */
    public ToolButton toolButtonForTextFigure(TextHolder figure) {
        if (!(figure instanceof TextFigure) || ((TextFigure) figure).parent() == null) {
            return _textToolButton;
        } else {
            return _conntextToolButton;
        }
    }

    @Override
    public void toolDone() {

        if (isStickyTools() && tool() != null) {
            tool().deactivate();
            tool().activate();
        } else if (_defaultToolButton != null) {
            setToolButton(_defaultToolButton);
        }


    }

    @Override
    public void selectionChanged(DrawingView view) {
        menuStateChanged();

        for (SelectionChangeListener listener : _selectionChangeListeners) {
            listener.selectionStateChanged();
        }
    }

    /**
     * Call if some action has occurred after which a
     * {@link CommandMenuItem} might be disabled.
     */
    @Override
    public void menuStateChanged() {

        JFrame frame = fWorkbench.getMainFrame();

        if (frame != null) {
            JMenuBar mb = frame.getJMenuBar();
            int count = mb.getMenuCount();

            for (int i = 0; i < count; i++) {
                JMenu menu = (mb.getMenu(i));

                if (menu instanceof CommandMenu) {
                    ((CommandMenu) menu).checkEnabled();
                    ((CommandMenu) menu).updateCommandText();
                }
            }
        }

    }

    @Override
    public void showStatus(String string) {
        fWorkbench.showStatus(string);
    }

    @Override
    public void prepareUndoSnapshot() {
        Drawing currentDrawing = drawing();
        _undoRedoManager.prepareUndoSnapshot(currentDrawing);
    }

    @Override
    public void commitUndoSnapshot() {
        Drawing currentDrawing = drawing();
        _undoRedoManager.commitUndoSnapshot(currentDrawing);
    }

    @Override
    public void prepareAccumulatedUndoSnapshot() {
        Drawing currentDrawing = drawing();
        _undoRedoManager.prepareAccumulatedUndoSnapshot(currentDrawing);
    }

    @Override
    public void triggerAccumulatedUndoSnapshot() {
        Drawing currentDrawing = drawing();
        _undoRedoManager.triggerAccumulatedUndoSnapshot(currentDrawing);
    }

    @Override
    public UndoRedoManager getUndoRedoManager() {
        return _undoRedoManager;
    }

    /**
     * This is not used anymore. Instead, use {@link StandardDrawingEditor#focusGained}.
     *
     * @param viewContainer
     */
    @Override
    public void drawingViewContainerActivated(DrawingViewContainer viewContainer) {
        // DrawingViewContainers are not used anymore.
    }

    /**
     * This is not used anymore. Instead, use {@link StandardDrawingEditor#closed} or
     * {@link StandardDrawingEditor#removed} if removeOnClose is not set to true.
     *
     * @param viewContainer
     */
    @Override
    public void drawingViewContainerClosing(DrawingViewContainer viewContainer) {
        // DrawingViewContainers are not used anymore.
    }

    /**
     * This is not used anymore. Instead, use
     * {@link StandardDrawingLookup#setCurrentView}.
     *
     * @param dvc
     */
    @Override
    public void setCurrentDrawing(DrawingViewContainer dvc) {
        // DrawingViewContainers are not used anymore.
    }

    /**
     * Reacts to user selections on the palette panel. Sticky tools are not used
     * anymore, so double click does nothing different.
     *
     * @param button
     * @param doubleClick
     */
    @Override
    public void paletteUserSelected(ToolButton button, boolean doubleClick) {
        setToolButton(button);

        _stickyTools = doubleClick;
        showStatus("Selected tool: " + button.name());
    }

    /**
     * Deactivate the old tool button and activate the new one.
     *
     * @param button
     */
    private void setToolButton(ToolButton button) {
        assert button != null;

        //_currentToolButton.button().setSelected(false);
        ToolButton.ToolButtonModel model =
            (ToolButton.ToolButtonModel) _currentToolButton.button().getModel();
        model.setTrulySelected(false);
        _currentToolButton.tool().deactivate();
        _currentToolButton = button;

        model = (ToolButton.ToolButtonModel) button.button().getModel();
        model.setTrulySelected(true);
        button.tool().activate();
    }

    @Override
    public void paletteUserOver(ToolButton button, boolean inside) {
        // We could show the name of the hovered tool on the status line.
        // But that is kind of pointless, since the buttons already have tooltips.
        // Also, it would erase any other more important information displayed there.
    }

    @Override
    public void added(CControl cControl, CDockable cDockable) {
        if (cDockable instanceof StandardDrawingViewContainer container) {

            LOGGER.debug("Dockable of type StandardDrawingViewContainer was added.");

            Drawing drawing = container.getView().drawing();
            _lookup.addViewContainer(drawing, container);
            _undoRedoManager.newUndoHistory(drawing);
        }
    }

    @Override
    public void removed(CControl cControl, CDockable cDockable) {
        if (cDockable instanceof StandardDrawingViewContainer container) {
            LOGGER.debug("Dockable of type StandardDrawingViewContainer was removed.");

            Drawing drawing = container.getView().drawing();
            _lookup.removeViewContainer(drawing);
            noUndoHistoryFor(drawing);
            if (_lookup.getCurrentView() == container.getView()) {
                _lookup.setCurrentView(EditorApi.getNullDrawingView());
                selectionChanged(null);
            }
        }
    }

    @Override
    public void opened(CControl cControl, CDockable cDockable) {
        // Use added instead
    }

    @Override
    public void closed(CControl cControl, CDockable cDockable) {
        // Use removed instead
    }

    @Override
    public void focusGained(CDockable cDockable) {
        if (cDockable instanceof StandardDrawingViewContainer container) {
            LOGGER.debug(container.getView().drawing().getName() + " gained focus.");
            _lookup.setCurrentView(container.getView());
            selectionChanged(container.getView());
        }
    }

    @Override
    public void focusLost(CDockable cDockable) {
        // We want to retain the current view, if a tool panel has been selected.
        // So unless another View has gained focus, do nothing
    }

    /**
     * Open the given {@link Drawing} by creating a new
     * {@link StandardDrawingViewContainer}. If {@link Drawing} is already open,
     * instead focus the view container.
     *
     * @param drawing
     * @return the same drawing given
     */
    public Drawing openDrawing(Drawing drawing) {

        assert drawing != null : "Drawing may not be null.";

        StandardDrawingViewContainer container = _lookup.getViewContainerForDrawing(drawing);
        if (container != null) {
            // focus view if not already focused
            if (!_lookup.getCurrentView().drawing().equals(drawing)) {
                // case: drawing is already open, but not focused
                container.toFront();
            }
        } else {
            // case: drawing has not been opened yet
            createNewViewContainerForDrawing(drawing);
            container = _lookup.getViewContainerForDrawing(drawing);
            if (container != null) {
                container.toFront();
            }

        }

        return drawing;
    }

    /**
     * Calls the {@link Workbench} to create a {@link StandardDrawingViewContainer} via a previously registered factory.
     * Once the drawing is opened, this class will be informed via {@link #added(CControl, CDockable)}.
     *
     * @param drawing The drawing to be opened
     */
    protected void createNewViewContainerForDrawing(Drawing drawing) {
        // search for the most specific type, use the last registered when specificity is equal
        String id = _factoryIdLookup.entrySet().stream().filter(e -> e.getKey().isInstance(drawing))
            .reduce((e1, e2) -> e2.getKey().isAssignableFrom(e1.getKey()) ? e1 : e2)
            .map(Map.Entry::getValue).orElse(FACTORY_ID);
        fWorkbench.addEditorWindow(new DrawingLayoutWrapper(drawing), id);
    }

    @Override
    public void openOrLoadDrawing(File file, DrawingView view) {
        Drawing drawing = _lookup.getLoadedDrawingForFile(file);

        if (drawing != null) {
            openDrawing(drawing);
        } else {
            getIOHelper().loadAndOpenDrawings(file);
        }

        Runnable r = () -> {
            Drawing drawing1 = _lookup.getLoadedDrawingForFile(file);
            StandardDrawingViewContainer container = _lookup.getViewContainerForDrawing(drawing1);
            container.setLocationsAside(_lookup.getViewContainerForDrawing(view.drawing()));
        };
        EventQueue.invokeLater(r);

    }

    /**
     * Opens the drawing from the given file. If it is not in the drawing list it
     * will be loaded.
     *
     * @param file the file of the drawing.
     */
    public void openOrLoadDrawing(File file) {
        Drawing drawing = _lookup.getLoadedDrawingForFile(file);
        if (drawing != null) {
            openDrawing(drawing);
        } else {
            getIOHelper().loadAndOpenDrawings(file);
        }
    }

    /**
     * Closes the specified {@link Drawing}.
     *
     * @param drawing
     */
    public void closeDrawing(Drawing drawing) {
        StandardDrawingViewContainer container = _lookup.getViewContainerForDrawing(drawing);
        // Drawing might already be closed by a different thread
        if (container == null)
            return;
        //If the container is still visible, the operation was canceled by the user.
        try {
            if (EventQueue.isDispatchThread()) {
                fWorkbench.close(container);
            } else {
                EventQueue.invokeAndWait(() -> fWorkbench.close(container));
            }
        } catch (InterruptedException | InvocationTargetException e) {
            LOGGER.error("Unable to close drawing due to exception: " + e);
        }
        if (!container.isVisible()) {
            //Prevent premature removal of drawing when user canceled the closing process.
            _lookup.removeViewContainer(drawing);
        }
    }

    /**
     * Creates a new empty {@link Drawing}. The specific subtype of the
     * {@link Drawing} is defined by the registered factory.
     *
     * @return
     */
    public Drawing newDrawing() {
        Drawing drawing = _factory.create().drawing();
        return openDrawing(drawing);
    }

    /**
     * Saves the given drawing to the given autosave file. The drawing's dirty flag
     * is not cleared.
     *
     * @param drawing the drawing to save.
     * @param file    the name of the autosave file.
     * @param loc     the location of the drawing's view window; it will be saved to the
     *                file, too.
     * @param size    width and height of the drawing's view window; they will be saved
     *                to the file, too.
     * @throws IOException if an I/O-failure occurs.
     **/
    public void saveAutosaveFile(Drawing drawing, File file, Point loc, Dimension size)
        throws IOException
    {
        DrawingFileHelper.saveAsStorableOutput(drawing, file, loc, size, false);
    }

    /**
     * Prohibits the management of undo and redo snapshots for the given drawing.
     **/
    protected void noUndoHistoryFor(Drawing drawing) {
        _undoRedoManager.removeUndoHistory(drawing);
    }

    public void addMenu(JMenu menu) {
        fWorkbench.registerMenu(menu);
    }

    public void addMenu(JMenu menu, int index) {

        try {
            fWorkbench.registerMenu(menu, index);
        } catch (UnknownMenuSectionException e) {
            LOGGER.error(e.getMessage() + e);
        }
    }

    public void removeMenu(JMenu menu) {
        fWorkbench.removeMenu(menu);
    }

    /**
     * Returns the DrawingView for the given drawing.
     * @param drawing The drawing whose view is returned
     * @return the DrawingView, or null if the drawing is not loaded
     */
    public DrawingView getView(Drawing drawing) {
        StandardDrawingViewContainer container = _lookup.getViewContainerForDrawing(drawing);
        if (container == null) {
            // Returning a NullDrawingView also seems reasonable.
            // However, many methods only check for the result to (not) be null.
            // Check usages of DrawApplication.getView(Drawing drawing)
            return null;
        }
        DrawingView view = container.getView();

        assert view != null;

        return view;
    }

    /**
     * Get the lookup managing all currently known drawings and their respective
     * visual representations.
     *
     * @return
     */
    public StandardDrawingLookup getDrawingLookup() {
        return _lookup;
    }

    private IOHelper getIOHelper() {
        return DrawPlugin.getCurrentDrawPlugin().getIOHelper();
    }

    /**
     * Opens the text edit for the given figure.
     *
     * @param textFigure the figure which should be edited.
     */
    public void doTextEdit(TextHolder textFigure) {
        doTextEdit(textFigure, 0, 0, 0, 0);
    }

    /**
     * Edit a given {@link TextFigure} with the appropriate {@link TextTool}.
     *
     * @param textFigure  The figure to edit
     * @param startLine   if {@code startLine > 0 && startColumn > 0}, set caret position
     * @param startColumn
     * @param endLine     if {@code endLine > startLine or (startLine == endLine) && (startColumn <
     *                    endColumn)}, select text in between
     * @param endColumn
     */
    public void doTextEdit(
        TextHolder textFigure, int startLine, int startColumn, int endLine, int endColumn)
    {

        ToolButton tb = toolButtonForTextFigure(textFigure);
        paletteUserSelected(tb, false);
        TextTool tt = (TextTool) tb.tool();
        tt.beginEdit(textFigure);

        if ((startLine < endLine) || ((startLine == endLine) && (startColumn < endColumn))) {
            tt.select(startLine, startColumn, endLine, endColumn);
        } else {
            if (startLine > 0 && startColumn > 0) {
                tt.setCaretPosition(startLine, startColumn);
            }
        }

    }

    /**
     * Loads a drawing from the given file and inserts all its figures into
     * the current drawing.
     *
     * @param file the file name where to retrieve the drawing.
     **/
    public synchronized void loadAndInsertDrawing(File file) {

        Drawing existingDrawing = drawing();
        DrawingView existingView = view();
        Drawing newDrawing = DrawingFileHelper.loadDrawing(file, null);
        if (newDrawing != null) {
            FigureEnumeration figures = newDrawing.figures();
            existingView.clearSelection();
            while (figures.hasMoreElements()) {
                Figure fig = figures.nextFigure();
                existingDrawing.add(fig);
                existingView.addToSelection(fig);
            }
            existingView.checkDamage();
        }

    }

    /**
     * Prints the drawing.
     */
    public void print() {

        _currentToolButton.tool().deactivate();
        PrinterJob printerJob = PrinterJob.getPrinterJob();
        printerJob.setPrintable(view());
        if (printerJob.printDialog()) {
            try {
                printerJob.print();
            } catch (PrinterException e) {
                LOGGER.debug(e.getMessage(), e);
                JOptionPane.showMessageDialog(
                    fWorkbench.getMainFrame(),
                    "Printing of this Drawing failed.\n" + "Reason: " + e.getMessage(),
                    "Printer Error", JOptionPane.ERROR_MESSAGE);
            }
        }
        _currentToolButton.tool().activate();
    }

    /**
     * Close the currently focused drawing.
     */
    public void closeCurrentDrawing() {
        Drawing drawing = drawing();
        StandardDrawingViewContainer container = _lookup.getViewContainerForDrawing(drawing);
        fWorkbench.close(container);
    }

    @Override
    public void registerDrawingViewFactoryID(Class key, String id) {
        _factoryIdLookup.put(key, id);
    }
}
