/* GrinvinWindow.java
 * =========================================================================
 * This file is part of the GrInvIn project - http://www.grinvin.org
 * 
 * Copyright (C) 2005-2008 Universiteit Gent
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * A copy of the GNU General Public License can be found in the file
 * LICENSE.txt provided with the source distribution of this program (see
 * the META-INF directory in the source jar). This license can also be
 * found on the GNU website at http://www.gnu.org/licenses/gpl.html.
 * 
 * If you did not receive a copy of the GNU General Public License along
 * with this program, contact the lead developer, or write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

package org.grinvin.main;

import be.ugent.caagt.swirl.SwirlUtilities;
import be.ugent.caagt.swirl.actions.ShowWindow;
import be.ugent.caagt.swirl.menus.MenuBuilder;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import javax.help.CSH;
import javax.swing.ActionMap;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.ListSelectionModel;
import javax.swing.WindowConstants;
import javax.swing.event.ChangeListener;

import org.grinvin.about.AboutDialog;
import org.grinvin.factories.FactoryManager;
import org.grinvin.worksheet.DefaultWorksheetWindowListModel;
import org.grinvin.gui.JTabbedPaneMouseListener;
import org.grinvin.gui.components.FactoryCellListComponent;
import org.grinvin.gui.components.GeneratorCellListComponent;
import org.grinvin.gui.components.GraphTreeComponent;
import org.grinvin.gui.components.InvariantTreeComponent;
import org.grinvin.gui.components.WorksheetListComponent;
import org.grinvin.gui.icons.SvgIconManager;
import org.grinvin.help.CSHAction;
import org.grinvin.help.FactoryListHelpManager;
import org.grinvin.help.GeneratorListHelpManager;
import org.grinvin.help.HelpManager;
import org.grinvin.help.InvariantTreeHelpManager;
import org.grinvin.help.TabbedPaneHelpManager;
import org.grinvin.help.actions.DisplayHelpAfterTracking;
import org.grinvin.help.actions.LaunchHelp;
import org.grinvin.help.actions.LaunchWebsite;
import org.grinvin.list.factories.DefaultGraphFactoryListModel;
import org.grinvin.list.generators.DefaultGraphGeneratorListModel;
import org.grinvin.list.DefaultWorksheetListModel;
import org.grinvin.list.HasURI;
import org.grinvin.list.factories.GraphFactoryListModel;
import org.grinvin.list.generators.GraphGeneratorListModel;
import org.grinvin.list.graphs.GraphTreeModel;
import org.grinvin.list.invariants.InvariantTreeModel;
import org.grinvin.list.WorksheetListModel;
import org.grinvin.list.actions.LoadInvariant;
import org.grinvin.list.actions.WorksheetRename;
import org.grinvin.main.actions.Exit;
import org.grinvin.main.actions.ExitWithoutSave;
import org.grinvin.main.actions.ImportGraphList;
import org.grinvin.main.actions.LoadWorkspace;
import org.grinvin.main.actions.NewWorksheet;
import org.grinvin.main.actions.NewWorkspace;
import org.grinvin.main.actions.SaveWorkspaceAs;
import org.grinvin.main.actions.SaveWorkspace;
import org.grinvin.main.actions.SaveWorkspaceToDirectory;
import org.grinvin.preferences.GrinvinPreferences;
import org.grinvin.workspace.Workspace;
import org.grinvin.workspace.WorkspaceListener;


/**
 * This class represents the GrInvIn main window.
 */
public class GrinvinWindow extends JFrame implements HasURI, WorkspaceListener {
    
    //
    private static final String MAIN_WINDOW_KEY = "org.grinvin.window.main";
    
    // TODO: split into multiple classes

    //
    private JTabbedPane tabs;
    
    //
    private JLabel locationLabel;
    
    //
    private JDialog about;
    
    //
    private URI uri;
    
    public enum GrinvinWindowComponentType {
        INVARIANTS("invariants"),
        FACTORIES("factories"),
        GENERATORS("generators"),
        WORKSHEETS("worksheetlist"),
        GRAPHLIST("graphlist");

        private String name;
        
        GrinvinWindowComponentType(String name) {
            this.name = name;
        }
        
        public String getName() {
            return name;
        }
    }
    
    /**
     * Creates a new instance of GrinvinWindow
     */
    public GrinvinWindow() {
        // TODO: determine what should go here and what should go in setupContents
        super();
        // window map
        this.windowMap = new HashMap<String,Window> ();
        
        // tabs map
        this.tabsMap = new HashMap<GrinvinWindowComponentType, JComponent>();
        this.reverseTabsMap = new HashMap<JComponent, GrinvinWindowComponentType>();

        // about window
        this.about = new AboutDialog();
    }
    
    /**
     * Setup the GUI contents of this window.
     */
    public void setupContents() {
        ResourceBundle bundle = ResourceBundle.getBundle("org.grinvin.main.resources");
        setTitle (bundle.getString("window.main.title"));
        setLayout(new BoxLayout(this.getContentPane(), BoxLayout.PAGE_AXIS));
        
        JPanel panel  = new JPanel();
        
        panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
        
        this.tabs = new JTabbedPane();
        
        // add logo
        ImageIcon icon = SvgIconManager.getInstance().getIcon("/org/grinvin/icons/grinvin.svg", 96);
        JLabel intro =  new JLabel(icon);
        intro.setAlignmentY(Component.TOP_ALIGNMENT);
        panel.add(intro);
        //tabs.addTab(BUNDLE.getString("tab.intro.title"), null, intro, BUNDLE.getString("tab.intro.tooltip"));
        //tabsMap.put("intro", intro);
        //reverseTabsMap.put(intro, "intro");
        
        //add invariants
        InvariantTreeComponent tree = new InvariantTreeComponent(InvariantTreeModel.getStandardTree());
        addComponent(tree, GrinvinWindowComponentType.INVARIANTS);
        
        //add factories
        FactoryCellListComponent factoryList = new FactoryCellListComponent(getStandardGraphFactories());
        addComponent(factoryList, GrinvinWindowComponentType.FACTORIES);
        
        //add generators
        GeneratorCellListComponent generatorList = new GeneratorCellListComponent(getStandardGraphGenerators());
        addComponent(generatorList, GrinvinWindowComponentType.GENERATORS);
        
        // add worksheets
        this.worksheetListModel = new DefaultWorksheetListModel(new DefaultWorksheetWindowListModel());
        JList worksheetList = new WorksheetListComponent(worksheetListModel.getWorksheetWindowListModel());
        worksheetListSelectionModel = worksheetList.getSelectionModel();
        addComponent(worksheetList, GrinvinWindowComponentType.WORKSHEETS);
        
        // standard graphs
        GraphTreeComponent graphTree = new GraphTreeComponent(GraphTreeModel.getStandardTree());
        addComponent(graphTree, GrinvinWindowComponentType.GRAPHLIST);
        
        add(tabs, BorderLayout.CENTER);
        
        tabs.setAlignmentY(Component.TOP_ALIGNMENT);
        panel.add(tabs);
        
        add(panel);
        
        locationLabel = new JLabel(" ");
        add(locationLabel);
        
        Workspace.getInstance().addWorkspaceListener(this);
        
        //
        MenuBuilder menuBuilder = new MenuBuilder();
        menuBuilder.load ("/org/grinvin/main/grinvin-menus.xml", 
                "org.grinvin.main.grinvin-menus");
        if (GrinvinPreferences.getInstance().getOsType() == GrinvinPreferences.OsType.MAC_OS_X)
            menuBuilder.setPredicate("mac");
        ActionMap actionMap = menuBuilder.getActionMap();
        actionMap.setParent(panel.getActionMap());
        panel.setActionMap (actionMap);
        
        actionMap.put("NewWorkspace", new NewWorkspace());
        
        actionMap.put("LoadWorkspace",  new LoadWorkspace(this));
        
        actionMap.put("SaveWorkspace",  new SaveWorkspace());
        
        actionMap.put("SaveWorkspaceAs",  new SaveWorkspaceAs(this));
        
        actionMap.put("SaveWorkspaceToDirectory",  new SaveWorkspaceToDirectory(this));
        
        actionMap.put("LoadInvariant",  new LoadInvariant(this));
        
        actionMap.put("ExitWithoutSave",  new ExitWithoutSave());
        
        actionMap.put("Exit",  new Exit() );
        
        actionMap.put("NewWorksheet",  new NewWorksheet(this));
        
        actionMap.put("ImportGraphList",  new ImportGraphList( this));
        
        actionMap.put("WorksheetRename",  new WorksheetRename(this));
        
        actionMap.put("LaunchHelp",  new LaunchHelp());
        
        actionMap.put("LaunchWebsite",  new LaunchWebsite());
        
        actionMap.put("DisplayHelpAfterTracking",  new DisplayHelpAfterTracking());
        
        actionMap.put("About",  new ShowWindow (about));
        
        actionMap.put("EditorWindow", new ShowWindow(getWindow("EditorWindow")));
        
        actionMap.put("LoggingWindow", new ShowWindow(getWindow("LoggingWindow")));
        
        setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
        addWindowListener(new GrinvinWindowListener());
        
        // menus
        JMenuBar menuBar = menuBuilder.createJMenuBar("main.menu");
        setJMenuBar(menuBar);
        
        // worksheet menus...
        WorksheetWindowMenuHandler wsmh = new WorksheetWindowMenuHandler
                ((JMenu)SwirlUtilities.getChildWithName(menuBar,"menu.worksheet"));
        wsmh.setWorksheetWindowListModel(getWorksheetListModel().getWorksheetWindowListModel());
        
        // icon
        setIconImage(SvgIconManager.getInstance().getIcon("/org/grinvin/icons/grinvin-favicon.svg", 16).getImage());
        
        // pack
        Dimension dim = getPreferredSize();
        dim.width += 30; // for some reason this is too small to contain all tabs
        setPreferredSize (dim);
        pack();
        
        // help
        HelpManager.setHelpIDString(this,MAIN_WINDOW_KEY);
        HelpManager.setHelpIDString(locationLabel,MAIN_WINDOW_KEY);
        HelpManager.setHelpIDString(SwirlUtilities.getChildWithName(menuBar,"menu.file"), "org.grinvin.window.main.menu.file");
        HelpManager.setHelpIDString(SwirlUtilities.getChildWithName(menuBar,"menu.worksheet"), "org.grinvin.window.main.menu.worksheet");
        HelpManager.setHelpIDString(SwirlUtilities.getChildWithName(menuBar,"menu.tools"), "org.grinvin.window.main.menu.tools");
        HelpManager.setHelpIDString(SwirlUtilities.getChildWithName(menuBar,"menu.help"), "org.grinvin.window.main.menu.help");
        HelpManager.setHelpIDString(worksheetList, "org.grinvin.window.main.tab.worksheets");
        HelpManager.enableHelpKey(this,MAIN_WINDOW_KEY);
        HelpManager.enableHelpKey(tabs,MAIN_WINDOW_KEY);
        HelpManager.enableHelpKey(tree, "org.grinvin.window.main.tab.invariants");
        CSH.addManager(new InvariantTreeHelpManager(tree));
        HelpManager.enableHelpKey(factoryList, "org.grinvin.window.main.tab.factory");
        CSH.addManager(new FactoryListHelpManager(factoryList));
        HelpManager.enableHelpKey(generatorList, "org.grinvin.window.main.tab.generators");
        CSH.addManager(new GeneratorListHelpManager(generatorList));
        HelpManager.enableHelpKey(worksheetList, "org.grinvin.window.main.tab.worksheets");
        TabbedPaneHelpManager tabHelpManager = new TabbedPaneHelpManager(tabs);
        HelpManager.enableHelpKey(graphTree, "org.grinvin.window.main.tab.library");
        tabHelpManager.putId(tabsMap.get(GrinvinWindowComponentType.INVARIANTS), "org.grinvin.window.main.tab.invariants");
        tabHelpManager.putId(tabsMap.get(GrinvinWindowComponentType.FACTORIES), "org.grinvin.window.main.tab.factory");
        tabHelpManager.putId(tabsMap.get(GrinvinWindowComponentType.GENERATORS), "org.grinvin.window.main.tab.generators");
        tabHelpManager.putId(tabsMap.get(GrinvinWindowComponentType.WORKSHEETS), "org.grinvin.window.main.tab.worksheets");
        tabHelpManager.putId(tabsMap.get(GrinvinWindowComponentType.GRAPHLIST), "org.grinvin.window.main.tab.library");
        CSH.addManager(tabHelpManager);
        
        //popup menus for tabs
        JTabbedPaneMouseListener l = new JTabbedPaneMouseListener(tabs);
        JPopupMenu popup = new JPopupMenu();
        popup.add(new CSHAction("org.grinvin.window.main.tab.invariants"));
        l.addPopup(tabsMap.get(GrinvinWindowComponentType.INVARIANTS), popup);
        popup = new JPopupMenu();
        popup.add(new CSHAction("org.grinvin.window.main.tab.factory"));
        l.addPopup(tabsMap.get(GrinvinWindowComponentType.FACTORIES), popup);
        popup = new JPopupMenu();
        popup.add(new CSHAction("org.grinvin.window.main.tab.generators"));
        l.addPopup(tabsMap.get(GrinvinWindowComponentType.GENERATORS), popup);
        popup = new JPopupMenu();
        popup.add(new CSHAction("org.grinvin.window.main.tab.graphlists"));
        l.addPopup(tabsMap.get(GrinvinWindowComponentType.WORKSHEETS), popup);
        popup = new JPopupMenu();
        popup.add(new CSHAction(MAIN_WINDOW_KEY));
        l.setDefaultPopup(popup);
    }

    // helper method for adding the different components to the main window
    private void addComponent(JComponent component, GrinvinWindowComponentType type) {
        JScrollPane treePane = new JScrollPane(component);
        ResourceBundle bundle = ResourceBundle.getBundle("org.grinvin.main.resources");
        tabs.addTab(bundle.getString("tab." + type.getName() + ".title"), null, 
                treePane, bundle.getString("tab." + type.getName() + ".tooltip"));
        tabsMap.put(type, treePane);
        reverseTabsMap.put(treePane, type);
    }
    
    //
    public void locationChanged() {
        setLocationLabel(Workspace.getInstance().getLocation().toString());
    }
    
    //
    private void setLocationLabel(String location) {
        locationLabel.setText(location);
        locationLabel.setToolTipText(location);
    }
    
    //
    private final Map<String,Window> windowMap;
    
    /**
     * Retrieve the application window with the given key.
     */
    public Window getWindow(String key) {
        return windowMap.get(key);
    }
    
    /**
     * Register the application window for the given key. Also installs a
     * menu command which enables the window to be made visible.
     */
    public void registerWindow(String key, Window window) {
        windowMap.put(key, window);
    }
    
    private WorksheetListModel worksheetListModel;
    
    public WorksheetListModel getWorksheetListModel(){
        return worksheetListModel;
    }
    
    public void setWorksheetListModel(WorksheetListModel model) {
        this.worksheetListModel = model;
    }
    
    private ListSelectionModel worksheetListSelectionModel;
    
    public ListSelectionModel getWorksheetListSelectionModel(){
        return worksheetListSelectionModel;
    }
    
    //
    private final Map<GrinvinWindowComponentType, JComponent> tabsMap;
    private final Map<JComponent, GrinvinWindowComponentType> reverseTabsMap;
    
    
    public GrinvinWindowComponentType getSelectedTab(){
        return reverseTabsMap.get(tabs.getSelectedComponent());
    }
    
    public void setSelectedTab(GrinvinWindowComponentType key){
        tabs.setSelectedComponent(tabsMap.get(key));
    }
    
    public void addTabsChangeListener(ChangeListener l){
        tabs.addChangeListener(l);
    }
    
    public void removeTabsChangeListener(ChangeListener l){
        tabs.removeChangeListener(l);
    }
    
    private GraphFactoryListModel getStandardGraphFactories() {
        GraphFactoryListModel model = new DefaultGraphFactoryListModel();
        FactoryManager.getGraphFactoryBroker().addToList(model);
        return model;
    }
    
    private GraphGeneratorListModel getStandardGraphGenerators() {
        GraphGeneratorListModel model = new DefaultGraphGeneratorListModel();
        FactoryManager.getGeneratorBroker().addToList(model);
        return model;
    }
    
    /**
     * Simple WindowAdapter that saves the Workspace when GrInvIn is closed.
     */
    private static class GrinvinWindowListener extends WindowAdapter {
        
        //
        GrinvinWindowListener () {
            // avoid creation of access type
        }
        
        //
        @Override
        public void windowClosing(WindowEvent e) {
            Exit.handleExit();
        }
        
    }

    public URI getURI() {
        return uri;
    }

    public void setURI(URI uri) {
        this.uri = uri;
    }
    
}
