/*
 *    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.
 */

/*
 *    Copyright (C) 2004
 *    & Matthias Schubert (schubert@dbs.ifi.lmu.de)
 *    & Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
 *    & Rainer Holzmann (holzmann@cip.ifi.lmu.de)
 */

package weka.clusterers.forOPTICSAndDBScan.OPTICS_GUI;

import weka.clusterers.forOPTICSAndDBScan.DataObjects.DataObject;
import weka.core.FastVector;
import weka.core.RevisionHandler;
import weka.core.RevisionUtils;
import weka.core.Utils;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;

import javax.swing.JComponent;

/**
 * <p>
 * GraphPanel.java <br/>
 * Authors: Rainer Holzmann, Zhanna Melnikova-Albrecht <br/>
 * Date: Sep 16, 2004 <br/>
 * Time: 10:28:19 AM <br/>
 * $ Revision 1.4 $ <br/>
 * </p>
 *
 * @author Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
 * @author Rainer Holzmann (holzmann@cip.ifi.lmu.de)
 * @version $Revision: 1.4 $
 */
public class GraphPanel
    extends JComponent
    implements RevisionHandler {

    /** for serialization */
    private static final long serialVersionUID = 7917937528738361470L;

    /**
     * Holds the clustering results
     */
    private FastVector resultVector;

    /**
     * Holds the value that is multiplied with the original values of core- and reachability
     * distances in order to get better graphical views
     */
    private int verticalAdjustment;

    /**
     * Specifies the color for displaying core-distances
     */
    private Color coreDistanceColor;

    /**
     * Specifies the color for displaying reachability-distances
     */
    private Color reachabilityDistanceColor;

    /**
     * Specifies the width for displaying the distances
     */
    private int widthSlider;

    /**
     * Holds the flag for showCoreDistances
     */
    private boolean showCoreDistances;

    /**
     * Holds the flag for showrRechabilityDistances
     */
    private boolean showReachabilityDistances;

    /**
     * Holds the index of the last toolTip
     */
    private int recentIndex = -1;

    // *****************************************************************************************************************
    // constructors
    // *****************************************************************************************************************

    public GraphPanel(FastVector resultVector,
                      int verticalAdjustment,
                      boolean showCoreDistances,
                      boolean showReachbilityDistances) {
        this.resultVector = resultVector;
        this.verticalAdjustment = verticalAdjustment;
        coreDistanceColor = new Color(100, 100, 100);
        reachabilityDistanceColor = Color.orange;
        widthSlider = 5;
        this.showCoreDistances = showCoreDistances;
        this.showReachabilityDistances = showReachbilityDistances;

        addMouseMotionListener(new MouseHandler());
    }

    // *****************************************************************************************************************
    // methods
    // *****************************************************************************************************************

    /**
     * Draws the OPTICS Plot
     * @param g
     */
    protected void paintComponent(Graphics g) {
        if (isOpaque()) {
            Dimension size = getSize();
            g.setColor(getBackground());
            g.fillRect(0, 0, size.width, size.height);
        }

        int stepSize = 0;
        int cDist = 0;
        int rDist = 0;

        for (int vectorIndex = 0; vectorIndex < resultVector.size(); vectorIndex++) {
            double coreDistance = ((DataObject) resultVector.elementAt(vectorIndex)).getCoreDistance();
            double reachDistance = ((DataObject) resultVector.elementAt(vectorIndex)).getReachabilityDistance();

            if (coreDistance == DataObject.UNDEFINED)
                cDist = getHeight();
            else
                cDist = (int) (coreDistance * verticalAdjustment);

            if (reachDistance == DataObject.UNDEFINED)
                rDist = getHeight();
            else
                rDist = (int) (reachDistance * verticalAdjustment);

            int x = vectorIndex + stepSize;

            if (isShowCoreDistances()) {
                /**
                 * Draw coreDistance
                 */
                g.setColor(coreDistanceColor);
                g.fillRect(x, getHeight() - cDist, widthSlider, cDist);
            }

            if (isShowReachabilityDistances()) {
                int sizer = widthSlider;
                if (!isShowCoreDistances()) sizer = 0;
                /**
                 * Draw reachabilityDistance
                 */
                g.setColor(reachabilityDistanceColor);
                g.fillRect(x + sizer, getHeight() - rDist, widthSlider, rDist);
            }

            if (isShowCoreDistances() && isShowReachabilityDistances()) {
                stepSize += (widthSlider * 2);
            } else
                stepSize += widthSlider;
        }
    }

    /**
     * Sets a new resultVector
     * @param resultVector
     */
    public void setResultVector(FastVector resultVector) {
        this.resultVector = resultVector;
    }

    /**
     * Displays a toolTip for the selected DataObject
     * @param toolTip
     */
    public void setNewToolTip(String toolTip) {
        setToolTipText(toolTip);
    }

    /**
     * Adjusts the size of this panel in respect of the shown content
     * @param serObject SERObject that contains the OPTICS clustering results
     */
    public void adjustSize(SERObject serObject) {
        int i = 0;
        if (isShowCoreDistances() && isShowReachabilityDistances())
            i = 10;
        else if ((isShowCoreDistances() && !isShowReachabilityDistances()) ||
                !isShowCoreDistances() && isShowReachabilityDistances())
            i = 5;
        setSize(new Dimension((i * serObject.getDatabaseSize()) +
                serObject.getDatabaseSize(), getHeight()));
        setPreferredSize(new Dimension((i * serObject.getDatabaseSize()) +
                serObject.getDatabaseSize(), getHeight()));
    }

    /**
     * Returns the flag for showCoreDistances
     * @return True or false
     */
    public boolean isShowCoreDistances() {
        return showCoreDistances;
    }

    /**
     * Sets the flag for showCoreDistances
     * @param showCoreDistances
     */
    public void setShowCoreDistances(boolean showCoreDistances) {
        this.showCoreDistances = showCoreDistances;
    }

    /**
     * Returns the flag for showReachabilityDistances
     * @return True or false
     */
    public boolean isShowReachabilityDistances() {
        return showReachabilityDistances;
    }

    /**
     * Sets the flag for showReachabilityDistances
     * @param showReachabilityDistances
     */
    public void setShowReachabilityDistances(boolean showReachabilityDistances) {
        this.showReachabilityDistances = showReachabilityDistances;
    }

    /**
     * Sets a new value for the vertical verticalAdjustment
     * @param verticalAdjustment
     */
    public void setVerticalAdjustment(int verticalAdjustment) {
        this.verticalAdjustment = verticalAdjustment;
    }

    /**
     * Sets a new color for the coreDistance
     * @param coreDistanceColor
     */
    public void setCoreDistanceColor(Color coreDistanceColor) {
        this.coreDistanceColor = coreDistanceColor;
        repaint();
    }

    /**
     * Sets a new color for the reachabilityDistance
     * @param reachabilityDistanceColor
     */
    public void setReachabilityDistanceColor(Color reachabilityDistanceColor) {
        this.reachabilityDistanceColor = reachabilityDistanceColor;
        repaint();
    }

    // *****************************************************************************************************************
    // inner classes
    // *****************************************************************************************************************

    private class MouseHandler
         extends MouseMotionAdapter
         implements RevisionHandler {
      
        /**
         * Invoked when the mouse button has been moved on a component
         * (with no buttons no down).
         */
        public void mouseMoved(MouseEvent e) {
            showToolTip(e.getX());
        }

        /**
         * Shows a toolTip with the dataObjects parameters (c-dist, r-dist, key, attributes . . .)
         * @param x MouseCoordinate X
         * @return boolean
         */
        private boolean showToolTip(int x) {
            int i = 0;
            if (isShowCoreDistances() && isShowReachabilityDistances())
                i = 11;
            else if ((isShowCoreDistances() && !isShowReachabilityDistances()) ||
                    !isShowCoreDistances() && isShowReachabilityDistances() ||
                    !isShowCoreDistances() && !isShowReachabilityDistances())
                i = 6;
            if ((x / i) == recentIndex)
                return false;
            else
                recentIndex = x / i;
            DataObject dataObject = null;
            try {
                dataObject = (DataObject) resultVector.elementAt(recentIndex);
            } catch (Exception e) {
            }
            if (dataObject != null) {
                if (!isShowCoreDistances() && !isShowReachabilityDistances()) {
                    setNewToolTip("<html><body><b>Please select a distance" +
                            "</b></body></html>"
                    );
                } else
                    setNewToolTip("<html><body><table>" +
                            "<tr><td>DataObject:</td><td>" + dataObject + "</td></tr>" +
                            "<tr><td>Key:</td><td>" + dataObject.getKey() + "</td></tr>" +
                            "<tr><td>" +
                            (isShowCoreDistances() ? "<b>" : "") + "Core-Distance:" +
                            (isShowCoreDistances() ? "</b>" : "") +
                            "</td><td>" +
                            (isShowCoreDistances() ? "<b>" : "") +
                            ((dataObject.getCoreDistance() == DataObject.UNDEFINED) ? "UNDEFINED" :
                            Utils.doubleToString(dataObject.getCoreDistance(), 3, 5)) +
                            (isShowCoreDistances() ? "</b>" : "") +
                            "</td></tr>" +
                            "<tr><td>" +
                            (isShowReachabilityDistances() ? "<b>" : "") + "Reachability-Distance:" +
                            (isShowReachabilityDistances() ? "</b>" : "") +
                            "</td><td>" +
                            (isShowReachabilityDistances() ? "<b>" : "") +
                            ((dataObject.getReachabilityDistance() == DataObject.UNDEFINED) ? "UNDEFINED" :
                            Utils.doubleToString(dataObject.getReachabilityDistance(), 3, 5)) +
                            (isShowReachabilityDistances() ? "</b>" : "") +
                            "</td></tr>" +
                            "</table></body></html>"
                    );
            }
            return true;
        }
        
        /**
         * Returns the revision string.
         * 
         * @return		the revision
         */
        public String getRevision() {
          return RevisionUtils.extract("$Revision: 1.4 $");
        }
    }
    
    /**
     * Returns the revision string.
     * 
     * @return		the revision
     */
    public String getRevision() {
      return RevisionUtils.extract("$Revision: 1.4 $");
    }
}
