package de.renew.gui;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Vector;
import javax.swing.AbstractButton;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;

import bibliothek.gui.dock.common.CControl;
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.figures.ConnectedTextTool;
import CH.ifa.draw.framework.FigureWithID;
import CH.ifa.draw.standard.ConnectionTool;
import CH.ifa.draw.standard.StandardDrawingEditor;
import CH.ifa.draw.standard.StandardDrawingLookup;
import CH.ifa.draw.standard.StandardDrawingView;
import CH.ifa.draw.standard.StandardDrawingViewContainer;
import CH.ifa.draw.standard.ToolButton;
import de.renew.draw.storables.ontology.Drawing;
import de.renew.draw.storables.ontology.Figure;
import de.renew.draw.storables.ontology.TextHolder;
import de.renew.draw.ui.ontology.DrawingView;
import de.renew.draw.ui.ontology.Tool;
import de.renew.gui.tool.CPNSelectionTool;
import de.renew.gui.tool.CPNTextTool;
import de.renew.gui.tool.DefaultSizeCreationTool;
import de.renew.gui.tool.VirtualPlaceFigureCreationTool;
import de.renew.gui.tool.VirtualTransitionFigureCreationTool;
import de.renew.gui.tool.ZoomAreaTool;
import de.renew.gui.tool.ZoomInTool;
import de.renew.gui.tool.ZoomOutTool;
import de.renew.gui.tool.ZoomResetTool;
import de.renew.windowmanagement.Workbench;

import static de.renew.gui.CPNApplication.CPNIMAGES;


public class CPNDrawingEditor extends StandardDrawingEditor implements CVetoClosingListener {
    private static final Color TOKEN_BAG_COLOR = new Color(230, 230, 255);
    private static final Color INSTANCE_COLOR = new Color(200, 200, 255);
    public static final String CPN_FACTORY_ID = "CPNDrawingViewFactory";
    private ToolButton fInscrTB;
    private ToolButton fNameTB;
    private ToolButton fDeclTB;
    private ToolButton fCommTB;
    private ToolButton fAuxTB;
    private final CPNDrawingViewFactory fFactory;
    private final GuiPlugin fGuiPlugin;

    /**
     * Listeners for all Drawings created or closed in the editor.
     */
    private final transient Vector<DrawingListener> _fDrawingListeners;

    public CPNDrawingEditor(GuiPlugin guiPlugin, Workbench workbench) {
        super(workbench);
        fGuiPlugin = guiPlugin;

        fFactory = new CPNDrawingViewFactory(this);
        workbench.registerEditorFactory(fFactory, CPN_FACTORY_ID);
        registerDrawingViewFactoryID(CPNDrawing.class, CPN_FACTORY_ID);
        registerDrawingViewFactoryID(CPNInstanceDrawing.class, CPN_FACTORY_ID);
        registerDrawingViewFactoryID(TokenBagDrawing.class, CPN_FACTORY_ID);

        workbench.registerVetoClosingListener(this);
        createTools();
        createZoomTools();
        _fDrawingListeners = new Vector<>();
    }

    @Override
    public void added(CControl cControl, CDockable cDockable) {
        super.added(cControl, cDockable);

        if (cDockable instanceof StandardDrawingViewContainer container) {
            Drawing drawing = container.getView().drawing();

            if (drawing instanceof CPNDrawing cpnDrawing) {
                // make sure the drawing loader has been instantiated.
                ModeReplacement.getInstance().getDrawingLoader();
                LOGGER.debug("drawing loaded: " + drawing.getName());
                ModeReplacement.getInstance().getDrawingLoader().addDrawing(cpnDrawing);
            }
            if (drawing instanceof TokenBagDrawing) {
                noUndoHistoryFor(drawing);
            }
        }
    }

    @Override
    public void opened(CControl cControl, CDockable cDockable) {
        super.opened(cControl, cDockable);

        if (cDockable instanceof StandardDrawingViewContainer container) {
            Drawing drawing = container.getView().drawing();

            if (drawing instanceof InstanceDrawing instanceDrawing) {
                // If InstanceDrawing, open close to original net pattern drawing.
                StandardDrawingViewContainer netpattern =
                    getDrawingLookup().getViewContainerForDrawing(instanceDrawing.getCpnDrawing());
                container.setLocationsAside(netpattern);

                // transfer viewport size and position
                Dimension patternViewportSize =
                    netpattern.getScrollPane().getViewport().getViewSize();
                container.getScrollPane().getViewport().setViewSize(patternViewportSize);
                Point patternViewportPosition =
                    netpattern.getScrollPane().getViewport().getViewPosition();
                container.getScrollPane().getViewport().setViewPosition(patternViewportPosition);

                // transfer scaling factor
                double patternScalingFactor = netpattern.getView().getScalingFactor();
                container.getView().changeScalingFactor(patternScalingFactor);

                container.getView().setBackground(INSTANCE_COLOR);
                noUndoHistoryFor(drawing);
            }
        }
    }

    @Override
    public void removed(CControl cControl, CDockable cDockable) {
        super.removed(cControl, cDockable);

        if (cDockable instanceof StandardDrawingViewContainer container) {
            Drawing drawing = container.getView().drawing();

            if (drawing instanceof CPNDrawing cpnDrawing) {
                ModeReplacement.getInstance().getDrawingLoader().releaseDrawing(cpnDrawing);
            }

            container.clean();
        }
    }

    /**
     * Show the named net and select a figure indicated by the elementID.
     *
     * @param netName
     * @param elementID
     */
    public void showNetPatternDrawing(String netName, int elementID) {
        StandardDrawingLookup lookup = getDrawingLookup();
        Enumeration<Drawing> drawenumeration = lookup.getAllDrawings();

        CPNDrawing drawing = null;
        while ((drawenumeration.hasMoreElements()) && (drawing == null)) {
            Object next = drawenumeration.nextElement();
            if (next instanceof CPNDrawing cpnDrawing && cpnDrawing.getName().equals(netName)) {
                drawing = cpnDrawing;
            }
        }

        if (drawing == null) {
            drawing = ModeReplacement.getInstance().getDrawingLoader().getDrawing(netName);
        }

        if (drawing != null) {
            // bring drawing to front
            openDrawing(drawing);
            lookup.getViewContainerForDrawing(drawing).toFront();


            if (elementID != FigureWithID.NOID) {
                Figure elementFigure = (drawing).getFigureWithID(elementID);
                if (elementFigure != null) {
                    DrawingView elementView = lookup.getViewContainerForDrawing(drawing).getView();
                    elementView.clearSelection();
                    elementView.addToSelection(elementFigure);

                    // Redraw the newly selected elements.
                    elementView.repairDamage();
                }
            }
        }
    }


    /**
     * Open a {@link TokenBagDrawing} close to the given {@link InstanceDrawing}.
     *
     * @param tokenBagDrawing
     * @param instanceDrawing
     */
    public void openTokenBagDrawing(
        TokenBagDrawing tokenBagDrawing, InstanceDrawing instanceDrawing)
    {
        openDrawing(tokenBagDrawing);

        StandardDrawingViewContainer container =
            getDrawingLookup().getViewContainerForDrawing(tokenBagDrawing);
        StandardDrawingView view = container.getView();
        view.setBackground(TOKEN_BAG_COLOR);

        StandardDrawingViewContainer instancecontainer =
            getDrawingLookup().getViewContainerForDrawing(instanceDrawing);
        container.setLocationsAside(instancecontainer);
    }

    @Override
    public void closing(CVetoClosingEvent cVetoClosingEvent) {
        Iterator<CDockable> it = cVetoClosingEvent.iterator();

        while (it.hasNext() && !cVetoClosingEvent.isCanceled()) {
            CDockable dockable = it.next();
            if (dockable instanceof StandardDrawingViewContainer standardDrawingViewContainer) {
                Drawing drawing = standardDrawingViewContainer.getView().drawing();

                if (drawing instanceof CPNDrawing cpnDrawing) {
                    Enumeration<InstanceDrawing> dependentDrawings =
                        CPNInstanceDrawing.getDependentInstanceDrawings(cpnDrawing);

                    if (dependentDrawings != null) {
                        openDrawing(drawing);

                        int answer = JOptionPane.showConfirmDialog(
                            fWorkbench.getMainFrame(),
                            new String[] {
                                "The drawing \"" + drawing.getName() + "\" "
                                    + "you are about to close",
                                "is needed to display one " + "or more instance drawings." },
                            "Renew: Confirm Close", JOptionPane.OK_CANCEL_OPTION);

                        if (answer == JOptionPane.CANCEL_OPTION) {
                            cVetoClosingEvent.cancel();
                        } else {
                            while (dependentDrawings.hasMoreElements()) {
                                closeDrawing(dependentDrawings.nextElement());
                            }
                        }
                    }
                }
            }
        }
    }

    @Override
    public void closed(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();

                drawing.release();

                notifyDrawingListeners(drawing, false);
            }
        }
    }

    protected void createTools() {
        final LinkedHashMap<AbstractButton, Component> list = new LinkedHashMap<>();


        Tool tool = new DefaultSizeCreationTool(
            this, TransitionFigure::new, TransitionFigure.defaultDimension());
        list.put(
            createToolButton(loadImageIcon("TRANS"), null, "Transition Tool", tool).button(), null);

        if (fGuiPlugin.getProperties().getBoolProperty(GuiPlugin.VIRTUAL_TRANSITION)) {
            tool = new VirtualTransitionFigureCreationTool(this);
            list.put(
                createToolButton(loadImageIcon("VTRANS"), null, "Virtual Transition Tool", tool)
                    .button(),
                null);
        }

        tool = new DefaultSizeCreationTool(this, PlaceFigure::new, PlaceFigure.defaultDimension());

        list.put(createToolButton(loadImageIcon("PLACE"), null, "Place Tool", tool).button(), null);

        tool = new VirtualPlaceFigureCreationTool(this);
        list.put(
            createToolButton(loadImageIcon("VPLACE"), null, "Virtual Place Tool", tool).button(),
            null);

        tool = new ConnectionTool(this, ArcConnection.NormalArc);
        list.put(createToolButton(loadImageIcon("ARC"), null, "Arc Tool", tool).button(), null);

        tool = new ConnectionTool(this, ArcConnection.TestArc);
        list.put(createToolButton(IMAGES + "LINE", "Test Arc Tool", tool).button(), null);

        tool = new ConnectionTool(this, ArcConnection.ReserveArc);
        list.put(createToolButton(IMAGES + "CONN", "Reserve Arc Tool", tool).button(), null);

        tool = new ConnectionTool(this, DoubleArcConnection.DoubleArc);
        list.put(
            createToolButton(loadImageIcon("DARC"), null, "Flexible Arc Tool", tool).button(),
            null);

        tool = new CPNTextTool(this, CPNTextFigure.Inscription);


        // store the inscription text tool button
        // for immediate editing of errors:
        fInscrTB = createToolButton(loadImageIcon("INSCR"), null, "Inscription Tool", tool);
        list.put(fInscrTB.button(), null);

        tool = new ConnectedTextTool(this, CPNTextFigure.Name);
        fNameTB = createToolButton(loadImageIcon("NAME"), null, "Name Tool", tool);
        list.put(fNameTB.button(), null);

        tool = new CPNTextTool(this, new DeclarationFigure(), false);
        fDeclTB = createToolButton(loadImageIcon("DECL"), null, "Declaration Tool", tool);
        list.put(fDeclTB.button(), null);

        tool = new CPNTextTool(this, CPNTextFigure.Comm);
        fCommTB = createToolButton(loadImageIcon("COMM"), null, "Comment Tool", tool);
        list.put(fCommTB.button(), null);

        String description = "<html><b>Petri Net Tools</b><br>" + "Tools to draw P/T nets<br>"
            + "or similar net types.</html>";
        getToolbar().addTools(list.keySet(), "Petri Net Tools", description);
    }

    /**
     * Creates the buttons for the Zoom function.
     */
    private void createZoomTools() {
        final LinkedHashSet<AbstractButton> buttons = new LinkedHashSet<>();

        buttons.add(
            createToolButton(loadImageIcon("ZOOMIN"), null, "Zoom In Tool", new ZoomInTool(this))
                .button());
        buttons.add(
            createToolButton(loadImageIcon("ZOOMOUT"), null, "Zoom Out Tool", new ZoomOutTool(this))
                .button());
        buttons.add(
            createToolButton(
                loadImageIcon("ZOOMRESET"), null, "Zoom Reset Tool", new ZoomResetTool(this))
                .button());
        buttons.add(
            createToolButton(
                loadImageIcon("ZOOMAREA"), null, "Zoom Area Tool", new ZoomAreaTool(this))
                .button());

        String description =
            "<html><b>Zoom Tools</b><br>" + "Tools to zoom in the drawing editor.</html>";
        getToolbar().addTools(buttons, "Zoom Tools", description);

    }

    protected ImageIcon loadImageIcon(String iconName) {
        URL resource = this.getClass().getResource(CPNIMAGES + iconName + ".gif");
        return new ImageIcon(resource);
    }

    @Override
    protected Tool createSelectionTool() {
        return new CPNSelectionTool(this);
    }

    @Override
    public ToolButton toolButtonForTextFigure(TextHolder figure) {
        if (figure instanceof CPNTextFigure) {
            if (figure instanceof DeclarationFigure) {
                return fDeclTB;
            }
            switch (((CPNTextFigure) figure).getType()) {
                case CPNTextFigure.AUX:
                    if (fAuxTB != null) {
                        return fAuxTB;
                    } else {
                        return fInscrTB;
                    }
                case CPNTextFigure.INSCRIPTION:
                    return fInscrTB;
                case CPNTextFigure.NAME:
                    return fNameTB;
                case CPNTextFigure.COMM:
                    return fCommTB;
                default: {
                }
            }
        }
        return super.toolButtonForTextFigure(figure);
    }

    @Override
    public Drawing newDrawing() {
        Drawing drawing = fFactory.create().drawing();
        return openDrawing(drawing);
    }

    /**
     * Adds DrawingListener
     *
     * @param listener the added listener
     */
    public void addDrawingListener(DrawingListener listener) {
        _fDrawingListeners.addElement(listener);
    }


    /**
     * Removes DrawingClosedListener
     *
     * @param listener the removed listener
     */
    public void removeDrawingListener(DrawingListener listener) {
        _fDrawingListeners.remove(listener);
    }

    @Override
    public Drawing openDrawing(Drawing drawing) {
        notifyDrawingListeners(drawing, true);
        return super.openDrawing(drawing);
    }

    private void notifyDrawingListeners(Drawing drawing, boolean add) {
        for (int i = 0; i < _fDrawingListeners.size(); i++) {
            if (add) {
                _fDrawingListeners.elementAt(i).drawingOpened(new DrawingEvent(this, drawing));
            } else {
                _fDrawingListeners.elementAt(i).drawingClosed(new DrawingEvent(this, drawing));
            }
        }
    }
}