/**
 *#########################################################################
 *
 * A component of the Gatherer application, part of the Greenstone digital
 * library suite from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * <BR><BR>
 *
 * Author: John Thompson, Greenstone Digital Library, University of Waikato
 *
 * <BR><BR>
 *
 * Copyright (C) 1999 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.util;

import java.awt.Point;
import java.awt.event.*;
import javax.swing.tree.*;
import org.greenstone.gatherer.DebugStream;
import org.greenstone.gatherer.Dictionary;
import org.greenstone.gatherer.gui.tree.DragTree;

public class DragTreeSelectionModel
    extends DefaultTreeSelectionModel
    implements MouseListener {

    private boolean immediate = false;
    private boolean mixed_selection = false;

    private int type               = -1;

    private TreePath temp_path     = null;
    private TreePath temp_paths[]  = null;

    static private final int NONE  = -1;
    static private final int ADD   =  0;
    static private final int SET   =  1;

    /** Constructor.
     * @param tree the DragTree which will use this selection model.
     * @param mixed_selection true if this selection model allows selections to contain files and folders, false otherwise.
     */
    public DragTreeSelectionModel(DragTree tree, boolean mixed_selection) {
	super();
	this.mixed_selection = mixed_selection;
	setSelectionMode(DISCONTIGUOUS_TREE_SELECTION);
	tree.addMouseListener(this);
    }

    public void addSelectionPath(TreePath path) {
	if(!immediate) {
	    this.temp_path = path;
	    this.type = this.ADD;
	}
	else if(isAppropriate(path)) {
	    temp_path = null;
	    super.addSelectionPath(path);
	}
    }

    public void addSelectionPaths(TreePath paths[]) {
	if(!immediate) {
	    this.temp_paths = paths;
	    this.type = this.ADD;
	}
	else if(isAppropriate(paths, true)) {
	    temp_paths = null;
	    super.setSelectionPaths(paths);
	}
    }

    public String getDetails() {
	String suffix = null;
	int file_count = 0;
	int folder_count = 0;
	for(int i = 0; selection != null && i < selection.length; i++) {
	    TreeNode node = (TreeNode) selection[i].getLastPathComponent();
	    if(node.isLeaf()) {
		file_count++;
	    }
	    else {
		folder_count++;
	    }
	}

	// Update the metaaudit_suffix
	String args[] = null;
	if(file_count > 0 && folder_count > 0) {
	    if(file_count > 1 && folder_count > 1) {
		args = new String[2];
		args[0] = String.valueOf(file_count);
		args[1] = String.valueOf(folder_count);
		suffix = Dictionary.get("FileActions.Selected", args);
	    }
	    else if(file_count > 1) {
		args = new String[1];
		args[0] = String.valueOf(file_count);
		suffix = Dictionary.get("FileActions.Files_And_Directory_Selected", args);
	    }
	    else if(folder_count > 1) {
		args = new String[1];
		args[0] = String.valueOf(folder_count);
		suffix = Dictionary.get("FileActions.File_And_Directories_Selected", args);
	    }
	    else {
		suffix = Dictionary.get("FileActions.File_And_Directory_Selected");
	    }
	}
	else if(file_count > 0) {
	    if(file_count > 1) {
		args = new String[1];
		args[0] = String.valueOf(file_count);
		suffix = Dictionary.get("FileActions.Files_Selected", args);
	    }
	    else if(file_count == 1) {
		suffix = Dictionary.get("FileActions.File_Selected");
	    }
	}
	else if(folder_count > 0) {
	    if(folder_count > 1) {
		args = new String[1];
		args[0] = String.valueOf(folder_count);
		suffix = Dictionary.get("FileActions.Directories_Selected", args);
	    }
	    else {
		suffix = Dictionary.get("FileActions.Directory_Selected");
	    }
	}
	args = null;

	return suffix;
    }

    /** Any implementation of MouseListener must include this method so
     * we can be informed when the mouse is clicked.
     * @param event A MouseEvent containing all the information about
     * this mouse click.
     */
    public void mouseClicked(MouseEvent event) {
    }

    /** Any implementation of MouseListener must include this method so
     * we can be informed when the mouse enters a component.
     * @param event A MouseEvent containing all the information about
     * this mouse action.
     */
    public void mouseEntered(MouseEvent event) {
    }

    /** Any implementation of MouseListener must include this method so
     * we can be informed when the mouse exits a component.
     * @param event A MouseEvent containing all the information about
     * this mouse action.
     */
    public void mouseExited(MouseEvent event) {
    }

    /** Any implementation of MouseListener must include this method so
     * we can be informed when the mouse is pressed (start of click).
     * @param event A MouseEvent containing all the information about
     * this mouse action.
     */
    public void mousePressed(MouseEvent event) {
    }

    /** Any implementation of MouseListener must include this method so
     * we can be informed when the mouse is released (end of click).
     * @param event A MouseEvent containing all the information about
     * this mouse action.
     */
    public void mouseReleased(MouseEvent event) {
	try {
	switch(this.type) {
	case 0: // this.ADD
	    if(this.temp_path != null && isAppropriate(temp_path)) {
		super.addSelectionPath(this.temp_path);
		this.temp_path = null;
	    }
	    if(this.temp_paths != null && isAppropriate(temp_paths, true)) {
		super.addSelectionPaths(this.temp_paths);
		this.temp_paths = null;
	    }
	    this.type = this.NONE;
	    break;
	case 1: // this.SET
	    if(this.temp_path != null) {
		super.setSelectionPath(this.temp_path);
		this.temp_path = null;
	    }
	    if(this.temp_paths != null && isAppropriate(temp_paths, false)) {
		super.setSelectionPaths(this.temp_paths);
		this.temp_paths = null;
	    }
	    this.type = this.NONE;
	    break;
	}
	}
	catch (Exception error) {
		DebugStream.printStackTrace(error);
	}
    }

    public void setImmediate(boolean value) {
	immediate = value;
    }

    public void setSelectionPath(TreePath path) {
	// Since this is only a single path we don't need to check whether its an appropriate selection.
	if(!immediate) {
	    this.temp_path = path;
	    this.type = this.SET;
	}
	else {
	    temp_path = null;
	    super.setSelectionPath(path);
	}
    }

    public void setSelectionPaths(TreePath paths[]) {
	if(!immediate) {
	    this.temp_paths = paths;
	    this.type = this.SET;
	}
	else if(isAppropriate(paths, false)) {
	    temp_paths = null;
	    super.setSelectionPaths(paths);
	}
    }

    /** Ensure that the given path is appropriate to add to the current selection, preventing mixed selections of files and folder if required. We also must check that no path is a ancestor/descendant of another.
     */
    private boolean isAppropriate(TreePath path) {
	boolean appropriate = true;
	TreeNode new_node = (TreeNode) path.getLastPathComponent();
	// If there is a current selection
	if(selection != null && selection.length > 0) {
	    for(int i = 0; appropriate && i < selection.length; i++) {
		TreeNode current_node = (TreeNode) selection[i].getLastPathComponent();
		appropriate = appropriate && (mixed_selection || new_node.isLeaf() == current_node.isLeaf());
		appropriate = appropriate && !path.isDescendant(selection[i]) && !selection[i].isDescendant(path);
	    }
	}
	return appropriate;
    }
    /** Ensure that the given paths are appropriate to add to the current selection, preventing mixed selections of files and folder if required. We also must check that no path is a ancestor/descendant of another. One last detail to keep in mind is that adding selections depends upon the current selection, whereas set the selection paths doesn't (replaces them instead) and thus no check of the current paths is needed. */
    private boolean isAppropriate(TreePath[] paths, boolean check_current) {
	boolean appropriate = true;
	if(check_current && paths != null && paths.length > 0) {
	    for(int i = 0; appropriate && i < paths.length; i++) {
		appropriate = appropriate && isAppropriate(paths[i]);
	    }
	}
	return appropriate;
    }
}
