/*	ZssChartExportEngine.java

	Purpose:
		
	Description:
		
	History:
		Jan 17, 2014 3:08:40 PM     2014, Created by kuro chung

	Copyright (C) 2010 Potix Corporation. All Rights Reserved.
*/

package org.zkoss.zssex.util;

import java.awt.Color;
import java.awt.Font;
import java.io.Serializable;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.block.BlockBorder;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PiePlot;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PolarPlot;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.general.PieDataset;
import org.jfree.data.xy.DefaultOHLCDataset;
import org.jfree.data.xy.DefaultXYZDataset;
import org.jfree.data.xy.OHLCDataItem;
import org.jfree.data.xy.OHLCDataset;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.data.xy.XYZDataset;
import org.jfree.ui.RectangleEdge;
import org.zkoss.zss.model.SChart;
import org.zkoss.zul.CategoryModel;
import org.zkoss.zul.Chart;
import org.zkoss.zul.ChartModel;
import org.zkoss.zul.HiLoModel;
import org.zkoss.zul.PieModel;
import org.zkoss.zul.XYModel;
import org.zkoss.zul.XYZModel;

/**
 * 
 * A utility to export jfreechart with 3.5 model directly.
 * 
 * @author kuro
 */
public class ZssChartExportEngine implements Serializable {
	private static final long serialVersionUID = 1634778378984535862L;
	
	final protected SChart _chartInfo;
	private static final String DEFAULT_HI_LO_SERIES = "High Low Data";
	static final int POS_B = 1;
	static final int POS_TR = 2;
	static final int POS_L = 3;
	static final int POS_R = 4;
	static final int POS_T = 5;
	
	public ZssChartExportEngine(SChart chartInfo) {
		this._chartInfo = chartInfo;
	}
	
	public JFreeChart drawJFreeChart() {
		
		if(_chartInfo == null)
			return null;
		
		JFreeChart jfchart = createJFreeChart(); // create chart with chart factory
		if(jfchart == null) { // prevent some unimplemented chart
			return null;
		}
		
		if(!prepareJFreeChart(jfchart)) {
			
			int fgAlpha = 128; // from ChartHelper$drawChartInner - origin value is 128
			int[] bgRGB = new int[] { 0xFF, 0xFF, 0xFF };
			int bgAlpha = 255;

			Plot plot = jfchart.getPlot();
			float alpha = (((float) fgAlpha) / 255);
			plot.setForegroundAlpha(alpha);

			alpha = (((float) bgAlpha) / 255);
			plot.setBackgroundAlpha(alpha);

			plot.setBackgroundPaint(new Color(bgRGB[0], bgRGB[1], bgRGB[2], bgAlpha));
			jfchart.setBackgroundPaint(Color.WHITE); // from ZssChartEngine$prepareJFreeChart - chart.setPaneColor("#FFFFFF");

			final Font tfont = JFreeChartHelper.getCustomFont(JFreeChartHelper.TITLE_FONT_PROPERTY_KEY);
			if (tfont != null) {
				final TextTitle title = jfchart.getTitle();
				if (title != null) {
					title.setFont(tfont);
				}
			}

			// legend font
			final Font lfont = JFreeChartHelper.getCustomFont(JFreeChartHelper.LEGEND_FONT_PROPERTY_KEY);
			if (lfont != null) {
				final org.jfree.chart.title.LegendTitle title = jfchart.getLegend();
				if (title != null) {
					title.setItemFont(lfont);
				}
			}

			if (plot instanceof PiePlot) {
				final PiePlot pplot = (PiePlot) plot;
				// Label font
				final Font xlbfont = JFreeChartHelper.getCustomFont(JFreeChartHelper.XAXIS_TICK_FONT_PROPERTY_KEY);
				if (xlbfont != null) {
					pplot.setLabelFont(xlbfont);
				}
			} else if (plot instanceof CategoryPlot) {
				final CategoryPlot cplot = (CategoryPlot) plot;
				cplot.setRangeGridlinePaint(new Color(0xc0, 0xc0, 0xc0));

				// Domain axis(x axis)
				final Font xlbfont = JFreeChartHelper.getCustomFont(JFreeChartHelper.XAXIS_TICK_FONT_PROPERTY_KEY);
				final Font xtkfont = JFreeChartHelper.getCustomFont(JFreeChartHelper.XAXIS_TICK_FONT_PROPERTY_KEY);
				if (xlbfont != null) {
					final org.jfree.chart.axis.CategoryAxis axis = cplot.getDomainAxis();
					if (axis != null) {
						axis.setLabelFont(xlbfont);
					}
				}
				if (xtkfont != null) {
					final org.jfree.chart.axis.CategoryAxis axis = cplot.getDomainAxis();
					if (axis != null) {
						axis.setTickLabelFont(xtkfont);
					}
				}

				// Range axis(y axis)
				final Font ylbfont = JFreeChartHelper.getCustomFont(JFreeChartHelper.YAXIS_TICK_FONT_PROPERTY_KEY);
				final Font ytkfont = JFreeChartHelper.getCustomFont(JFreeChartHelper.YAXIS_TICK_FONT_PROPERTY_KEY);
				if (ylbfont != null) {
					final org.jfree.chart.axis.ValueAxis axis = cplot.getRangeAxis();
					if (axis != null) {
						axis.setLabelFont(ylbfont);
					}
				}
				if (ytkfont != null) {
					final org.jfree.chart.axis.ValueAxis axis = cplot.getRangeAxis();
					if (axis != null) {
						axis.setTickLabelFont(ytkfont);
					}
				}
			} else if (plot instanceof XYPlot) {
				final XYPlot xyplot = (XYPlot) plot;
				xyplot.setRangeGridlinePaint(Color.LIGHT_GRAY);
				xyplot.setDomainGridlinePaint(Color.LIGHT_GRAY);

				// Domain axis(x axis)
				final Font xlbfont = JFreeChartHelper.getCustomFont(JFreeChartHelper.XAXIS_TICK_FONT_PROPERTY_KEY);
				final Font xtkfont = JFreeChartHelper.getCustomFont(JFreeChartHelper.XAXIS_TICK_FONT_PROPERTY_KEY);
				if (xlbfont != null) {
					final org.jfree.chart.axis.ValueAxis axis = xyplot.getDomainAxis();
					if (axis != null) {
						axis.setLabelFont(xlbfont);
					}
				}
				if (xtkfont != null) {
					final org.jfree.chart.axis.ValueAxis axis = xyplot.getDomainAxis();
					if (axis != null) {
						axis.setTickLabelFont(xtkfont);
					}
				}

				// Range axis(y axis)
				final Font ylbfont = JFreeChartHelper.getCustomFont(JFreeChartHelper.YAXIS_TICK_FONT_PROPERTY_KEY);
				final Font ytkfont = JFreeChartHelper.getCustomFont(JFreeChartHelper.YAXIS_TICK_FONT_PROPERTY_KEY);
				if (ylbfont != null) {
					final org.jfree.chart.axis.ValueAxis axis = xyplot.getRangeAxis();
					if (axis != null) {
						axis.setLabelFont(ylbfont);
					}
				}
				if (ytkfont != null) {
					final org.jfree.chart.axis.ValueAxis axis = xyplot.getRangeAxis();
					if (axis != null) {
						axis.setTickLabelFont(ytkfont);
					}
				}
			} else if (plot instanceof PolarPlot) {
				final PolarPlot pplot = (PolarPlot) plot;
				pplot.setAngleGridlinePaint(Color.LIGHT_GRAY);
				pplot.setRadiusGridlinePaint(Color.LIGHT_GRAY);

				final Font xlbfont = JFreeChartHelper.getCustomFont(JFreeChartHelper.XAXIS_TICK_FONT_PROPERTY_KEY);
				if (xlbfont != null) {
					pplot.setAngleLabelFont(xlbfont);
				}
			}
		}
		
		return jfchart;
		
	}
	
	protected boolean prepareJFreeChart(JFreeChart jfchart) {
		
		jfchart.getPlot().setOutlineVisible(false); //DO NOT draw the plot outline
		jfchart.getLegend().setFrame(BlockBorder.NONE); //remove legend boarder
		
		// ZSS-140: check legend first
		if(hasLegend()) {
			RectangleEdge edge = null; 
			switch(getLegendPos()) {
				case POS_B:
					edge = RectangleEdge.BOTTOM;
					break;
				case POS_L:
					edge = RectangleEdge.LEFT;
					break;
				case POS_T:
					edge = RectangleEdge.TOP;
					break;
				case POS_TR: //TODO corner not supported
				case POS_R:
				default:
					edge = RectangleEdge.RIGHT;
					break;
			}
			jfchart.getLegend().setPosition(edge); //put legend on the right
		} else {
			// ZSS-140: handle not legend case
			jfchart.getLegend().setVisible(false);
		}
		//ZSS-822
		JFreeChartHelper.prepareAxis(jfchart, _chartInfo);
		
		return false;
	}
	
	protected JFreeChart createJFreeChart() {
		JFreeChart jfchart = null;
		String type = JFreeChartHelper.getChartType(_chartInfo);
		if (type.equals(Chart.AREA)) {
			ChartModel model = JFreeChartHelper.prepareCategoryModel(_chartInfo.getData());
			jfchart = ChartFactory.createAreaChart(_chartInfo.getTitle(), _chartInfo.getXAxisTitle(), _chartInfo.getYAxisTitle(), CategoryModelToCategoryDataset((CategoryModel) model),
					JFreeChartHelper.getOrientation(_chartInfo), // orientation
					true, // show legend
					true, // show tooltips
					true);
		} else if (type.equals(Chart.STACKED_AREA)) {
			ChartModel model = JFreeChartHelper.prepareCategoryModel(_chartInfo.getData());
			jfchart = ChartFactory.createStackedAreaChart(_chartInfo.getTitle(), _chartInfo.getXAxisTitle(), _chartInfo.getYAxisTitle(), CategoryModelToCategoryDataset((CategoryModel) model),
					JFreeChartHelper.getOrientation(_chartInfo), // orientation
					true, // show legend
					true, // show tooltips
					true);
		} else if (type.equals(Chart.STACKED_BAR)) {
			ChartModel model = JFreeChartHelper.prepareCategoryModel(_chartInfo.getData());
			if (_chartInfo.isThreeD()) {
				jfchart = ChartFactory.createStackedBarChart3D(_chartInfo.getTitle(), _chartInfo.getXAxisTitle(), _chartInfo.getYAxisTitle(), CategoryModelToCategoryDataset((CategoryModel) model),
						JFreeChartHelper.getOrientation(_chartInfo), // orientation
						true, // show legend
						true, // show tooltips
						true);
			} else {
				jfchart = ChartFactory.createStackedBarChart(_chartInfo.getTitle(), _chartInfo.getXAxisTitle(), _chartInfo.getYAxisTitle(), CategoryModelToCategoryDataset((CategoryModel) model),
						JFreeChartHelper.getOrientation(_chartInfo), // orientation
						true, // show legend
						true, // show tooltips
						true);
			}
		} else if (type.equals(Chart.BAR)) {
			ChartModel model = JFreeChartHelper.prepareCategoryModel(_chartInfo.getData());
			if (_chartInfo.isThreeD()) {
				jfchart = ChartFactory.createBarChart3D(_chartInfo.getTitle(), _chartInfo.getXAxisTitle(), _chartInfo.getYAxisTitle(), CategoryModelToCategoryDataset((CategoryModel) model),
						JFreeChartHelper.getOrientation(_chartInfo), // orientation
						true, // show legend
						true, // show tooltips
						true);
			} else {
				jfchart = ChartFactory.createBarChart(_chartInfo.getTitle(), _chartInfo.getXAxisTitle(), _chartInfo.getYAxisTitle(), CategoryModelToCategoryDataset((CategoryModel) model),
						JFreeChartHelper.getOrientation(_chartInfo), // orientation
						true, // show legend
						true, // show tooltips
						true);
			}
		} else if (type.equals(Chart.LINE)) {
			ChartModel model = JFreeChartHelper.prepareCategoryModel(_chartInfo.getData());
			if (_chartInfo.isThreeD()) {
				jfchart = ChartFactory.createLineChart3D(_chartInfo.getTitle(), _chartInfo.getXAxisTitle(), _chartInfo.getYAxisTitle(), CategoryModelToCategoryDataset((CategoryModel) model),
						JFreeChartHelper.getOrientation(_chartInfo), // orientation
						true, // show legend
						true, // show tooltips
						true);
			} else {
				jfchart = ChartFactory.createLineChart(_chartInfo.getTitle(), _chartInfo.getXAxisTitle(), _chartInfo.getYAxisTitle(), CategoryModelToCategoryDataset((CategoryModel) model),
						JFreeChartHelper.getOrientation(_chartInfo), // orientation
						true, // show legend
						true, // show tooltips
						true);
			}
		} else if (type.equals(Chart.PIE)) {
			ChartModel model = JFreeChartHelper.preparePieModel(_chartInfo.getData());
			if (_chartInfo.isThreeD()) {
				jfchart = ChartFactory.createPieChart3D(_chartInfo.getTitle(), PieModelToPieDataset((PieModel) model), true, true, true);
			} else {
				jfchart = ChartFactory.createPieChart(_chartInfo.getTitle(), PieModelToPieDataset((PieModel) model), true, true, true);
			}
		} else if (type.equals(Chart.SCATTER)) {
			ChartModel model = JFreeChartHelper.prepareXYModel(_chartInfo.getData());
			jfchart = ChartFactory.createScatterPlot(_chartInfo.getTitle(), _chartInfo.getXAxisTitle(), _chartInfo.getYAxisTitle(), XYModelToXYDataset((XYModel) model), JFreeChartHelper.getOrientation(_chartInfo), // orientation
					true, // show legend
					true, // show tooltips
					true);
		} else if (type.equals(Chart.CANDLESTICK)) {

		} else if (type.equals(Chart.BUBBLE)) {
			ChartModel model = JFreeChartHelper.prepareXYZModel(_chartInfo.getData());
			jfchart = ChartFactory.createBubbleChart(_chartInfo.getTitle(), _chartInfo.getXAxisTitle(), _chartInfo.getYAxisTitle(), XYZModelToXYZDataset((XYZModel) model), JFreeChartHelper.getOrientation(_chartInfo), // orientation
					true, // show legend
					true, // show tooltips
					true);
		} else if (type.equals(Chart.HIGHLOW)) {
			ChartModel model = JFreeChartHelper.prepareHiLoModel(_chartInfo.getData());
			jfchart = ChartFactory.createHighLowChart(_chartInfo.getTitle(), _chartInfo.getXAxisTitle(), _chartInfo.getYAxisTitle(), HiLoModelToOHLCDataset((HiLoModel) model), true);
		} else if (type.equals(Chart.RING)) {
			ChartModel model = JFreeChartHelper.preparePieModel(_chartInfo.getData());
			jfchart = ChartFactory.createRingChart(_chartInfo.getTitle(), PieModelToPieDataset((PieModel) model), true, true, true);
		}
		
		return jfchart;
	}
	
	protected PieDataset PieModelToPieDataset(PieModel model) {
		final DefaultPieDataset dataset = new DefaultPieDataset();
		for (final Iterator it = model.getCategories().iterator(); it.hasNext();) {
			Comparable category = (Comparable) it.next();
			Number value = model.getValue(category);
			dataset.setValue(category, value);
		}
		return dataset;
	}

	protected CategoryDataset CategoryModelToCategoryDataset(CategoryModel model) {
		final DefaultCategoryDataset dataset = new DefaultCategoryDataset();
		for (final Iterator it = model.getKeys().iterator(); it.hasNext();) {
			final List key = (List) it.next();
			Comparable series = (Comparable) key.get(0);
			Comparable category = (Comparable) key.get(1);
			Number value = model.getValue(series, category);
			dataset.setValue(value, series, category);
		}
		return dataset;
	}

	protected XYDataset XYModelToXYDataset(XYModel model) {
		final XYSeriesCollection dataset = new XYSeriesCollection();
		for (final Iterator it = model.getSeries().iterator(); it.hasNext();) {
			final Comparable series = (Comparable) it.next();
			XYSeries xyser = new XYSeries(series, model.isAutoSort());
			final int size = model.getDataCount(series);
			for (int j = 0; j < size; ++j) {
				xyser.add(model.getX(series, j), model.getY(series, j), false);
			}
			dataset.addSeries(xyser);
		}
		return dataset;
	}

	protected XYZDataset XYZModelToXYZDataset(XYZModel model) {
		final DefaultXYZDataset dataset = new DefaultXYZDataset();
		for (final Iterator it = model.getSeries().iterator(); it.hasNext();) {
			final Comparable seriesKey = (Comparable) it.next();
			final int size = model.getDataCount(seriesKey);
			final double[][] data = new double[3][size];
			for (int j = 0; j < size; ++j) {
				data[0][j] = model.getX(seriesKey, j).doubleValue();
				data[1][j] = model.getY(seriesKey, j).doubleValue();
				data[2][j] = model.getZ(seriesKey, j).doubleValue();
			}
			dataset.addSeries(seriesKey, data);
		}
		return dataset;
	}

	protected OHLCDataset HiLoModelToOHLCDataset(HiLoModel model) {
		final int size = model.getDataCount();
		final OHLCDataItem[] items = new OHLCDataItem[size];

		for (int j = 0; j < size; ++j) {
			Date date = model.getDate(j);
			Number open = model.getOpen(j);
			Number high = model.getHigh(j);
			Number low = model.getLow(j);
			Number close = model.getClose(j);
			Number volume = model.getVolume(j);

			OHLCDataItem item = new OHLCDataItem(date, doubleValue(open), doubleValue(high), doubleValue(low), doubleValue(close), doubleValue(volume));
			items[j] = item;
		}

		Comparable series = model.getSeries();
		if (series == null) {
			series = DEFAULT_HI_LO_SERIES;
		}
		return new DefaultOHLCDataset(series, items);
	}
	
	protected double doubleValue(Number n) {
		return n == null ? 0.0 : n.doubleValue();
	}
	
	// ZSS-140: for testing legend existed or not
	private boolean hasLegend() {
		return _chartInfo.getLegendPosition()!=null;
	}
	
	private int getLegendPos() { //r, l, t, b
		switch(_chartInfo.getLegendPosition()) {
			case BOTTOM:
				return POS_B;
			case LEFT:
				return POS_L;
			case TOP:
				return POS_T;
			case TOP_RIGHT:
				return POS_TR;
			case RIGHT:
			default:
				return POS_R;
			}
	}
}
