 /**
 *#########################################################################
 *
 * A component of the Gatherer application, part of the Greenstone digital
 * library suite from the New Zealand Digital Library Project at th * University of Waikato, New Zealand.
 *
 * <BR><BR>
 *
 * Author: Shaoqun Wu, Greenstone Digital Library, University of Waikato
 *
 * <BR><BR>
 *
 * Copyright (C) 2006 New Zealand Digital Library Project
 *
 * <BR><BR>
 *
 * 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.
 *
 * <BR><BR>
 *
 * 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.
 *
 * <BR><BR>
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *############################################## ##########################
 */
package org.greenstone.gatherer.gems;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import java.lang.String;
import javax.swing.event.*;
import javax.swing.filechooser.*;
import javax.swing.text.*;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.*;

import java.io.File;

import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.DebugStream;
import org.greenstone.gatherer.Dictionary;
import org.greenstone.gatherer.GetOpt;
import org.greenstone.gatherer.gui.GComboBox;
import org.greenstone.gatherer.gui.GLIButton;
import org.greenstone.gatherer.gui.HelpFrame;
import org.greenstone.gatherer.gui.ModalDialog;
import org.greenstone.gatherer.gui.NonWhitespaceField;
import org.greenstone.gatherer.util.Codec;
import org.greenstone.gatherer.util.JarTools;
import org.greenstone.gatherer.util.Utility;
import org.greenstone.gatherer.util.XMLTools;
import org.greenstone.gatherer.Dictionary;
import org.w3c.dom.*;

import org.greenstone.gatherer.Gatherer; // only for invokeInEDT stuff
import org.greenstone.gatherer.gui.TestingPreparation;

/* 
 * @author Shaoqun Wu, Greenstone Digital Library, University of Waikato
 * @version 2.4
 */ 
public class GEMS
    extends JFrame implements WindowFocusListener 
{
 
    static final private Dimension SIZE = new Dimension(800,550);

    // we have a card pane to switch between the no set loaded view, and the 
    // set loaded view
    private CardLayout card_layout = null;
    private JPanel card_pane = null;
    /** The name of the panel containing the "set loaded" card. */
    private String SET_LOADED_CARD = "";
    /** The name of the panel containing the "no set loaded" placeholder */
    private String NO_SET_LOADED_CARD = "No set loaded";

    private GEMS self = null;

    private OpenMetadataSetPrompt open_prompt;
    private DeleteMetadataSetPrompt delete_prompt;
    private NewMetadataSetPrompt new_prompt;   
    
    // this is the main panel that gets switched in once a set is loaded
    private JSplitPane metadata_set_details_split_pane = null;
    // this is the split with the attribute tables in it
    private JSplitPane attribute_tables_split_pane = null;
     
    /** Um, the size of the screen I'd guess. */
    private Dimension screen_size = null;
    
    private MetadataSetManager msm;

    private AttributeTable attribute_table;  // meta_element_table
    private AttributeTable language_dependent_attribute_table; //lang_element_table; 

    private MetadataSetTree meta_set_tree;     

    private MetadataSetModel metadata_set_model;

    private boolean stand_alone = true;

    private ArrayList listeners;

    static public void main(String[] args)
    {
	//TODO: add an option to open a paticular metadata set

	// Parse arguments
	GetOpt go = new GetOpt(args);

	if (go.debug) {
	    DebugStream.enableDebugging();

	    Calendar now = Calendar.getInstance();
	    String debug_file_path = "debug" + now.get(Calendar.DATE) + "-" + now.get(Calendar.MONTH) + "-" + now.get(Calendar.YEAR) + ".txt";
	    
	    // Debug file is created in the GLI directory
	    DebugStream.println("Debug file path: " + debug_file_path);
	    DebugStream.setDebugFile(debug_file_path);   

	}
	
	if(go.testing_mode) { // Gatherer does this if GLI launched, but we're launching GEMS
	    TestingPreparation.TEST_MODE = true;
	}

	//System.err.println("@@@ GEMS launch:\n" + go.gsdl_path);
	//System.err.println(go.gsdl3_web_path);
	//System.err.println(go.gsdl3_writableweb_path);
	//System.err.println(go.metadata_path);
	//System.err.println(go.new_set);
	
	Gatherer.invokeInEDT_replacesProceedInCurrThread("GEMS.main()",
	 Gatherer.SYNC,
	 new Runnable() {
		public void run() {
		    new GEMS(go.gsdl_path,go.gsdl3_web_path,go.gsdl3_writableweb_path,go.metadata_path,true,go.new_set);
		}
	    });
    }
    
    /** Constructor - original. Called when GEMS is launched as a standalone application.
     * Sets up a very basic static-looking Configuration object, and Dictionary.
     * Do not call if GEMS is launched through GLI, as this constructor will erase and replace
     * GLI's carefully configured Configuration and Dictionary causing hard to track down errors.
     * So don't call this constructor if standalone = false.
     metadata_path - (opt) the path to an existing metadata set to be edited.
     */
    public GEMS(String gsdl_path, String gsdl3_web_path, String gsdl3_writableweb_path, String metadata_path, boolean standalone, boolean new_set)
    {
	self = this;
	JarTools.initialise(this);
	// Load GLI config file: set up the Configuration so we can set up the Dictionary language
	// Determine the GLI user directory path
	String gli_user_directory_path = System.getProperty("user.home") + File.separator;
        if (Utility.isWindows()) {
	    gli_user_directory_path += "Application Data" + File.separator + "Greenstone" + File.separator + "GLI" + File.separator;
        }
        else {
            gli_user_directory_path += ".gli" + File.separator;
        }	

	// Since GEMS launched standalone, need to set up Configuration and Dictionary
	new Configuration(gli_user_directory_path, gsdl_path, gsdl3_web_path, gsdl3_writableweb_path,
			  null /*gsdl3_src_path*/, null /*site_name*/, null /*servlet_name*/, null /*fedora_info*/, null /*startup_lang */); // null for parameters unknown to GEMS

	// Read Dictionary in the locale specified in the config.xml
	new Dictionary(Configuration.getLocale("general.locale", true), Configuration.getFont("general.font", true));
	init(metadata_path, standalone, new_set);
    }

    /** Constructor - overloaded to use existing Configuration and Dictionary
     * This Constructor is used when GEMS is launched through GLI.
     * It's crucial for GLI to launch GEMS with this constructor as the other one will erase and
     * replace GLI's carefully set-up Configuration and Dictionary, resulting in subtle but major
     * flow-on errors particularly because of the alternate's cutdown version of Configuration.
     */
    public GEMS(String metadata_path, boolean new_set)
    {	
	self = this;
	JarTools.initialise(this);

	// When GEMS is launched through GLI (so not standalone)
	// a Configuration and Dictionary is already set up

	init(metadata_path, false /*standalone*/, new_set);
    }

    private void init(String metadata_path, boolean standalone, boolean new_set) {
	screen_size = Configuration.screen_size;
	
	//msm = new MetadataSetManager(Configuration.gsdl_path,Configuration.gsdl3_path);
	         // dangerously side-effecting version of MetadataSetManager constructor that
	    // erases Configuration all over again (as the original GEMS constructor also did)
	    // and is noticeable when remote GS3 server cryptically fails because
	    // Configuration.site_name is suddenly null due to these constructors.
	    // Creating/adding a new metadata set also prevents GLI > Preferences from opening
	    // because Configuration.site_name is nulled as a consequence of launching GEMS through GLI

	// Instead, call the MetadataSetManager() constructor as it reuses existing Configuration,
	// whether this is a basic Configuration created by standalone GEMS
	// or a carefully and highly configured Configuration object created by GLI launching GEMS.
	msm = new MetadataSetManager();
	
        stand_alone = standalone;
	listeners = new ArrayList();

        addWindowListener(new WindowAdapter(){
		public void windowClosing(WindowEvent e){
		    metadata_set_model.save(true);
		    if (stand_alone){
			System.exit(0);
		    }
		    else {
			notifyListeners();
			setVisible(false);
		    }
		}
	    });
	
	setSize(SIZE);
	setTitle(Dictionary.get("GEMS.Title"));
	setJMenuBar(new GEMSMenuBar());

	addWindowFocusListener(this);

	card_layout = new CardLayout();
	card_pane = new JPanel();

        metadata_set_model = new MetadataSetModel(msm);
        msm.setMetadataSetModel(metadata_set_model);
	meta_set_tree = new MetadataSetTree(self);
        metadata_set_model.addObserver(meta_set_tree);

	attribute_table = new AttributeTable(false);
	MetadataElementModel mem = new MetadataElementModel();
        mem.addMetadataElementListener((MetadataElementListener)attribute_table);
	attribute_table.addAttributeListener(metadata_set_model);

	language_dependent_attribute_table = new AttributeTable(true);
        mem.addMetadataElementListener((MetadataElementListener)language_dependent_attribute_table);
	language_dependent_attribute_table.addAttributeListener(metadata_set_model);

      	MetadataSetInfo msti= new MetadataSetInfo();
        msti.addMetadataSetListener((MetadataSetListener)attribute_table);
        msti.addMetadataSetListener((MetadataSetListener)language_dependent_attribute_table);
        msti.setMetadataSetModel(metadata_set_model);
        
        open_prompt = new OpenMetadataSetPrompt(self,msm);
        open_prompt.addMetadataSetListener((MetadataSetListener)metadata_set_model);
        open_prompt.addMetadataSetListener((MetadataSetListener)attribute_table);        
	open_prompt.addMetadataSetListener((MetadataSetListener)language_dependent_attribute_table); 	
	
        delete_prompt = new DeleteMetadataSetPrompt(self,msm);
        delete_prompt.addMetadataSetListener((MetadataSetListener)metadata_set_model); 
        delete_prompt.addMetadataSetListener((MetadataSetListener)attribute_table);
	delete_prompt.addMetadataSetListener((MetadataSetListener)language_dependent_attribute_table);
	
        new_prompt = new NewMetadataSetPrompt(self,msm);
        new_prompt.addMetadataSetListener((MetadataSetListener)metadata_set_model); 
        new_prompt.addMetadataSetListener((MetadataSetListener)attribute_table);
        new_prompt.addMetadataSetListener((MetadataSetListener)language_dependent_attribute_table);
	
	
        // load the initial metadataset
        if (metadata_path !=null && !metadata_path.equals("")){
	    open_prompt.openMetadataSet(metadata_path);
	}
	else{
	    if (new_set) new_prompt.display();
	}

	// the set tree
        JScrollPane treePane = new JScrollPane(meta_set_tree);
	// the language independent attributes
	JScrollPane tablePane = new JScrollPane(attribute_table);
	// the language dependent attributes
	JScrollPane langTablePane =  new JScrollPane(language_dependent_attribute_table);

	// no set loaded pane
	JPanel no_set_loaded_pane = new JPanel();
	no_set_loaded_pane.setBackground(Color.lightGray);
	JLabel no_set_loaded_label = new JLabel(Dictionary.get("GEMS.No_Set_Loaded"));
	no_set_loaded_label.setHorizontalAlignment(JLabel.CENTER);
	no_set_loaded_label.setVerticalAlignment(JLabel.CENTER);

	no_set_loaded_pane.setLayout(new BorderLayout());
	no_set_loaded_pane.add(no_set_loaded_label, BorderLayout.CENTER);

        JPanel buttonPane = new JPanel(new GridLayout(1,2));
        
        JButton arrow_up_button = new GLIButton(Dictionary.get("GEMS.Move_Up"));
	arrow_up_button.setIcon(JarTools.getImage("arrow-up.gif"));

        JButton arrow_down_button = new GLIButton(Dictionary.get("GEMS.Move_Down"));
	arrow_down_button.setIcon(JarTools.getImage("arrow-down.gif")); 
        
        arrow_up_button.setActionCommand(GEMSConstants.MOVE_UP);
	arrow_up_button.addActionListener(meta_set_tree);
        arrow_down_button.addActionListener(meta_set_tree);
	arrow_down_button.setActionCommand(GEMSConstants.MOVE_DOWN);
	
	buttonPane.add(arrow_up_button);
        buttonPane.add(arrow_down_button);
        
        JPanel leftPane =  new JPanel(new BorderLayout());          
        leftPane.add(treePane,BorderLayout.CENTER);  
	leftPane.add(buttonPane,BorderLayout.SOUTH);  
         
	JLabel selected_language_label = new JLabel(Dictionary.get("GEMS.SelectedLanguage"));
	selected_language_label.setOpaque(true);
        
	Vector language_vector = new Vector(msm.getLanguageList());
        language_vector.add(0, Dictionary.get("GEMS.Language"));
	JComboBox language_combo = new JComboBox(language_vector);
	
	language_combo.addActionListener(new ActionListener(){
		public void actionPerformed(ActionEvent e){
		    JComboBox language_combo = (JComboBox) e.getSource();
		    String langInfo = (String)language_combo.getSelectedItem(); 
		    if (!langInfo.equals(Dictionary.get("GEMS.Language"))) {
			String lang = langInfo.split("\\s")[0];     		    
			language_dependent_attribute_table.addNewLanguage(lang);
		    }
		}

	    });      

       	JLabel language_label = new JLabel(Dictionary.get("GEMS.LanguageDependent"));
	language_label.setOpaque(true);
      
         	
	JPanel selectLangPane = new JPanel(new BorderLayout(5,5));
        selectLangPane.add(selected_language_label,BorderLayout.WEST);
        selectLangPane.add(language_combo, BorderLayout.CENTER);   


        JPanel languagePane =  new JPanel(new BorderLayout(5,5));
	languagePane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
	languagePane.add(language_label,BorderLayout.NORTH);  
        languagePane.add(selectLangPane,BorderLayout.CENTER);        


        JPanel languageAttributePane =  new JPanel(new BorderLayout());
	languageAttributePane.add(languagePane,BorderLayout.NORTH);  
        languageAttributePane.add(langTablePane,BorderLayout.CENTER);        

	JLabel attribute_label = new JLabel(Dictionary.get("GEMS.Attribute_Table"));
	attribute_label.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
	JPanel mainAttributePane = new JPanel(new BorderLayout());
	mainAttributePane.add(attribute_label, BorderLayout.NORTH);
	mainAttributePane.add(tablePane, BorderLayout.CENTER);
	
	attribute_tables_split_pane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,mainAttributePane,languageAttributePane);
	
	metadata_set_details_split_pane =  new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,leftPane,attribute_tables_split_pane); 	

	card_pane.setLayout(card_layout);
	card_pane.add(no_set_loaded_pane, NO_SET_LOADED_CARD);
	card_pane.add(metadata_set_details_split_pane, SET_LOADED_CARD);

	getContentPane().add(card_pane,BorderLayout.CENTER);
	setLocation((screen_size.width - SIZE.width) / 2, (screen_size.height - SIZE.height) / 2);
	
	if (stand_alone) {
	    setVisible(true);
	}

	
	TestingPreparation.setNamesRecursively(this);
	if(TestingPreparation.TEST_MODE) {
	    this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
	    this.setName("GEMS"); // Don't want the GEMS main window called GEMSMenuBar.this$0
	} 
    }

    public void setVisible(boolean visible) {
	super.setVisible(visible);
	if (visible) {
	    attribute_tables_split_pane.setDividerLocation(0.3);
	}
    }
    
    // called from GLI
    public void displayMetadataSet(String metadata_path){
	open_prompt.openMetadataSet(metadata_path);
	updateCardLayout(true);
	setVisible(true);
    }   

    // called from GLI
    public void newMetadataSet(){
	new_prompt.display();
	if (!new_prompt.isCancelled()) {
	    updateCardLayout(true);
	    setVisible(true);
	}

    }

    public void addGEMSListener(GEMSListener listener){
	listeners.add(listener);
    }  

    public void removeGEMSListener(GEMSListener listener){
	listeners.remove(listener);
    }  

    public void notifyListeners(){
	for (int i=0;i<listeners.size();i++){
	    GEMSListener listener = (GEMSListener) listeners.get(i); 
	    listener.gemsIsClosed();
	}
    }


    public void exit(){
	//clean up things
	cleanUpListeners();
    }
    
    protected void cleanUpListeners() {
	MetadataSetInfo msti= new MetadataSetInfo();
	msti.removeAllMetadataSetListeners();
	MetadataElementModel mem= new MetadataElementModel();
	mem.removeAllMetadataElementListeners();

    }
    
    public void windowGainedFocus(WindowEvent e) {
	//System.out.println("windowGainedFocus.");
    }

    public void windowLostFocus(WindowEvent e) {
        //System.out.println("windowLostFocus.");
          
	//save stuff
	
    }

    protected void updateCardLayout(boolean set_loaded) {
	if (set_loaded) {
	    card_layout.show(card_pane, SET_LOADED_CARD);
	} else {
	    card_layout.show(card_pane, NO_SET_LOADED_CARD);
	}
    }
   
    private class GEMSMenuBar
	extends JMenuBar
	implements ActionListener
    {
	private JMenu file = null;
	private JMenu edit = null;

	public JMenuItem file_exit   = null;
	public JMenuItem file_new    = null;
	public JMenuItem file_open   = null;
	public JMenuItem file_close   = null;
	public JMenuItem file_save   = null;
	public JMenuItem edit_copy   = null;
	public JMenuItem edit_cut    = null;
	public JMenuItem edit_paste  = null;
	public JMenuItem help_help   = null;
        public JMenuItem file_delete = null;
        //public JMenuItem file_preferences = null;
	public GEMSMenuBar()
	{
	    file = new JMenu();
	    file.setText(Dictionary.get("Menu.File"));
	    
	    file_exit = new JMenuItem(Dictionary.get("Menu.File_Exit"));
	    file_exit.addActionListener(this);
	    
	    file_new = new JMenuItem(Dictionary.get("Menu.File_New"));
	    file_new.addActionListener(this);

	    file_open = new JMenuItem(Dictionary.get("Menu.File_Open"));
	    file_open.addActionListener(this);
	    
	    file_close = new JMenuItem(Dictionary.get("Menu.File_Close"));
	    file_close.addActionListener(this);

	    file_save = new JMenuItem(Dictionary.get("Menu.File_Save"));
	    file_save.addActionListener(this);
   
            file_delete = new JMenuItem(Dictionary.get("Menu.File_Delete"));
	    file_delete.addActionListener(this);
	                
            //file_preferences = new JMenuItem(Dictionary.get("Menu.File_Options"));
	    //file_preferences.addActionListener(this);
	    
	    if (!stand_alone) {
		// when running from GLI, we disable most of the menu so 
		// that we have control over what sets the user is editing
		file_new.setEnabled(false);
		file_open.setEnabled(false);
		file_close.setEnabled(false);
		file_delete.setEnabled(false);
	    }
	    
	    // Layout (file menu)
	    file.add(file_new);
	    file.add(file_open);
	    file.add(file_close);
	    file.add(file_save);
            file.add(file_delete);
	    file.add(new JSeparator());
            //file.add(file_preferences);
            //file.add(new JSeparator());
	    file.add(file_exit);
            
	    // Edit menu
	    edit = new JMenu();
	    edit.setText(Dictionary.get("Menu.Edit"));

	    String modkey = "ctrl";
	    if(Utility.isMac()) { // on Mac, we now use the Apple key instead of Ctrl for GLI/GEMS edit shortcuts
		// http://stackoverflow.com/questions/5585919/creating-unicode-character-from-its-number
		char appleKeyCodepoint = 0x2318; // applekey symbol unicode codepoint: U+2318 (\u2318)
		String appleKeySymbol = String.valueOf(appleKeyCodepoint);
		modkey = appleKeySymbol;
	    }

	    edit_cut = new JMenuItem(Dictionary.get("Menu.Edit_Cut", modkey));
	    edit_cut.addActionListener(this);
	    
	    edit_copy = new JMenuItem(Dictionary.get("Menu.Edit_Copy", modkey));
	    edit_copy.addActionListener(this);
	    
	    edit_paste = new JMenuItem(Dictionary.get("Menu.Edit_Paste", modkey));
	    edit_paste.addActionListener(this);
	    
	    // Layout (edit menu)
	    edit.add(edit_cut);
	    edit.add(edit_copy);
	    edit.add(edit_paste);

	
	    // Layout (menu bar)
	    this.add(file);
	    this.add(Box.createHorizontalStrut(15));
	    this.add(edit);
	    this.add(Box.createHorizontalGlue());
	    //this.add(help);
	}


	public void actionPerformed(ActionEvent event)
	{
	    Object event_source = event.getSource();

	    // File -> New
	    if (event_source == file_new) {
		new_prompt.display();
		if (!new_prompt.isCancelled()) {
		    updateCardLayout(true);
		}
 		return;
	    }

	    // File -> Open
	    if (event_source == file_open) {               
		open_prompt.display();
		if (!open_prompt.isCancelled()) {
		    updateCardLayout(true);
		}
		return;
	    }

	    // File -> Close
	    if (event_source == file_close) {
		metadata_set_model.save(true); 
		updateCardLayout(false);
		return;
	    }
              // File -> Delete
	    if (event_source == file_delete) {               
                delete_prompt.display();
		return;
	    }


	    // File -> Save
	    if (event_source == file_save) {
		metadata_set_model.save(false);
		return;
	    }        

	    // File -> Exit
	    if (event_source == file_exit) {
               	metadata_set_model.save(true); 
		if (stand_alone) {
		    if(TestingPreparation.TEST_MODE) {
			GEMS.this.setVisible(false);
			GEMS.this.dispose();
		    } else {
			System.exit(0);
		    }
		}
		else {
		    self.notifyListeners();
		    self.setVisible(false);
		    return;
		}
	    }

	    /// File -> Preferences
	    // if(event_source == file_preferences){
		// GEMSPreferences GemsPreferences = new GEMSPreferences();  
            //}

	    // Edit -> Cut
	    if (event_source == edit_cut) {
		try {
		    KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
		    // Get the component with selected text as a JTextComponent
		    JTextComponent text = (JTextComponent) kfm.getPermanentFocusOwner();
		    // Cut the text to the clipboard
		    text.cut();
		}
		catch (ClassCastException cce) {
		    // If the component is not a text component ignore the cut command
		    DebugStream.println(cce.toString());
		}
		return;
	    }

	    // Edit -> Copy
	    if (event_source == edit_copy) {
		try {
		    KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
		    // Get the component with selected text as a JTextComponent
		    JTextComponent text = (JTextComponent) kfm.getPermanentFocusOwner();//getFocusOwner();
		    text.copy();
		}
		catch (Exception cce) {
		    // If the component is not a text component ignore the copy command
		    DebugStream.println(cce.toString());
		}
		return;
	    }

	    // Edit -> Paste
	    if (event_source == edit_paste) {
		try {
		    KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
		    // Get the component with selected text as a JTextComponent
		    JTextComponent text = (JTextComponent) kfm.getPermanentFocusOwner();
		    // Cut the text to the clipboard
		    text.paste();
		}
		catch (ClassCastException cce) {
		    // If the component is not a text component ignore the paste command
		    DebugStream.println(cce.toString());
		}
		return;
	    }
	  
	}
    }

}
