package de.renew.windowmanagement;

import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import javax.swing.AbstractButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JPanel;
import javax.swing.WindowConstants;

import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.MultipleCDockable;
import bibliothek.gui.dock.common.MultipleCDockableFactory;
import bibliothek.gui.dock.common.MultipleCDockableLayout;
import bibliothek.gui.dock.common.SingleCDockable;
import bibliothek.gui.dock.common.event.CControlListener;
import bibliothek.gui.dock.common.event.CFocusListener;
import bibliothek.gui.dock.common.event.CKeyboardListener;
import bibliothek.gui.dock.common.event.CVetoClosingListener;
import bibliothek.util.Path;


/**
 * This class is the port to access to a workbench.
 * It manages different listeners and functions to a Workbench.
 */
public class WorkbenchProxy implements Workbench {

    private boolean _fwithGui = false;
    private WorkbenchImpl _proxy;
    private HashMap<String, MultipleCDockableFactory> _fFactoryMap = new HashMap();

    private LinkedHashSet<JPanel> _fPanelSet = new LinkedHashSet<>();
    private ArrayList<MenuBarEntry> _fmenu = new ArrayList<>();
    private HashMap<DefaultSingleCDockable, Path> _fViewMap = new HashMap();
    private final ArrayList<RenewShutdownHook> _fShutdownHooks = new ArrayList<RenewShutdownHook>();

    // Registered Listeners. They are kept as weak references, so plugins don't have to deregister.
    private LinkedHashSet<WeakReference> _fControlListeners = new LinkedHashSet<>();
    private LinkedHashSet<WeakReference> _fFocusListeners = new LinkedHashSet<>();
    private LinkedHashSet<WeakReference> _fVetoListeners = new LinkedHashSet<>();
    private LinkedHashSet<WeakReference> _fKeyboardListeners = new LinkedHashSet<>();

    @Override
    public JFrame getMainFrame() {
        if (_fwithGui) {
            return ensureProxyInitialized().getMainFrame();
        }

        return null;
    }

    @Override
    public void showStatus(String string) {
        if (_fwithGui) {
            ensureProxyInitialized().showStatus(string);
        }
    }

    @Override
    public void addStaticPanel(JPanel panel) {
        if (_fwithGui) {
            ensureProxyInitialized().addStaticPanel(panel);
        } else {
            _fPanelSet.add(panel);
        }
    }

    @Override
    public void addEditorWindow(MultipleCDockableLayout editor, String factoryID) {
        if (_fwithGui) {
            ensureProxyInitialized().addEditorWindow(editor, factoryID);
        }
    }

    @Override
    public void newEditorWindow(String factoryID) {
        if (_fwithGui) {
            ensureProxyInitialized().newEditorWindow(factoryID);
        }
    }

    @Override
    public void registerControlListener(CControlListener listener) {
        if (_fwithGui) {
            ensureProxyInitialized().registerControlListener(listener);
        } else {
            _fControlListeners.add(new WeakReference(listener));
        }
    }

    @Override
    public void registerEditorFactory(MultipleCDockableFactory factory, String id) {
        if (_fwithGui) {
            ensureProxyInitialized().registerEditorFactory(factory, id);
        } else {
            _fFactoryMap.put(id, factory);
        }

    }

    @Override
    public void registerMenu(JMenu menu) {

        _fmenu.add(new MenuBarEntry(menu, RenewMenuBar.MISC_SECTION));
        if (_fwithGui) {
            ensureProxyInitialized().registerMenu(menu);
        }
    }

    @Override
    public void registerMenu(JMenu menu, int index) throws UnknownMenuSectionException {

        _fmenu.add(new MenuBarEntry(menu, index));
        if (_fwithGui) {
            ensureProxyInitialized().registerMenu(menu, index);
        }
    }

    @Override
    public void removeMenu(JMenu menu) {
        _fmenu.remove(new MenuBarEntry(menu, 0));

        //invokeLater is needed, because ensureProxyFreed()
        //would be called too late resulting in fwithGui still being
        //true while the proxy was already destroyed
        EventQueue.invokeLater(() -> {
            if (_fwithGui) {
                ensureProxyInitialized().removeMenu(menu);
            }
        });
    }

    @Override
    public void close(MultipleCDockable dockable) {
        if (_fwithGui) {
            ensureProxyInitialized().close(dockable);
        }
    }

    @Override
    public void close(SingleCDockable dockable) {
        //invokeLater is needed, because ensureProxyFreed()
        //would be called too late resulting in fwithGui still being
        //true while the proxy was already destroyed
        EventQueue.invokeLater(() -> {
            if (_fwithGui) {
                ensureProxyInitialized().close(dockable);
            }
        });
    }

    @Override
    public void addViewWindow(DefaultSingleCDockable dockable, Path path) {
        if (_fwithGui) {
            ensureProxyInitialized().addViewWindow(dockable, path);
        } else {
            _fViewMap.put(dockable, path);
        }
    }

    /**
     * Request closing of this application.
     * All shutdown hooks will be informed.
     * Any hook objecting to closing the application may intervene.
     */
    protected synchronized void requestClose() {

        assert EventQueue.isDispatchThread();

        if (!_fwithGui) {
            return;
        }

        for (RenewShutdownHook hook : _fShutdownHooks) {

            if (hook.canClose()) {
                hook.exit();
            } else {
                // Some hook objected and the process is canceled.
                // Currently no rollback is done.
                _fwithGui = true;
                return;
            }

        }

        _proxy.destroy();
        ensureProxyFreed();
    }

    @Override
    public void registerShutdownHook(RenewShutdownHook hook) {
        _fShutdownHooks.add(hook);
    }

    private synchronized WorkbenchImpl ensureProxyInitialized() {

        if (_proxy == null) {

            RenewMenuBar bar = new RenewMenuBar();
            for (MenuBarEntry entry : _fmenu) {

                try {
                    bar.addMenu(entry._menu, entry._section);
                } catch (UnknownMenuSectionException e) {
                    e.printStackTrace();
                }
            }

            JFrame frame = new JFrame();
            addWindowListeners(frame);

            _proxy = new WorkbenchImpl(bar, frame);

            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {

                    for (Map.Entry<String, MultipleCDockableFactory> entryset : _fFactoryMap
                        .entrySet()) {
                        _proxy.registerEditorFactory(entryset.getValue(), entryset.getKey());
                    }

                    for (WeakReference listener : _fControlListeners) {
                        _proxy.registerControlListener((CControlListener) listener.get());
                    }

                    for (WeakReference listener : _fFocusListeners) {
                        _proxy.registerFocusListener((CFocusListener) listener.get());
                    }

                    for (WeakReference listener : _fVetoListeners) {
                        _proxy.registerVetoClosingListener((CVetoClosingListener) listener.get());
                    }

                    for (WeakReference listener : _fKeyboardListeners) {
                        _proxy.registerKeyboardListener((CKeyboardListener) listener.get());
                    }

                    for (JPanel panel : _fPanelSet) {
                        _proxy.addStaticPanel(panel);
                    }

                    for (Map.Entry<DefaultSingleCDockable, Path> view : _fViewMap.entrySet()) {
                        _proxy.addViewWindow(view.getKey(), view.getValue());
                    }
                }
            });

        }

        return _proxy;
    }

    private void ensureProxyFreed() {
        _proxy = null;
        _fwithGui = false;
    }


    @Override
    public void openGui() {
        _fwithGui = true;
        ensureProxyInitialized();
    }

    @Override
    public void registerFocusListener(CFocusListener listener) {
        if (_fwithGui) {
            ensureProxyInitialized().registerFocusListener(listener);
        } else {
            _fFocusListeners.add(new WeakReference<CFocusListener>(listener));
        }
    }

    @Override
    public void registerVetoClosingListener(CVetoClosingListener listener) {
        if (_fwithGui) {
            ensureProxyInitialized().registerVetoClosingListener(listener);
        } else {
            _fVetoListeners.add(new WeakReference(listener));
        }
    }

    @Override
    public void registerKeyboardListener(CKeyboardListener listener) {
        if (_fwithGui) {
            ensureProxyInitialized().registerKeyboardListener(listener);
        } else {
            _fKeyboardListeners.add(new WeakReference(listener));
        }
    }

    /**
     * Registers the listeners for the main window
     *
     * @param frame the frame where you want to add the listener to.
     */
    protected void addWindowListeners(JFrame frame) {
        frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent event) {
                EventQueue.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        requestClose();
                    }
                });
            }

            public void windowDeactivated(WindowEvent event) {}
        });
    }

    /*
     * Data container for toolbar entries
     */
    private class ToolbarEntry {
        protected String _name;
        protected Map<AbstractButton, Component> _buttonBoxMap;
        protected boolean _immediatelyVisible;

        ToolbarEntry(
            String name, Map<AbstractButton, Component> buttonBoxMap, boolean immediatelyVisible)
        {
            _name = name;
            _buttonBoxMap = buttonBoxMap;
            _immediatelyVisible = immediatelyVisible;
        }

        @Override
        public boolean equals(Object otherEntry) {

            if (otherEntry instanceof ToolbarEntry) {
                return ((ToolbarEntry) otherEntry)._name == this._name;
            }
            return false;
        }
    }

    private class MenuBarEntry {

        protected JMenu _menu;
        protected int _section;

        MenuBarEntry(JMenu menu, int section) {
            _menu = menu;
            _section = section;
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof MenuBarEntry) {
                return ((MenuBarEntry) o)._menu == _menu;
            }
            return false;
        }
    }
}
