package CH.ifa.draw.standard;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

import de.renew.draw.storables.ontology.Drawing;
import de.renew.draw.storables.ontology.DrawingListener;
import de.renew.draw.ui.api.EditorApi;
import de.renew.draw.ui.ontology.DrawingView;

public class StandardDrawingLookup {

    public static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(StandardDrawingLookup.class);

    private DrawingView _previousView;
    private DrawingView _currentView;
    private final Map<Drawing, StandardDrawingViewContainer> _openedDrawings = new HashMap<>();
    private final Vector<DrawingListener> _drawingListeners = new Vector<>();

    public DrawingView getPreviousView() {
        return _previousView;
    }

    /**
     * Set the currently selected {@link DrawingView}
     *
     * @param currentView
     */
    public void setCurrentView(DrawingView currentView) {

        LOGGER.debug("Setting current view to " + currentView.drawing().getName());

        this._previousView = this._currentView;
        this._currentView = currentView;
    }

    /**
     * @return the currently focused {@link DrawingView}
     */
    public DrawingView getCurrentView() {
        if (_currentView != null) {
            return _currentView;
        }
        return EditorApi.getNullDrawingView();
    }

    /**
     * Returns the String identifier of the view container wrapping this drawing in the gui.
     *
     * @param drawing
     * @return A String object identifying a view container in the gui
     */
    public StandardDrawingViewContainer getViewContainerForDrawing(Drawing drawing) {

        try {
            return _openedDrawings.get(drawing);
        } catch (Exception e) {
            LOGGER.error("Tried to retrieve a view container for a drawing that isn't opened.");
        }
        return null;
    }

    /**
     * Add a {@link Drawing} and it's view container to the set of currently tracked drawings,
     * or replaces the associated view container of a {@link Drawing}.
     *
     * @param drawing
     * @param container
     */
    public void addViewContainer(Drawing drawing, StandardDrawingViewContainer container) {
        _openedDrawings.put(drawing, container);
        drawingAdded(drawing);
    }

    /**
     * Removes the {@link Drawing} and it's associated view container from the set of tracked entities.
     *
     * @param drawing
     */
    public void removeViewContainer(Drawing drawing) {
        _openedDrawings.remove(drawing);
        drawingReleased(drawing);
    }


    /**
     * Check whether a {@link Drawing} saved in a {@link File} is already loaded.
     *
     * @param file A saved {@link Drawing} file.
     * @return If the file is opened the respective drawing, null otherwise
     */
    public Drawing getLoadedDrawingForFile(File file) {
        for (Map.Entry<Drawing, StandardDrawingViewContainer> entry : _openedDrawings.entrySet()) {
            File filename = entry.getKey().getFilename();
            if (filename != null && filename.getAbsoluteFile().equals(file.getAbsoluteFile())) {
                return entry.getKey();
            }
        }
        return null;
    }


    /**
     * Get all currently known drawings as an {@link Enumeration}.
     *
     * @return All open drawings
     */
    public Enumeration<Drawing> getAllDrawings() {

        Vector<Drawing> vector = new Vector<>(_openedDrawings.keySet());
        return vector.elements();
    }


    /**
     * Removes all currently known drawings.
     * Users will be asked if they want to save changed drawings and may cancel this operation.
     */
    public void closeAllDrawings() {

        LOGGER.debug("Closing all opened drawings.");

        ArrayList<Drawing> allDrawings = Collections.list(getAllDrawings());
        // sort by descending name to close instances before templates
        allDrawings.sort((d1, d2) -> d2.getName().compareTo(d1.getName()));

        for (Drawing drawingEntry : allDrawings) {
            StandardDrawingViewContainer container = _openedDrawings.get(drawingEntry);
            // drawing might have been closed concurrently
            if (container != null) {
                Drawing drawing = container.getView().drawing();
                container.setVisible(false);

                // If container is still visible, operation was canceled by user.
                if (!container.isVisible()) {

                    _openedDrawings.remove(drawing);
                    drawingReleased(drawing);
                }
            }
        }
    }

    /**
     * Adds drawing listener to the list of drawing listeners
     *
     * @param listener the listener to add
     */
    public void addDrawingListener(DrawingListener listener) {
        _drawingListeners.add(listener);
    }

    /**
     * Removes drawing listener from the list of drawing listeners.
     *
     * @param listener the listener to remove
     */
    public void removeDrawingListener(DrawingListener listener) {
        _drawingListeners.remove(listener);
    }

    /**
     * Inform listeners that a drawing was added
     * @param drawing the added drawing
     */
    private void drawingAdded(Drawing drawing) {
        _drawingListeners.forEach(listener -> listener.drawingAdded(drawing));
    }

    /**
     * Informs listeners that a drawing was released and removes it from
     * stored views.
     * @param drawing the released drawing
     */
    private void drawingReleased(Drawing drawing) {
        _drawingListeners.forEach(listener -> listener.drawingReleased(drawing));

        if (_previousView != null && _previousView.drawing().equals(drawing)) {
            _previousView = EditorApi.getNullDrawingView();
        }
        if (_currentView != null && _currentView.drawing().equals(drawing)) {
            _currentView = _previousView;
            _previousView = EditorApi.getNullDrawingView();
        }

    }
}
