/**
 *############################################################################
 * A component of the Greenstone Librarian Interface, part of the Greenstone
 * digital library suite from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * Copyright (C) 2025 New Zealand Digital Library Project
 *
 * 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.
 *
 * 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.collection;

import java.awt.*;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.event.*;
import java.io.File;
import java.util.Vector;
import javax.swing.*;
import javax.swing.tree.*;
import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.Dictionary;
import org.greenstone.gatherer.Gatherer;
import org.greenstone.gatherer.file.FileNode;
import org.greenstone.gatherer.file.FileSystemModel;
import org.greenstone.gatherer.gui.tree.DragTree;
import org.greenstone.gatherer.gui.tree.DragTreeCellRenderer;
import org.greenstone.gatherer.gui.TextFileEditor;
import org.greenstone.gatherer.gui.XMLFileEditor;
import org.greenstone.gatherer.util.UnzipTools;
import org.greenstone.gatherer.util.Utility;


public class FullCollectionTree
    extends DragTree
    implements MouseListener
{

  public FullCollectionTree(FileSystemModel full_collection_tree_model, boolean mixed_selection)
    {
	super(full_collection_tree_model, mixed_selection);
	addMouseListener(this);

	setBackgroundNonSelectionColor(Configuration.getColor("coloring.collection_tree_background", false));
	setBackgroundSelectionColor(Configuration.getColor("coloring.collection_selection_background", false));
	setTextNonSelectionColor(Configuration.getColor("coloring.collection_tree_foreground", false));
	setTextSelectionColor(Configuration.getColor("coloring.collection_selection_foreground", false));

	filter.setBackground(Configuration.getColor("coloring.collection_heading_background", false));
	filter.setEditable(Configuration.getMode() >= Configuration.LIBRARIAN_MODE);
    }

    // For automated testing, we need to know the collection folder name on the filesystem
    // So return the file object of the root of this tree
    public File getRootFile() {
	FileNode node = (FileNode)this.getModel().getRoot();
	return node.getFile();
    }
    
    public boolean isDraggable()
    {
	return true;
    }

    public boolean isDroppable()
    {
	return true;
    }

  // is this still necessary?
	// Overridden here: when a successful drag and drop takes place 
	// make sure nothing is selected in the tree, so that no valueChanged()
	// event gets fired anymore by items getting reselected in the tree 
	// (this  used to result in the metadata table in EnrichPane being updated 
	// upon its valueChanged() getting called and funny things happened to
	// the metadata due to state inconsistencies). 
	public void drop(DropTargetDropEvent event) {
		if (!isDroppable()) {
			return;
		}
		
		setImmediate(true);
		clearSelection();
		setImmediate(false);
		
		// let the superclass' drop() process the rest of the drag event
		super.drop(event);
		
	}

    public void mouseClicked(MouseEvent event)
    {
	if (SwingUtilities.isRightMouseButton(event)) {
          new FullCollectionTreeRightClickMenu(this, event).showMenu(event);
	}
    }

    public void mouseEntered(MouseEvent event) { }

    public void mouseExited(MouseEvent event) { }

    public void mousePressed(MouseEvent event) { }

    public void mouseReleased(MouseEvent event) { }


    public String toString()
    {
        return "CollectionFiles";
    }

    /** When a user right-clicks within the workspace and collection trees they are presented with a small popup menu of context based options. This class provides such functionality.
     */
    protected class FullCollectionTreeRightClickMenu
	extends JPopupMenu
	implements ActionListener
    {
	/** The tree over which the right click action occurred. */
	protected FullCollectionTree collection_tree = null;
	/** The tree nodes selected when the right click action occurred. */
	protected TreePath[] selection_paths = null;
	/** The file record over which the right click action occurred. */
	protected FullCollectionTreeNode node = null;

	protected JMenuItem collapse_folder = null;
	protected JMenuItem expand_folder = null;
	protected JMenuItem unzip_file = null;
	protected JMenuItem delete = null;
	protected JMenuItem new_folder = null;
	protected JMenuItem new_file = null;
        protected JMenuItem new_dummy_doc = null;
	protected JMenuItem refresh = null; // for refreshing folder view
	protected JMenuItem open_externally = null;
	protected JMenuItem rename = null;
	protected JMenuItem replace = null;
        protected JMenuItem edit_file = null;

	protected FullCollectionTreeRightClickMenu(FullCollectionTree full_collection_tree, MouseEvent event)
	{
	    super();
	    this.collection_tree = full_collection_tree;

	    // Note we have to use setImmediate() with the set selction paths
	    // otherwise the selection doesn't get updated until after the 
	    // popup comes up.

	    // the right click position
	    TreePath right_click_path = collection_tree.getPathForLocation(event.getX(), event.getY());
	    if (right_click_path == null) {
		// user has clicked outside of the tree, clear the selection
		selection_paths = null;
		collection_tree.setImmediate(true);
		collection_tree.clearSelection();
		collection_tree.setImmediate(false);
	    }
	    else {
		// Get the paths currently selected in the tree
		selection_paths = collection_tree.getSelectionPaths();
		if (selection_paths == null) {
		    // nothing currently selected - we shift the selection to 
		    // the node that was right clicked on
		    selection_paths = new TreePath[1];
		    selection_paths[0] = right_click_path;
		    collection_tree.setImmediate(true);
		    collection_tree.setSelectionPath(right_click_path);
		    collection_tree.setImmediate(false);
		}
		else if (selection_paths.length == 1 && ! selection_paths[0].equals( right_click_path)) {
		    collection_tree.setImmediate(true);
		    collection_tree.clearSelection();
		    collection_tree.setSelectionPath(right_click_path);
		    collection_tree.setImmediate(false);
		    selection_paths[0] = right_click_path;
		}
		else {
		    // we had multiply selected paths in the tree.
		    // if we clicked on one of those paths, then use all the 
		    // current selection, otherwise clear the selection and 
		    // select the one we right clicked on
		    boolean clicked_in_selection = false;
		    for (int i = 0; i < selection_paths.length; i++) {
			if (selection_paths[i].equals(right_click_path)) {
			    clicked_in_selection = true;
			    break;
			}
		    }
		    if (!clicked_in_selection) {
			// want the tree to update right away
			collection_tree.setImmediate(true);
			collection_tree.clearSelection();
			collection_tree.setSelectionPath(right_click_path);
			collection_tree.setImmediate(false);
			selection_paths = new TreePath[1];
			selection_paths[0] = right_click_path;
		    }
		}
	    }
	    
	}

      // We can't call an overridable method from in the constructor, as the sub class
      // will not be instantiated yet, and its member variables will remain null.
      // So need to call this after the constructor is finished.
      public void showMenu(MouseEvent event) {
        // Create an appropriate context menu, based on what is selected
        buildContextMenu(selection_paths);
        
        // Show the popup menu on screen
        show(collection_tree, event.getX(), event.getY());
        
      }
	/** Checks whether the files selected are of the same filetype (have the same extension).
	 * @param selectedFilePaths - the full file paths to the treenodes selected in the 
	 * collection fileview. 
	 * @return true if the file extensions of all the selected files are the same. False is 
	 * returned if the file extension of any selected file is different (this means that if 
	 * a folder was selected, false would be returned). False is also returned if nothing was
	 * selected.
	 * For use with unzip and replace_srcdoc_with_html.pl(CollectionTree)
	*/
	protected boolean selectedFilesOfSameType(TreePath[] selectedFilePaths) {
	    if(selectedFilePaths == null || selectedFilePaths.length <= 0)
		return false;

	    boolean sameExtension = true;

	    // get just the filename from the path and extract its extension
	    String firstFile = selectedFilePaths[0].getLastPathComponent().toString();
	    int period = firstFile.lastIndexOf('.');
	    if(period == -1) { // someone could have selected a folder
		return false;
	    }
	    String extension = firstFile.substring(period); // includes period

	    // compare with the other selected files' extensions:
	    for(int i = 1; i < selectedFilePaths.length && sameExtension; i++) {
		String otherFile = selectedFilePaths[i].getLastPathComponent().toString();
		String otherFileExt = otherFile.substring(otherFile.lastIndexOf('.'));
		if(!extension.equals(otherFileExt)) 
		    sameExtension = false;
	    }
	    return sameExtension;
	}

	protected void buildContextMenu(TreePath[] selection_paths)
	{

	    // If nothing is selected, only the new folder/dummy doc options are available...
	    if (selection_paths == null) {
		new_folder = new JMenuItem(Dictionary.get("CollectionPopupMenu.New_Folder"), KeyEvent.VK_N);
		new_folder.addActionListener(this);
		add(new_folder);

		new_file = new JMenuItem(Dictionary.get("CollectionPopupMenu.New_File"));
		new_file.addActionListener(this);
		add(new_file);

                new_dummy_doc = new JMenuItem(Dictionary.get("CollectionPopupMenu.New_Empty_File"));
                new_dummy_doc.addActionListener(this);
		add(new_dummy_doc);

		refresh = new JMenuItem(Dictionary.get("CollectionPopupMenu.Refresh"));
		if(Gatherer.isGsdlRemote) {
		    refresh.setEnabled(false);
		}
		refresh.addActionListener(this);
		add(refresh);

		node = (FullCollectionTreeNode) collection_tree.getModel().getRoot();
		return;
	    }

            // something has been selected
            
	    // delete and unzip options
            
	    delete = new JMenuItem(Dictionary.get("CollectionPopupMenu.Delete"), KeyEvent.VK_D);
	    delete.addActionListener(this);
	    add(delete);

            FullCollectionTreeNode firstSelectedNode = (FullCollectionTreeNode)selection_paths[0].getLastPathComponent();
            
	    
	    // Unzip menu option is available if not remote GS
	    // and when all selected files are of .zip extension
	    if(!Gatherer.isGsdlRemote && firstSelectedNode.isZipFile()) { // test 1st selected node
		unzip_file = new JMenuItem(Dictionary.get("CollectionPopupMenu.Unzip"), KeyEvent.VK_U);
		unzip_file.addActionListener(this);		
		add(unzip_file);
		
		// Now the menu is there, grey it out if not all the files are of the same type
		if(!selectedFilesOfSameType(selection_paths)) {
		    unzip_file.setEnabled(false);
		}
	    }
	    
	    // Only delete (and possibly unzip_file)
	    // are available if multiple items are selected...
	    if (selection_paths.length > 1) {
		return;
	    }

	    // Rename option
	    rename = new JMenuItem(Dictionary.get("CollectionPopupMenu.Rename"), KeyEvent.VK_R);
	    rename.addActionListener(this);
	    add(rename);

	    TreePath path = selection_paths[0];
	    node = (FullCollectionTreeNode) path.getLastPathComponent();

	    // ---- Options for file nodes ----
	    if (node.isLeaf()) {

		// Replace file
		replace = new JMenuItem(Dictionary.get("CollectionPopupMenu.Replace"), KeyEvent.VK_P);
		replace.addActionListener(this);
		add(replace);

                // Edit the file in GLI
                if (node.isEditable()) {
                  edit_file = new JMenuItem(Dictionary.get("CollectionPopupMenu.Edit"), KeyEvent.VK_E);
                  edit_file.addActionListener(this);
                  add(edit_file);
                }
                // Open the file in an external program
		open_externally = new JMenuItem(Dictionary.get("Menu.Open_Externally"), KeyEvent.VK_O);
		open_externally.addActionListener(this);
		add(open_externally);

		return;
	    }

	    // ---- Options for folder nodes ----
	    // Collapse or expand, depending on current status
	    if (collection_tree.isExpanded(path)) {
		collapse_folder = new JMenuItem(Dictionary.get("Menu.Collapse"), KeyEvent.VK_C);
		collapse_folder.addActionListener(this);
		add(collapse_folder);
	    }
	    else {
		expand_folder = new JMenuItem(Dictionary.get("Menu.Expand"), KeyEvent.VK_O);
		expand_folder.addActionListener(this);
		add(expand_folder);
	    }

	    // New folder/dummy doc options
	    if (!node.isReadOnly()) {
		new_folder = new JMenuItem(Dictionary.get("CollectionPopupMenu.New_Folder"), KeyEvent.VK_N);
		new_folder.addActionListener(this);
		add(new_folder);

		
		new_file = new JMenuItem(Dictionary.get("CollectionPopupMenu.New_File"));
		new_file.addActionListener(this);
		add(new_file);

                new_dummy_doc = new JMenuItem(Dictionary.get("CollectionPopupMenu.New_Empty_File"));
                new_dummy_doc.addActionListener(this);
		add(new_dummy_doc);
	    }
	}


	/** Called whenever one of the menu items is clicked, this method then causes the appropriate effect. */
	public void actionPerformed(ActionEvent event)
	{
	    Object source = event.getSource();
	    // Collapse folder
	    if (source == collapse_folder) {
		collection_tree.collapsePath(selection_paths[0]);
	    }

	    // Expand folder
	    else if (source == expand_folder) {
		collection_tree.expandPath(selection_paths[0]);
	    }

	    else if (source == unzip_file) {
		Vector<FullCollectionTreeNode> sourceNodes = new Vector(selection_paths.length);
		// all selected shall be zip files at this stage
		for (int i = 0; i < selection_paths.length; i++) {
		    FullCollectionTreeNode node = (FullCollectionTreeNode) selection_paths[i].getLastPathComponent();
		    //System.err.println("Away to unzip file" + node.getFile().getPath());
		    
		    // unzip the zip file into its parent directory
		    // and maintain list of successfully unzipped files to delete
		    if(UnzipTools.unzipFile(node.getFile().getPath(), node.getFile().getParent()+File.separator)) {
			sourceNodes.add(node);
		    }		    
		}

		// refresh collection view - now done in file/FileQueue::run() in synchronized(this)
		//Gatherer.g_man.refreshCollectionTree(DragTree.COLLECTION_CONTENTS_CHANGED);
		
		// for all zips that were successfully unzipped, delete the zip file itself
		FullCollectionTreeNode[] unzippedNodes = new FullCollectionTreeNode[sourceNodes.size()];
		unzippedNodes = sourceNodes.toArray(unzippedNodes);
		// Fire a delete action - refreshes collection tree on its own iff refresh not
		// called on the tree before or after. But then the unzipped files didn't show.
		// So now refresh collection tree done in file/FileQueue::run() in synchronized(this)
		Gatherer.f_man.action(collection_tree, unzippedNodes, Gatherer.recycle_bin, null);
	    }
	    
	    // Delete
	    else if (source == delete) {
		FullCollectionTreeNode[] source_nodes = new FullCollectionTreeNode[selection_paths.length];
		for (int i = 0; i < selection_paths.length; i++) {
		    source_nodes[i] = (FullCollectionTreeNode) selection_paths[i].getLastPathComponent();
		}

		// Fire a delete action
		Gatherer.f_man.action(collection_tree, source_nodes, Gatherer.recycle_bin, null);
	    }


	    // New folder
  	    else if (source == new_folder) {
 		Gatherer.f_man.newFolder(collection_tree, node);
 	    }
	    else if (source == new_file) {
		Gatherer.f_man.newCollectionFile(collection_tree, node);
	    }
	    // New dummy doc
	    else if (source == new_dummy_doc) {
                Gatherer.f_man.newEmptyFile(collection_tree, node);
            }

	    // Refresh action to reload folder view
	    else if (source == refresh) { // Refresh collection tree
		Gatherer.g_man.refreshCollectionTree(DragTree.COLLECTION_CONTENTS_CHANGED);
	    }

	    // Open in external program
	    else if (source == open_externally) {
		Gatherer.f_man.openFileInExternalApplication(node.getFile());
	    }

            // edit the file in GLI
            else if (source == edit_file) {
              File this_file = node.getFile();
              String ext = this_file.getName();
              ext = ext.substring(ext.lastIndexOf(".")+1);
              // xxxFileEditor, on close, calls doRegainFocus - so we need to call doLoseFocus first, otherwise the
                // Files pane content disappears and you cant get it back.
              Gatherer.g_man.doLoseFocus();
              TextFileEditor editor;
              if (ext.matches("xml|xsl|mds|col")) {
                editor = new XMLFileEditor(this_file);
              } else {
                editor = new TextFileEditor(this_file);
              }

              // should this be going through filemanager, in diff thread?
              editor.setVisible(true);
              editor.setSize(900,700);
             
            }

            
	    // Rename
	    else if (source == rename) {
		Gatherer.f_man.renameCollectionFile(collection_tree, node);
	    }

	    // Replace
	    else if (source == replace) {
		Gatherer.f_man.replaceCollectionFile(collection_tree, node);
	    }
  	}
    }
    }
