/*
 * File:     SelectableResultTextPane.java
 * Project:  MPI Linguistic Application
 * Date:     03 April 2006
 *
 * Copyright (C) 2001-2006  Max Planck Institute for Psycholinguistics
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/* 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package mpi.search.viewer;

import mpi.search.model.ResultHandler;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionAdapter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;

import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;


/**
 * Adds the possibility to select a match(=string) and send its number to the ResultHandler;
 * Furthermore tooltip can be shown corresponding to each appended string $Id:
 * SelectableResultTextPane.java,v 1.4 2005/06/03 13:49:32 klasal Exp $
 *
 * @author $Author: klasal $
 * @version $Revision: 1.7 $
 */
public class SelectableResultTextPane extends ResultTextPane
    implements MouseListener {
    private static final Dimension minimalSize = new Dimension(200, 200);

    /** list of tooltip texts */
    private final ArrayList tooltipTexts = new ArrayList();

    /** remembers hightlighted substrings */
    private final Hashtable substringHighlights;

    /** to choose an action which the resulthandler should perform */
    private JPopupMenu actionsMenu;

    /** remembers for each added String the corresponding match number */
    private final List matchNumbers;

    /** remembers the begin positions of each inserted match-string */
    private final List resultBeginPositions;

    /** handler that does something with selected match number */
    private ResultHandler resultHandler;

    /** style of (mouse-)selected text */
    private final Style selected;

    /** previous selected match number */
    private int previousSelectedStringNumber = -1;

    /** selected match number */
    private int selectedStringNumber = -1;

    public SelectableResultTextPane() {
        this(null);
    }

    /**
     * Creates a new ResultTextPane object.
     *
     * @param resultHandler DOCUMENT ME!
     */
    public SelectableResultTextPane(ResultHandler resultHandler) {
        super();
        this.resultHandler = resultHandler;
        setEditable(false);
        resultBeginPositions = new ArrayList();
        matchNumbers = new ArrayList();
        substringHighlights = new Hashtable();
        selected = addStyle("selected", regular);
        StyleConstants.setBackground(selected, getSelectionColor());
        addMouseListener(this);
        addMouseMotionListener(new MouseMotionAdapter() {
                public void mouseMoved(MouseEvent e) {
                    int stringNumber = getStringNumberAtPosition(viewToModel(
                                e.getPoint()));

                    if (stringNumber > 0) {
                        setToolTipText((String) tooltipTexts.get(stringNumber -
                                1));
                    }
                }
            });
    }

    /**
     * Creates a new SelectableResultTextPane object.
     *
     * @param resultHandler
     * @param popupChoices popupMenu is built with these strings; if selected,
     *        resultHandler.handleMatch(matchnumber, selectedPopupString) is called
     */
    public SelectableResultTextPane(ResultHandler resultHandler,
        String[] popupChoices) {
        this(resultHandler);
        setPopupChoices(popupChoices);
    }

    public void setResultHandler(ResultHandler resultHandler) {
        this.resultHandler = resultHandler;
    }

    /**
     * DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public Dimension getMinimumSize() {
        return minimalSize;
    }

    /**
     * DOCUMENT ME!
     *
     * @param popupChoices DOCUMENT ME!
     */
    public void setPopupChoices(String[] popupChoices) {
        if (popupChoices.length > 0) {
            actionsMenu = new JPopupMenu();

            ActionListener actionListener = new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        if (selectedStringNumber >= 0) {
                            resultHandler.handleMatch(getMatchNumberForString(
                                    selectedStringNumber), e.getActionCommand());
                        }
                    }
                };

            JMenuItem menuItem;

            for (int i = 0; i < popupChoices.length; i++) {
                menuItem = new JMenuItem(popupChoices[i]);
                menuItem.addActionListener(actionListener);
                actionsMenu.add(menuItem);
            }
        } else {
            actionsMenu = null;
        }
    }

    /**
     * DOCUMENT ME!
     *
     * @param s String to be appended in document
     */
    public void appendString(String s) {
        appendString(s, (String) null);
    }

    /**
     * DOCUMENT ME!
     *
     * @param s String to be appended in document
     * @param t tooltip text
     */
    public void appendString(String s, String t) {
        appendString(s, t, matchNumbers.size() + 1);
    }

    /**
     * DOCUMENT ME!
     *
     * @param s String to be appended in document
     * @param t tooltip Text
     * @param matchNumber corresponding match number
     */
    public void appendString(String s, String t, int matchNumber) {
        resultBeginPositions.add(new Integer(getDocument().getLength()));
        matchNumbers.add(new Integer(matchNumber));
        tooltipTexts.add(t);
        super.appendString(s);
    }

    /**
     * DOCUMENT ME!
     *
     * @param s String to be appended
     * @param positions parts of the string that shall be highlighted
     */
    public void appendString(String s, int[][] positions) {
        appendString(s, null, positions);
    }

    /**
     * DOCUMENT ME!
     *
     * @param s String to be appended
     * @param t tooltip text
     * @param positions parts of the string that shall be highlighted
     */
    public void appendString(String s, String t, int[][] positions) {
        appendString(s, t, positions, resultBeginPositions.size() + 1);
    }

    /**
     * DOCUMENT ME!
     *
     * @param s String to be appended
     * @param t tooltip text
     * @param positions parts of the string that shall be highlighted
     * @param matchNumber correspodnign match number
     */
    public void appendString(String s, String t, int[][] positions,
        int matchNumber) {
        resultBeginPositions.add(new Integer(getDocument().getLength()));
        substringHighlights.put(new Integer(resultBeginPositions.size()),
            positions);
        tooltipTexts.add(t);
        matchNumbers.add(new Integer(matchNumber));
        super.appendString(s, positions);
    }

    /**
     * DOCUMENT ME!
     *
     * @param e DOCUMENT ME!
     */
    public void mouseClicked(MouseEvent e) {
        selectedStringNumber = getStringNumberAtPosition(viewToModel(
                    e.getPoint()));

        if (e.getClickCount() == 1) {
            highlightSelectedMatch();
        } else if (e.getClickCount() > 1) {
            // first registered action is considered default action
            if ((actionsMenu != null) &&
                    (actionsMenu.getSubElements().length > 0) &&
                    (selectedStringNumber >= 0)) {
                resultHandler.handleMatch(getMatchNumberForString(
                        selectedStringNumber),
                    ((JMenuItem) actionsMenu.getSubElements()[0]).getActionCommand());
            }
        }
    }

    /**
     * DOCUMENT ME!
     *
     * @param e DOCUMENT ME!
     */
    public void mouseEntered(MouseEvent e) {
    }

    /**
     * DOCUMENT ME!
     *
     * @param e DOCUMENT ME!
     */
    public void mouseExited(MouseEvent e) {
    }

    /**
     * Class for handling the mouse clicks in the result pane.
     *
     * @param e DOCUMENT ME!
     */

    // on a pc this is the popup trigger method
    public void mousePressed(MouseEvent e) {
        if ((actionsMenu != null) && e.isPopupTrigger()) {
            showPopup(e);
        }
    }

    // On the sun this is the popup trigger method
    public void mouseReleased(MouseEvent e) {
        if ((actionsMenu != null) && e.isPopupTrigger()) {
            showPopup(e);
        }
    }

    /**
     * Resets the result panel to progress(0), hits(0) and no text in the result panel.
     */
    public void reset() {
        resultBeginPositions.clear();
        matchNumbers.clear();
        substringHighlights.clear();
        tooltipTexts.clear();
        super.reset();
    }

    /**
     * returns match number for corresponding string number if no match number has been set
     * explicitly, then match number is equal to string number
     *
     * @param stringNumber
     *
     * @return
     */
    private int getMatchNumberForString(int stringNumber) {
        return (stringNumber > 0)
        ? ((Integer) matchNumbers.get(stringNumber - 1)).intValue() : 0;
    }

    private int[] getPositionOfMatch(int matchNumber) {
        int[] position = null;

        try {
            Integer start = (Integer) resultBeginPositions.get(matchNumber - 1);
            Integer end = (matchNumber < resultBeginPositions.size())
                ? (Integer) resultBeginPositions.get(matchNumber)
                : new Integer(getDocument().getLength());
            position = new int[] { start.intValue(), end.intValue() - 1 };
        } catch (Exception ble) {
            ; //BadLocationException
        }

        return position;
    }

    /**
     * Gives the string number that belongs to the displayed string at the given position in the
     * document. Note: First string has number 1! Returns 0, if no corresponding string is found
     *
     * @param position the position at which the result is displayed in the result Pane.
     *
     * @return match number
     */
    private int getStringNumberAtPosition(int position) {
        int pos = Arrays.binarySearch(resultBeginPositions.toArray(),
                new Integer(position));

        //if position not contained in list, eg. position is not on a result
        // boundary,
        //binarySearch returns -insertPoint - 1
        if (pos < 0) {
            pos = -pos - 2;
        }

        return pos + 1;
    }

    private void highlightSelectedMatch() {
        int[] position;

        String s = null;

        if (previousSelectedStringNumber != -1) {
            position = getPositionOfMatch(previousSelectedStringNumber);

            try {
                s = getDocument().getText(position[0],
                        position[1] - position[0] + 1);
                getDocument().remove(position[0], position[1] - position[0] +
                    1);

                int[][] substringPositions = (int[][]) substringHighlights.get(new Integer(
                            previousSelectedStringNumber));
                insertString(position[0], s, substringPositions);
            } catch (BadLocationException e1) {
                ;
            }
             //badlocation
        }

        position = getPositionOfMatch(selectedStringNumber);

        if (position != null) {
            try {
                s = getDocument().getText(position[0],
                        position[1] - position[0] + 1);
                getDocument().remove(position[0], position[1] - position[0] +
                    1);
                getDocument().insertString(position[0], s, selected);
            } catch (BadLocationException e2) {
                ;
            }

            previousSelectedStringNumber = selectedStringNumber;
        } else {
            previousSelectedStringNumber = -1;
        }
    }

    private void showPopup(MouseEvent e) {
        selectedStringNumber = getStringNumberAtPosition(viewToModel(
                    e.getPoint()));
        highlightSelectedMatch();

        //System.out.println("Selected match number: " + getMatchNumberForString(selectedStringNumber));
        if (selectedStringNumber > 0) {
            actionsMenu.show(SelectableResultTextPane.this, e.getX(), e.getY());
            actionsMenu.setVisible(true);
            requestFocus();
        }
    }
}
