/* ===========================================================

 * JFreeChart : a free chart library for the Java(tm) platform

 * ===========================================================

 *

 * (C) Copyright 2000-2004, by Object Refinery Limited and Contributors.

 *

 * Project Info:  http://www.jfree.org/jfreechart/index.html

 *

 * This library is free software; you can redistribute it and/or modify it under the terms

 * of the GNU Lesser General Public License as published by the Free Software Foundation;

 * either version 2.1 of the License, or (at your option) any later version.

 *

 * This library 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 Lesser General Public License for more details.

 *

 * You should have received a copy of the GNU Lesser General Public License along with this

 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,

 * Boston, MA 02111-1307, USA.

 *

 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 

 * in the United States and other countries.]

 *

 * ---------------------

 * WaferMapRenderer.java

 * ---------------------

 * (C) Copyright 2003, 2004, by Robert Redburn and Contributors.

 *

 * Original Author:  Robert Redburn;

 * Contributor(s):   David Gilbert (for Object Refinery Limited);

 *

 * $Id: WaferMapRenderer.java,v 1.1 2004/08/31 14:45:01 mungady Exp $

 *

 * Changes

 * -------

 * 25-Nov-2003 : Version 1, contributed by Robert Redburn.  Changes have been made to fit the

 *               JFreeChart coding style (DG);

 *

 */



package org.jfree.chart.renderer;



import java.awt.Color;

import java.awt.Paint;

import java.awt.Shape;

import java.awt.Stroke;

import java.awt.geom.Rectangle2D;

import java.util.HashMap;

import java.util.HashSet;

import java.util.Iterator;

import java.util.Map;

import java.util.Set;



import org.jfree.chart.LegendItem;

import org.jfree.chart.LegendItemCollection;

import org.jfree.chart.plot.DrawingSupplier;

import org.jfree.chart.plot.WaferMapPlot;

import org.jfree.data.general.WaferMapDataset;



/**

 * A renderer for wafer map plots.  Provides color managment facilities.

 * 

 * @author Robert Redburn.

 */

public class WaferMapRenderer extends AbstractRenderer {



    /** paint index */

    private Map paintIndex;

    

    /** plot */

    private WaferMapPlot plot;

    

    /** paint limit */

    private int paintLimit;

    

    /** default paint limit */

    private static final int DEFAULT_PAINT_LIMIT = 35;  

    

    /** default multivalue paint calculation */

    public static final int POSITION_INDEX = 0;

    

    /** The default value index. */

    public static final int VALUE_INDEX = 1;

    

    /** paint index method */

    private int paintIndexMethod;

    

    /**

     * Creates a new renderer.

     */

    public WaferMapRenderer() {

        this(null, null);

    }

    

    /**

     * Creates a new renderer.

     * 

     * @param paintLimit  the paint limit.

     * @param paintIndexMethod  the paint index method.

     */

    public WaferMapRenderer(int paintLimit, int paintIndexMethod) {

        this(new Integer(paintLimit), new Integer(paintIndexMethod));

    }

    

    /**

     * Creates a new renderer.

     * 

     * @param paintLimit  the paint limit.

     * @param paintIndexMethod  the paint index method.

     */

    public WaferMapRenderer(Integer paintLimit, Integer paintIndexMethod) {

        

        super();

        this.paintIndex = new HashMap();

        

        if (paintLimit == null) {

            this.paintLimit = DEFAULT_PAINT_LIMIT;

        }

        else {

            this.paintLimit = paintLimit.intValue();

        }

        

        this.paintIndexMethod = VALUE_INDEX;

        if (paintIndexMethod != null) {

            if (isMethodValid(paintIndexMethod.intValue())) { 

                this.paintIndexMethod = paintIndexMethod.intValue();

            }

        }

    }



    /**

     * Verifies that the passed paint index method is valid.

     * 

     * @param method  the method.

     * 

     * @return true or false.

     */

    private boolean isMethodValid(int method) {

        switch (method) {

            case POSITION_INDEX: return true;

            case VALUE_INDEX:    return true;

            default: return false;

        }

    }



    /**

     * Returns the drawing supplier from the plot.

     * 

     * @return the drawing supplier.

     */

    public DrawingSupplier getDrawingSupplier() {

        DrawingSupplier result = null;

        WaferMapPlot p = getPlot();

        if (p != null) {

            result = p.getDrawingSupplier();

        }

        return result;

    }



    /**

     * Returns the plot.

     * 

     * @return the plot.

     */

    public WaferMapPlot getPlot() {

        return this.plot;

    }



    /**

     * Sets the plot and build the paint index.

     * 

     * @param plot  the plot.

     */

    public void setPlot(WaferMapPlot plot) {

        this.plot = plot;

        makePaintIndex();

    }

    

    /**

     * 

     */

//    public void addPropertyChangeListener(PropertyChangeListener ear) {

        

//    }

    

    /**

     * Returns the paint for a given chip value.

     * 

     * @param value  the value.

     * 

     * @return the paint.

     */

    public Paint getChipColor(Number value) {

        return getSeriesPaint(getPaintIndex(value));

    }

    

    /**

     * Returns the paint index for a given chip value.

     * 

     * @param value  the value.

     * 

     * @return the paint index.

     */

    private int getPaintIndex(Number value) {

        return ((Integer) this.paintIndex.get(value)).intValue();

    }

    

    /**

     * Builds a map of chip values to paint colors.

     * paintlimit is the maximum allowed number of colors.

     */

    private void makePaintIndex() {

        if (this.plot == null) {

            return;

        }

        WaferMapDataset data = this.plot.getDataset();

        Number dataMin = data.getMinValue();

        Number dataMax = data.getMaxValue();

        Set uniqueValues = data.getUniqueValues();

        if (uniqueValues.size() <= this.paintLimit) {

            int count = 0; // assign a color for each unique value

            for (Iterator i = uniqueValues.iterator(); i.hasNext();) {

                this.paintIndex.put(i.next(), new Integer(count++));

            }

        }

        else {  

            // more values than paints so map

            // multiple values to the same color

            switch (this.paintIndexMethod) {

                case POSITION_INDEX: 

                    makePositionIndex(uniqueValues); 

                    break;

                case VALUE_INDEX:    

                    makeValueIndex(dataMax, dataMin, uniqueValues); 

                    break;

                default:

                    break;

            }

        }

    }

        

    /**

     * Builds the paintindex by assigning colors based on the number 

     * of unique values: totalvalues/totalcolors.

     * 

     * @param uniqueValues  the set of unique values.

     */

    private void makePositionIndex(Set uniqueValues) {

        int valuesPerColor = (int) Math.ceil((double) uniqueValues.size() / this.paintLimit);

        int count = 0; // assign a color for each unique value

        int paint = 0;

        for (Iterator i = uniqueValues.iterator(); i.hasNext();) {

            this.paintIndex.put(i.next(), new Integer(paint));

            if (++count % valuesPerColor == 0) {

                paint++;

            }

            if (paint > this.paintLimit) {

                paint = this.paintLimit;

            }

        }

    }



    /**

     * Builds the paintindex by assigning colors evenly across the range

     * of values:  maxValue-minValue/totalcolors

     * 

     * @param max  the maximum value.

     * @param min  the minumum value.

     * @param uniqueValues  the unique values.

     */

    private void makeValueIndex(Number max, Number min, Set uniqueValues) {

        double valueRange = max.doubleValue() - min.doubleValue();

        double valueStep = valueRange / this.paintLimit;

        int paint = 0;

        double cutPoint = min.doubleValue() + valueStep;

        for (Iterator i = uniqueValues.iterator(); i.hasNext();) {

            Number value = (Number) i.next();

            while (value.doubleValue() > cutPoint) {

                cutPoint += valueStep;

                paint++;

                if (paint > this.paintLimit) {

                    paint = this.paintLimit;

                }

            } 

            this.paintIndex.put(value, new Integer(paint));

        }

    }



    /**

     * Builds the list of legend entries.  called by getLegendItems in

     * WaferMapPlot to populate the plot legend.

     * 

     * @return the legend items.

     */

    public LegendItemCollection getLegendCollection() {

        LegendItemCollection result = new LegendItemCollection();

        if (this.paintIndex != null && this.paintIndex.size() > 0) {

            if (this.paintIndex.size() <= this.paintLimit) {

                for (Iterator i = this.paintIndex.entrySet().iterator(); i.hasNext();) {

                    // in this case, every color has a unique value

                    Map.Entry entry =  (Map.Entry) i.next();

                    String label = entry.getKey().toString();

                    String description = label;

                    Shape shape = new Rectangle2D.Double(1d, 1d, 1d, 1d);

                    Paint paint = this.getSeriesPaint(((Integer) entry.getValue()).intValue());

                    Paint outlinePaint = Color.black;

                    Stroke stroke = DEFAULT_STROKE;



                    result.add(new LegendItem(

                        label, description, shape, true, paint, stroke, outlinePaint, stroke

                    ));

                    

                }               

            }

            else {

                // in this case, every color has a range of values

                Set unique = new HashSet();

                for (Iterator i = this.paintIndex.entrySet().iterator(); i.hasNext();) {

                    Map.Entry entry = (Map.Entry) i.next();

                    if (unique.add(entry.getValue())) {

                        String label = getMinPaintValue((Integer) entry.getValue()).toString()

                            + " - " + getMaxPaintValue((Integer) entry.getValue()).toString();

                        String description = label;

                        Shape shape = new Rectangle2D.Double(1d, 1d, 1d, 1d);

                        Paint paint = getSeriesPaint(((Integer) entry.getValue()).intValue());

                        Paint outlinePaint = Color.black;

                        Stroke stroke = DEFAULT_STROKE;



                        result.add(new LegendItem(

                            label, description, shape, true, paint, stroke, outlinePaint, stroke

                        ));

                    }

                } // end foreach map entry

            } // end else

        }

        return result;

    }



    /**

     * Returns the minimum chip value assigned to a color

     * in the paintIndex

     * 

     * @param index  the index.

     * 

     * @return the value.

     */

    private Number getMinPaintValue(Integer index) {

        double minValue = Double.POSITIVE_INFINITY;

        for (Iterator i = this.paintIndex.entrySet().iterator(); i.hasNext();) {

            Map.Entry entry = (Map.Entry) i.next();

            if (((Integer) entry.getValue()).equals(index)) {

                if (((Number) entry.getKey()).doubleValue() < minValue) {

                    minValue = ((Number) entry.getKey()).doubleValue();

                }

            }

        }               

        return new Double(minValue);

    }

    

    /**

     * Returns the maximum chip value assigned to a color

     * in the paintIndex

     * 

     * @param index  the index.

     * 

     * @return the value

     */

    private Number getMaxPaintValue(Integer index) {

        double maxValue = Double.NEGATIVE_INFINITY;

        for (Iterator i = this.paintIndex.entrySet().iterator(); i.hasNext();) {

            Map.Entry entry = (Map.Entry) i.next();

            if (((Integer) entry.getValue()).equals(index)) {

                if (((Number) entry.getKey()).doubleValue() > maxValue) {

                    maxValue = ((Number) entry.getKey()).doubleValue();

                }

            }

        }               

        return new Double(maxValue);

    }





} // end class wafermaprenderer

