/* ===========================================================
 * Orson Charts : a 3D chart library for the Java(tm) platform
 * ===========================================================
 * 
 * (C)opyright 2013-2016, by Object Refinery Limited.  All rights reserved.
 * 
 * http://www.object-refinery.com/orsoncharts/index.html
 * 
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 * 
 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
 * Other names may be trademarks of their respective owners.]
 * 
 * If you do not wish to be bound by the terms of the GPL, an alternative
 * commercial license can be purchased.  For details, please see visit the
 * Orson Charts home page:
 * 
 * http://www.object-refinery.com/orsoncharts/index.html
 * 
 */

package com.orsoncharts.renderer.category;

import com.orsoncharts.Range;
import com.orsoncharts.data.category.CategoryDataset3D;
import com.orsoncharts.data.DataUtils;
import com.orsoncharts.data.Values3D;
import com.orsoncharts.graphics3d.Dimension3D;
import com.orsoncharts.graphics3d.World;
import com.orsoncharts.plot.CategoryPlot3D;
import com.orsoncharts.Chart3DFactory;
import com.orsoncharts.data.KeyedValues3DItemKey;
import com.orsoncharts.graphics3d.Object3D;
import com.orsoncharts.graphics3d.Offset3D;
import com.orsoncharts.label.ItemLabelPositioning;

/**
 * A renderer that can be used with the {@link CategoryPlot3D} class to create
 * 3D stacked bar charts from data in a {@link CategoryDataset3D}.  The 
 * {@code createStackedBarChart()} method in the {@link Chart3DFactory} 
 * class will construct a chart that uses this renderer.  Here is a sample:
 * <div>
 * <object id="ABC" data="../../../../doc-files/StackedBarChart3DDemo1.svg"  
 * type="image/svg+xml" width="500" height="359"></object>
 * </div>
 * (refer to {@code StackedBarChart3DDemo1.java} for the code to generate 
 * the above chart).
 * <br><br> 
 * There is a factory method to create a chart using this renderer - see
 * {@link Chart3DFactory#createStackedBarChart(String, String, CategoryDataset3D, String, String, String)}.
 * <br><br>
 * NOTE: This class is serializable, but the serialization format is subject 
 * to change in future releases and should not be relied upon for persisting 
 * instances of this class.
 */
@SuppressWarnings("serial")
public class StackedBarRenderer3D extends BarRenderer3D {

    /**
     * Creates a default constructor.
     */
    public StackedBarRenderer3D() {
        super();
        setItemLabelPositioning(ItemLabelPositioning.FRONT_AND_BACK);
        setItemLabelOffsets(new Offset3D(0.0, 0.0, -1.0));
    }
    
    /**
     * Returns the range of values that will be required on the value axis
     * to see all the data from the dataset.  We override the method to 
     * account for the bars from each series being stacked on top of one 
     * another.
     * 
     * @param data  the data ({@code null} not permitted).
     * 
     * @return The range (possibly {@code null}) 
     */
    @Override
    public Range findValueRange(Values3D<? extends Number> data) {
        return DataUtils.findStackedValueRange(data);
    }
    
    /**
     * Constructs and places one item from the specified dataset into the given 
     * world.  This method will be called by the {@link CategoryPlot3D} class
     * while iterating over the items in the dataset.
     * 
     * @param dataset  the dataset ({@code null} not permitted).
     * @param series  the series index.
     * @param row  the row index.
     * @param column  the column index.
     * @param world  the world ({@code null} not permitted).
     * @param dimensions  the plot dimensions ({@code null} not permitted).
     * @param xOffset  the x-offset.
     * @param yOffset  the y-offset.
     * @param zOffset  the z-offset.
     */
    @Override
    @SuppressWarnings("unchecked")
    public void composeItem(CategoryDataset3D dataset, int series, int row, 
            int column, World world, Dimension3D dimensions,  
            double xOffset, double yOffset, double zOffset) {
        
        double value = dataset.getDoubleValue(series, row, column);
        if (Double.isNaN(value)) {
            return;
        }        
        double[] stack = DataUtils.stackSubTotal(dataset, getBase(), series,
                row, column);
        double lower = stack[1];
        if (value < 0.0) {
            lower = stack[0];
        }
        double upper = lower + value;
        composeItem(upper, lower, dataset, series, row, column, world, 
                dimensions, xOffset, yOffset, zOffset);
        
    }
    
    @Override
    protected void drawItemLabels(World world, CategoryDataset3D dataset, 
            KeyedValues3DItemKey itemKey, double xw, double yw, double zw, 
            double basew, boolean inverted) {
        ItemLabelPositioning positioning = getItemLabelPositioning();
        if (getItemLabelGenerator() != null) {
            String label = getItemLabelGenerator().generateItemLabel(dataset, 
                   itemKey.getSeriesKey(), itemKey.getRowKey(), 
                   itemKey.getColumnKey());
            if (label != null) {
                Dimension3D dimensions = getPlot().getDimensions();
                double dx = getItemLabelOffsets().getDX();
                double dy = getItemLabelOffsets().getDY() 
                        * dimensions.getHeight();
                double dz = getItemLabelOffsets().getDZ() * getBarZWidth();
                if (positioning.equals(ItemLabelPositioning.CENTRAL)) {
                    double yy = yw;
                    if (inverted) {
                        yy = basew;
                        dy = -dy;
                    }
                    Object3D labelObj = Object3D.createLabelObject(label, 
                            getItemLabelFont(), getItemLabelColor(), 
                            getItemLabelBackgroundColor(), xw + dx, 
                            yy + dy, zw, false, true);
                    labelObj.setProperty(Object3D.ITEM_KEY, itemKey);
                    world.add(labelObj);
                } else if (positioning.equals(
                        ItemLabelPositioning.FRONT_AND_BACK)) {
                    double yy = (yw + basew) / 2.0;
                    Object3D labelObj1 = Object3D.createLabelObject(label, 
                            getItemLabelFont(), getItemLabelColor(), 
                            getItemLabelBackgroundColor(), xw + dx, 
                            yy + dy, zw + dz, false, false);
                    labelObj1.setProperty(Object3D.ITEM_KEY, itemKey);
                    world.add(labelObj1);
                    Object3D labelObj2 = Object3D.createLabelObject(label, 
                            getItemLabelFont(), getItemLabelColor(), 
                            getItemLabelBackgroundColor(), xw + dx, 
                            yy + dy, zw - dz, true, false);
                    labelObj2.setProperty(Object3D.ITEM_KEY, itemKey);
                    world.add(labelObj2);
                }
            }
        }        
    }    
    
    /**
     * Tests this renderer for equality with an arbitrary object.
     * 
     * @param obj  the object ({@code null} permitted).
     * 
     * @return A boolean. 
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (!(obj instanceof StackedBarRenderer3D)) {
            return false;
        }
        return super.equals(obj);
    }
    
}
