/*ChartHelper.java

	Purpose:
		
	Description:
		
	History:
		Oct 18, 2010 2:27:40 PM     2010, Created by ashish

Copyright (C) 2010 Potix Corporation. All Rights Reserved.
 */
package io.keikaiex.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import io.keikai.model.SChart;
import io.keikai.model.SChartAxis;
import io.keikai.model.SColor;
import io.keikai.model.chart.SSeries;
import io.keikai.model.impl.AbstractChartAdv;
import io.keikai.model.impl.AbstractChartAxisAdv;
import io.keikai.model.impl.AbstractSeriesAdv;
import io.keikai.model.impl.chart.AreaChartDataImpl;
import io.keikai.model.impl.chart.BarChartDataImpl;
import io.keikai.model.impl.chart.GeneralChartDataImpl;
import io.keikai.model.impl.chart.GroupingChartDataImpl;
import io.keikai.model.impl.chart.LineChartDataImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.chart.Chart;
import org.zkoss.chart.Color;
import org.zkoss.chart.Legend;
import org.zkoss.chart.Marker;
import org.zkoss.chart.Point;
import org.zkoss.chart.Series;
import org.zkoss.chart.Title;
import org.zkoss.chart.Tooltip;
import org.zkoss.chart.XAxis;
import org.zkoss.chart.YAxis;
import org.zkoss.chart.model.ChartsModel;
import org.zkoss.chart.options3D.Options3D;
import org.zkoss.chart.plotOptions.ColumnPlotOptions;
import org.zkoss.chart.plotOptions.PiePlotOptions;
import org.zkoss.chart.plotOptions.PlotOptions;
import org.zkoss.chart.plotOptions.SeriesPlotOptions;

import io.keikai.model.SChart.ChartGrouping;
import io.keikai.model.SChart.ChartLegendPosition;
import io.keikai.model.SChart.ChartType;
import io.keikai.model.chart.SChartData;
import io.keikai.model.chart.SGeneralChartData;
import io.keikai.model.util.Strings;
import io.keikaiex.ui.ZssCharts;

/**
 * Internal Use Only. Helper Utility class for handling Chart.
 * 
 * @author ashish
 * 
 */
public class HighchartsHelper {

	private static final Logger logger = LoggerFactory.getLogger(HighchartsHelper.class);
	private static final String DOUGHNUT_INNER_SIZE = "80%";
	private static final ChartsModelManager chartsModelManager = new ChartsModelManager();

	/**
	 * Creates {@link org.zkoss.zul.Chart} based on the {@link SChart}
	 * 
	 * @param chartInfo
	 * @return the created {@link org.zkoss.zul.Chart} based on the
	 *         {@link SChart}
	 */
	public static ZssCharts createCharts(SChart chartInfo) {
		final ZssCharts chart = new ZssCharts();
		getMapper(chartInfo).doSetting(chart, chartInfo.getData());
		
		// (ZKCHARTS-22) workaround: animation may cause zk charts rendering failure.
		chart.setAnimation(false);
		chart.getNavigation().getButtonOptions().setEnabled(false);
		
		return chart;
	}

	private static TypeMapper getMapper(ChartType type) {
		try{
			return TypeMapper.valueOf(type.toString());
		} catch (IllegalArgumentException | NullPointerException e) {
			return TypeMapper.OTHER;
		}
	}
	
	private static TypeMapper getMapper(SChart chartInfo) {
		return getMapper(chartInfo.getType());
	}

	/**
	 * Convert data and options of {@link SChart} into a {@link ZssCharts}
	 */
	public static void drawCharts(ZssCharts chart, SChart chartInfo) {
		drawCharts(chart, chartInfo, false);
	}

	/**
	 * Convert data and options of {@link SChart} into a {@link ZssCharts}
	 * @param chart
	 * @param chartInfo
	 * @param dataLabelsEnabled enable data labels or not
	 */
	public static void drawCharts(ZssCharts chart, SChart chartInfo, boolean dataLabelsEnabled) {
		final AbstractChartAdv chartAdv = (AbstractChartAdv) chartInfo;
		if (chartInfo.isSparkline()) {
			drawSparklineChart(chart, chartInfo);
		} else if (chartAdv.getChartData().size() > 1) {
			drawComboChart(chart, chartInfo);
		} else {
			// draw chart data
			final ChartsModel model = chartsModelManager.getChartsModel(chartInfo);
			final List<SColor> seriesColors = ((AbstractChartAdv) chartInfo).getSeriesColors();
			if (!seriesColors.isEmpty()) {
				chart.setColors(((AbstractChartAdv) chartInfo).getSeriesColors().stream()
						.map((c) -> new Color(c.getHtmlColor()))
						.collect(Collectors.toList()));
			}

			// draw chart view, like label, tooltip, unit of axis
			if (model != null) {
				drawChartInner(chart, model, chartInfo, dataLabelsEnabled);
			}
		}
	}
	
	/**
	 * Pre-set fonts of {@link Chart} upon configuration.
	 * 
	 * @param chart
	 */
	private static void drawChartInner(ZssCharts chart, ChartsModel model, SChart chartInfo, boolean dataLabelsEnabled) {
		setChartTitle(chart, chartInfo);
		chart.setLegend(getLegend(chartInfo));
		chart.setModel(model);

		final int seriesSize = chart.getSeriesSize();
		for (int i = 0; i < seriesSize; i++) {
			Series series = chart.getSeries(i);
			int pointsSize = series.getData().size();
			AbstractSeriesAdv seriesInfo = (AbstractSeriesAdv) ((SGeneralChartData) chartInfo.getData()).getSeries(i);
			Map<Integer, SColor> dpColor = seriesInfo.getDataPointColors();
			final SColor defaultColor = seriesInfo.getDefaultColor();
			if (defaultColor != null) {
				series.setColor(defaultColor.getHtmlColor());
			}
			for (int j = 0; j < pointsSize; j++) {
				SColor color = dpColor.get(j);
				if (color != null) {
					series.getPoint(j).setColor(color.getHtmlColor());
				}
			}
			// KEIKAI-579
			if (dataLabelsEnabled && seriesInfo.isDataLabelsVisible()) {
				series.getDataLabels().setEnabled(true);
			}
		}

		//ZSS-822
		HighchartsAxisHelper.drawAxis(chart, chartInfo);

		final YAxis yAxis = chart.getYAxis();
		final XAxis xAxis = chart.getXAxis();
		final ChartGrouping grouping = chartInfo.getGrouping();

		// KEIKAI-619: See the horizontal graph at the very bottom of "keikai-619.xlsx": categories [1,2,3,4] should
		// read from bottom to top as in Excel.
		// Recover the default setting where inverted charts have their x-axis reversed.
		xAxis.setReversed(chart.isInverted());
		// KEIKAI-619: Stacked columns should have data stacked from bottom to top where the legend reads left to right.
		yAxis.setReversedStacks(grouping != ChartGrouping.STACKED && grouping != ChartGrouping.PERCENT_STACKED);

		// set empty text to prevent from showing default label
		yAxis.getTitle().setText("");
		xAxis.getTitle().setText("");
		
		PiePlotOptions options = chart.getPlotOptions().getPie();
		options.getDataLabels().setEnabled(false);
		options.setShowInLegend(true);
	}

	/**
	 * Pre-set fonts of {@link ZssCharts} upon configuration.
	 *
	 * @param chart chart component
	 * @param chartInfo chart model
	 */
	private static void drawComboChart(ZssCharts chart, SChart chartInfo) {
		setChartTitle(chart, chartInfo);

		final AbstractChartAdv chartAdv = (AbstractChartAdv) chartInfo;
		final List<SChartData> chartSeries = chartAdv.getChartData();
		final boolean onlyVisible = !chartAdv.isPlotOnlyVisibleCells();

		// reset Axis and series settings
		for (int i = chart.getSeriesSize() - 1; i >= 0; --i) {
			chart.getSeries(i).remove();
		}
		for (int i = chart.getXAxisSize() - 1; i >= 0; --i) {
			chart.getXAxis(i).remove();
		}
		for (int i = chart.getyAxisSize() - 1; i >= 0; --i) {
			chart.getYAxis(i).remove();
		}

		String[] categories = new String[0];
		int num = 0;
		boolean containsBar = false;
		for (final SChartData chartData : chartSeries) {
			if (chartData instanceof GeneralChartDataImpl) {
				final GeneralChartDataImpl generalChartData = (GeneralChartDataImpl) chartData;
				final ChartType type = generalChartData.getType();
				if (!containsBar && type == ChartType.BAR) {
					containsBar = true;
				}
				for (int j = 0; j < generalChartData.getNumOfSeries(); ++j) {
					final SSeries sseries = generalChartData.getSeries(j);
					num = Math.max(num, sseries.getNumOfValue());
				}
				if (categories.length == 0) {
					final int numCat = generalChartData.getNumOfCategory();
					if (numCat > 0) {
						if (generalChartData.getCategoryAxis() != null) {
							categories = new String[numCat];
							for (int i = 0; i < numCat; ++i) {
								categories[i] = generalChartData.getCategory(i).toString();
							}
						}
					}
				}
			}
		}
		final Map<Long, Integer> xAxisMap = new HashMap<>();
		final Map<Long, Integer> yAxisMap = new HashMap<>();
		HighchartsAxisHelper.prepareAxes(chart, chartAdv, containsBar, xAxisMap, yAxisMap);

		final List<SColor> seriesColors = ((AbstractChartAdv) chartInfo).getSeriesColors();
		final int colorSize = seriesColors.size();
		final List<Series> seriesArray = new ArrayList<>();
		final Set<ChartType> visited = new HashSet<>();
		final HighchartsAxisHelper.AxisRanger ranger = new HighchartsAxisHelper.AxisRanger();
		for (final SChartData chartData : chartSeries) {
			if (chartData instanceof GeneralChartDataImpl) {
				final GeneralChartDataImpl data = (GeneralChartDataImpl) chartData;
				final ChartType chartType = data.getType();
				final int xAxisIndex = getAxisIndex(data, xAxisMap);
				final int yAxisIndex = getAxisIndex(data, yAxisMap);
				for (int j = 0; j < data.getNumOfSeries(); ++j) {
					final AbstractSeriesAdv sseries = (AbstractSeriesAdv) data.getSeries(j);
					if (chartType != null) {
						final Series series = getComboSeries(data, sseries, onlyVisible, categories, containsBar, num,
								xAxisIndex, yAxisIndex, ranger);
						if (series != null) {
							if (!visited.contains(chartType)) {
								getMapper(chartType).doSetting(chart, data);
							}
							visited.add(chartType);
							series.setZIndex(getSeriesIndex(chartType));
							String color;
							final SColor defaultColor = sseries.getDefaultColor();
							if (defaultColor != null) {
								series.setColor(defaultColor.getHtmlColor());
							} else if (colorSize != 0) {
								color = seriesColors.get(sseries.getOrder() % colorSize).getHtmlColor();
								series.setColor(color);
							}
							seriesArray.add(series);
						}
					}
				}
			}
		}
		chart.setLegend(getLegend(chartInfo));

		for (Series s: seriesArray) {
			chart.addSeries(s);
		}

		HighchartsAxisHelper.drawAxisLabelTick(chart, chartInfo, xAxisMap, yAxisMap, ranger, containsBar);

		final PiePlotOptions options = chart.getPlotOptions().getPie();
		options.getDataLabels().setEnabled(false);
	}

	private static void drawSparklineChart(ZssCharts chart, SChart chartInfo) {
		// KEIKAI-527
		boolean hidden = chartInfo.getSheet().getRow(chartInfo.getAnchor().getRowIndex()).isHidden();
		chart.setVisible(!hidden);
		if (hidden) {
			return; // skip hidden drawing
		}
		chart.setType(chartInfo.getType().name().toLowerCase());

		chart.setBorderWidth(0);
		chart.setMargin(new Integer[]{0, 0, 0, 0});
		chart.setStyle("overflow:visible");
		chart.setTitle("");
		chart.getCredits().setEnabled(false);
		chart.getExporting().setEnabled(false);

		chart.getLegend().setEnabled(false);
		Tooltip tooltip = chart.getTooltip();
		tooltip.setEnabled(false);

		if (!(chartInfo.getData() instanceof SGeneralChartData)) {
			return;
		}
		final SGeneralChartData chartData = (SGeneralChartData) chartInfo.getData();

		final AbstractChartAdv chartAdv = (AbstractChartAdv) chartInfo;
		SeriesPlotOptions seriesOpt = chart.getPlotOptions().getSeries();
		seriesOpt.setAnimation(false);
		seriesOpt.setLineWidth(1);
		seriesOpt.setShadow(false);
		seriesOpt.setPointStart(1);
		seriesOpt.getStates().getHover().setLineWidth(1);
		seriesOpt.setColor(chartAdv.getSparklineSeriesColor().getHtmlColor());
		final boolean isLineType = chartInfo.getType() == ChartType.LINE;
		// reset Axis and series settings
		for (int i = chart.getSeriesSize() - 1; i >= 0; --i) {
			chart.getSeries(i).remove();
		}
		final SSeries sseries = chartData.getSeries(0);
		final List<Point> points = new ArrayList<>();
		double high = Double.NEGATIVE_INFINITY;
		double low = Double.POSITIVE_INFINITY;
		boolean isWinLose = chartInfo.isWinloseSpark();
		if (isWinLose) {
			high = 1;
			low = -1;
		}
		for (int i = 0; i < sseries.getNumOfValue(); ++i) {
			final Number num = ChartsModelManager.toNumber(sseries.getValue(i), 0);
			if (num != null) {
				double v = num.doubleValue();
				if (isWinLose) {
					if (v == 0) {
						continue;
					}
					v = v > 0 ? 1 : -1;
				} else {
					if (high < v) {
						high = v;
					}
					if (low > v) {
						low = v;
					}
				}
				final Point point = new Point(v);
				points.add(point);
			}
		}
		if (isLineType) {
			final Marker marker = seriesOpt.getMarker();
			marker.setRadius(1);
			marker.getStates().getHover().setEnabled(false);
			if (chartAdv.isSparklineMarkerVisible()) {
				marker.setFillColor(chartAdv.getSparklineMarkerColor().getHtmlColor());
			} else {
				marker.setEnabled(false);
			}
		} else {
			if (isWinLose) {
				chart.getYAxis().setMin(-1);
				chart.getYAxis().setMax(1);
			}
			final ColumnPlotOptions colOpt = chart.getPlotOptions().getColumn();
			colOpt.setNegativeColor(chartAdv.getSparklineNegativePointColor().getHtmlColor());
		}

		final int size = points.size();
		for (int i = 0; i < size; ++i) {
			final Point point = points.get(i);
			final SColor color = getSparklinePointColor(chartAdv, i, size, point.getY().doubleValue(),
					high, low);
			if (color != null) {
				if (isLineType) {
					point.getMarker().setFillColor(color.getHtmlColor());
				} else {
					point.setColor(color.getHtmlColor());
				}
			}
		}
		chart.getSeries(0).setData(points.toArray(new Point[0]));
		chart.setShowAxes(false);
	}

	private static SColor getSparklinePointColor(AbstractChartAdv chartAdv, int index, int size, double value,
												 double high, double low) {
		if (chartAdv.isSparklineHighPointVisible() && value == high) {
			return chartAdv.getSparklineHighPointColor();
		}
		if (chartAdv.isSparklineLowPointVisible() && value == low) {
			return chartAdv.getSparklineLowPointColor();
		}
		if (chartAdv.isSparklineFirstPointVisible() && index == 0) {
			return chartAdv.getSparklineFirstPointColor();
		}
		if (chartAdv.isSparklineLastPointVisible() && index == size - 1) {
			return chartAdv.getSparklineLastPointColor();
		}
		if (chartAdv.isSparklineNegativePointVisible() && value < 0) {
			return chartAdv.getSparklineNegativePointColor();
		}
		return null;
	}

	private static String getChartType(ChartType type) {
		switch (type) {
			case AREA:
				return "area";
			case BAR:
				return "bar";
			case COLUMN:
				return "column";
			case BUBBLE:
				return "bubble";
			case DOUGHNUT:
			case PIE:
				return "pie";
			case LINE:
				return "line";
			case OF_PIE:
				return "ofpie";
			case RADAR:
				return "radar";
			case SCATTER:
				return "scatter";
			default:
				return "";
		}
	}

	private static Legend getLegend(SChart chartInfo) {
		final Legend legend = new Legend();
		// KEIKAI-296
		final ChartLegendPosition position = chartInfo.getLegendPosition();
		if (position == null) {
			legend.setEnabled(false);
		} else {
			final String align = getLegendHorizontalAlign(position);
			legend.setAlign(align);
			// many labels in a horizontal order will make chart be too small.
			legend.setLayout(align.equals("center") ? "horizontal" : "vertical");
			legend.setVerticalAlign(getLegendVerticalAlign(position));
		}
		return legend;
	}

	private static Series getComboSeries(GeneralChartDataImpl chartData, AbstractSeriesAdv sseries, boolean onlyVisible,
										 String[] categories, boolean containsBar, int num, int xAxisIndex,
										 int yAxisIndex, HighchartsAxisHelper.AxisRanger ranger) {
		final ChartType chartType = chartData.getType();
		final Series series = new Series();
		final String name = ChartsModelManager.toSeriesName(sseries.getName(), sseries.getOrder());
		series.setName(name);
		series.setType(getChartType(chartType));
		final List<SChartAxis> valAxes = chartData.getValueAxes();
		long valAxId = -1;
		if (valAxes != null && !valAxes.isEmpty()) {
			if (valAxes.size() > 1) {
				for (SChartAxis valAxe : valAxes) {
					final AbstractChartAxisAdv.Position position = ((AbstractChartAxisAdv) valAxe).getPosition();
					if (!AbstractChartAxisAdv.Position.BOTTOM.equals(position)) {
						valAxId = valAxe.getId();
					}
				}
			} else {
				valAxId = valAxes.get(0).getId();
			}
		}
		switch (chartType) {
			// refer CategoryModel
			case BAR:
			{
				final Map<Integer, SColor> dpColors = sseries.getDataPointColors();
				final Number[] values = getCategorySeriesValues(sseries, num);
				final List<Point> points = new ArrayList<>();
				final ChartGrouping grouping = ((BarChartDataImpl) chartData).getGrouping();
				for (int i = 0, length = values.length; i < length; ++i) {
					if (!onlyVisible || !sseries.isXValueFomulaHidden(i)) {
						final Number v = values[i];
						ranger.scan(valAxId, chartType, i, v, grouping);
						final Point point = new Point(v);
						point.setName(HighchartsAxisHelper.getCategory(chartData, i));
						final SColor color = dpColors.get(i);
						if (color != null) {
							point.setColor(color.getHtmlColor());
						}
						points.add(point);
					}
				}
				series.setData(points.toArray(new Point[0]));
				series.setXAxis(xAxisIndex);
				series.setYAxis(yAxisIndex);
				return series;
			}
			// refer CategoryModel
			case AREA:
			case COLUMN:
			case LINE:
			{
				final Map<Integer, SColor> dpColors = sseries.getDataPointColors();
				final Number[] values = getCategorySeriesValues(sseries, num);
				final List<Point> points = new ArrayList<>();
				final ChartGrouping grouping = ((GroupingChartDataImpl) chartData).getGrouping();
				for (int i = 0, length = values.length; i < length; ++i) {
					if (!onlyVisible || !sseries.isXValueFomulaHidden(i)) {
						final Number v = values[i];
						final Point point = containsBar ? new Point(v, i) : new Point(v);
						point.setName(HighchartsAxisHelper.getCategory(chartData, i));
						ranger.scan(valAxId, chartType, i, v, grouping);
						final SColor color = dpColors.get(i);
						if (color != null) {
							point.setColor(color.getHtmlColor());
						}
						points.add(point);
					}
				}
				series.setData(points.toArray(new Point[0]));
				series.setXAxis(xAxisIndex);
				series.setYAxis(yAxisIndex);
				return series;
			}
			// refer PieModel
			case DOUGHNUT:
			case PIE:
			{
				series.setData(getPieSeriesValues(chartData, categories, onlyVisible, num, sseries));
				series.setXAxis(xAxisIndex);
				series.setYAxis(yAxisIndex);
				series.getPlotOptions().setShowInLegend(false);
				return series;
			}
			// refer XYModel
			case SCATTER: // bar chart and scatter chart can not combine together
			{
				final Point[] points = getXySeriesValues(onlyVisible, sseries);
				series.setData(points);
				for (int i = 0; i < points.length; ++i) {
					final Point p = points[i];
					ranger.scan(valAxId, chartType, p.getX() == null ? i : p.getX(), p.getY(), ChartGrouping.STANDARD);
				}
				series.setXAxis(xAxisIndex);
				series.setYAxis(yAxisIndex);
				return series;
			}
			// not support combo in Excel
			// case BUBBLE:
			// case RADAR:
			default:
				return null;
		}
	}

	private static Number[] getCategorySeriesValues(SSeries sseries, int num) {
		final Number[] numbers = new Number[num];
		for (int i = 0; i < num; ++i) {
			numbers[i] = ChartsModelManager.toNumber(sseries.getValue(i), 0);
		}
		return numbers;
	}

	private static Point[] getPieSeriesValues(SGeneralChartData chartData, String[] categories, boolean onlyVisible,
											  int num, AbstractSeriesAdv sseries) {
		final Map<Integer, SColor> dpColors = sseries.getDataPointColors();
		final List<Point> points = new ArrayList<>();
		for (int i = 0; i < num; ++i) {
			String label = ".";
			Number val;
			if (onlyVisible) {
				// if it doesn't have value, then we skip this record
				if (sseries.isXValueFomulaHidden(i))
					continue;

				if (!chartData.isCategoryHidden(i)) {
					label = HighchartsAxisHelper.getCategory(chartData, i);
				}
			} else {
				label = HighchartsAxisHelper.getCategory(chartData, i);
			}
			val = ChartsModelManager.toNumber(sseries.getValue(i), 0);
			final Point point = new Point(label, val);
			final SColor color = dpColors.get(i);
			if (color != null) {
				point.setColor(color.getHtmlColor());
			}
			points.add(point);
		}
		final Point[] array = new Point[points.size()];
		return points.toArray(array);
	}

	private static Point[] getXySeriesValues(boolean onlyVisible, AbstractSeriesAdv sseries) {
		final Map<Integer, SColor> dpColors = sseries.getDataPointColors();
		final List<Point> points = new ArrayList<>();
		final int num = sseries.getNumOfYValue();
		final boolean hasXValue = sseries.getNumOfValue() != 0;
		for (int i = 0; i < num; ++i) {
			if (!onlyVisible || !sseries.isYValueFomulaHidden(i)) {
				final Point point = new Point(ChartsModelManager.toNumber(sseries.getYValue(i), 0));
				if (hasXValue) {
					final Object xval = sseries.getValue(i);
					if (xval instanceof Number) {
						point.setX((Number) xval);
					}
				}
				final SColor color = dpColors.get(i);
				if (color != null) {
					point.setColor(color.getHtmlColor());
				}
				points.add(point);
			}
		}
		final Point[] array = new Point[points.size()];
		return points.toArray(array);
	}

	private static int getSeriesIndex(ChartType chartType) {
		switch (chartType) {
			case OF_PIE:
				return 1;
			case DOUGHNUT:
				return 2;
			case AREA:
				return 3;
			case BAR:
				return 4;
			case COLUMN:
				return 5;
			case LINE:
				return 6;
			case RADAR:
				return 7;
			case SCATTER:
				return 8;
			case BUBBLE:
				return 9;
			case SURFACE:
				return 10;
			case STOCK:
				return 11;
			case PIE:
			default:
				return 0;
		}
	}

	private static String getLegendHorizontalAlign(ChartLegendPosition legendPosition) {
		if(legendPosition == ChartLegendPosition.TOP || legendPosition == ChartLegendPosition.BOTTOM)
			return "center";
		if(legendPosition == ChartLegendPosition.TOP_RIGHT)
			return "right";
		if(legendPosition != null)
			return legendPosition.toString().toLowerCase();
		
		return "right";
	}
	
	private static String getLegendVerticalAlign(ChartLegendPosition legendPosition) {
		if(legendPosition == ChartLegendPosition.LEFT || legendPosition == ChartLegendPosition.RIGHT)
			return "middle";
		if(legendPosition == ChartLegendPosition.TOP_RIGHT)
			return "top";
		if(legendPosition != null)
			return legendPosition.toString().toLowerCase();
		
		return "middle";
	}

	// KEIKAI-297
	private static void setChartTitle(ZssCharts charts, SChart chartInfo) {
		final AbstractChartAdv chartAdv = (AbstractChartAdv) chartInfo;
		String t = null;
		final Title title = new Title();
		if (chartAdv.isShowTitle()) {
			t = chartInfo.getTitle();
			if (Boolean.TRUE.equals(chartAdv.isAutoTitleDeleted())) {
				if (Strings.isEmpty(t)) {
					t = null;
				}
			} else {
				if (Strings.isEmpty(t)) {
					t = getFirstSeriesTitle(chartInfo);
				}
				if (Strings.isEmpty(t)) {
					t = "Chart Title";
				}
			}
		}
		title.setAlign("center");
		title.setText(t);
		charts.setTitle(title);
	}
	
	private static String getFirstSeriesTitle(SChart chartInfo) {
		//ZSS-821
		final List<SChartData> chartData = ((AbstractChartAdv) chartInfo).getChartData();
		if (chartData.size() == 1) {
			final SChartData firstData = chartData.get(0);
			if (firstData instanceof SGeneralChartData) {
				final SGeneralChartData cd = (SGeneralChartData) firstData;
				if (cd.getType() == ChartType.PIE || cd.getNumOfSeries() == 1) {
					return cd.getSeries(0).getName();
				}
			}
		}
		return null;
	}

	private static int getAxisIndex(GeneralChartDataImpl chartData, Map<Long, Integer> axisMap) {
		final SChartAxis catAxis = chartData.getCategoryAxis();
		if (catAxis != null) {
			final long catAxId = catAxis.getId();
			if (axisMap.containsKey(catAxId)) {
				return axisMap.get(catAxId);
			}
		}
		final List<SChartAxis> valAxes = chartData.getValueAxes();
		if (valAxes != null) {
			for (SChartAxis valAxis: valAxes) {
				final long valAxId = valAxis.getId();
				if (axisMap.containsKey(valAxId)) {
					return axisMap.get(valAxId);
				}
			}
		}
		return -1;
	}
	
	private enum TypeMapper {
		AREA(ZssCharts.AREA, (chart, info) -> {
			if (info instanceof AreaChartDataImpl) {
				final ChartGrouping grouping = ((AreaChartDataImpl) info).getGrouping();
				if (grouping == ChartGrouping.STACKED) {
					chart.getPlotOptions().getArea().setStacking("normal");
				} else if(grouping == ChartGrouping.PERCENT_STACKED) {
					chart.getPlotOptions().getArea().setStacking("percent");
				}
			}

			// for 3D
			if (info.getChart().isThreeD()) {
				final Options3D option = chart.getOptions3D();
				option.setEnabled(true);
				option.setAlpha(6);
				option.setBeta(6);
				option.setDepth(50);
				option.setViewDistance(25);
				chart.setOptions3D(option);
			}
		}),
		BAR(ZssCharts.BAR, (chart, info) -> {
			if (info instanceof BarChartDataImpl) {
				final ChartGrouping grouping = ((BarChartDataImpl) info).getGrouping();
				if (grouping == ChartGrouping.STACKED) {
					chart.getPlotOptions().getBar().setStacking("normal");
				} else if(grouping == ChartGrouping.PERCENT_STACKED) {
					chart.getPlotOptions().getBar().setStacking("percent");
				}
			}

			// for 3D
			if(info.getChart().isThreeD()) {
				final Options3D option = chart.getOptions3D();
				option.setEnabled(true);
				option.setAlpha(6);
				option.setBeta(6);
				option.setDepth(50);
				option.setViewDistance(25);
				chart.setOptions3D(option);
			}
		}),
		COLUMN("column", (chart, info) -> {
			if (info instanceof BarChartDataImpl) {
				final ChartGrouping grouping = ((BarChartDataImpl) info).getGrouping();
				if (grouping == ChartGrouping.STACKED) {
					chart.getPlotOptions().getColumn().setStacking("normal");
				} else if(grouping == ChartGrouping.PERCENT_STACKED) {
					chart.getPlotOptions().getColumn().setStacking("percent");
				}
			}

			// for 3D
			if(info.getChart().isThreeD()) {
				final Options3D option = chart.getOptions3D();
				option.setEnabled(true);
				option.setAlpha(6);
				option.setBeta(6);
				option.setDepth(50);
				option.setViewDistance(25);
				chart.setOptions3D(option);
			}
		}),
		BUBBLE(ZssCharts.BUBBLE),
		DOUGHNUT("pie", (chart, info) -> {
			final PlotOptions pOptions = chart.getPlotOptions();
			pOptions.getPie().setShowInLegend(true);
			pOptions.getPie().setInnerSize(DOUGHNUT_INNER_SIZE);
			final List<SColor> colors = ((AbstractChartAdv) info.getChart()).getSeriesColors();
			if (!colors.isEmpty()) {
				pOptions.getPie().setColors(colors.stream().map((c) -> new Color(c.getHtmlColor()))
						.collect(Collectors.toList()));
			}
		}),
		LINE(ZssCharts.LINE, (chart, info) -> {
			if (info instanceof LineChartDataImpl) {
				final ChartGrouping grouping = ((LineChartDataImpl) info).getGrouping();
				if (grouping == ChartGrouping.STACKED) {
					chart.getPlotOptions().getLine().setStacking("normal");
				} else if(grouping == ChartGrouping.PERCENT_STACKED) {
					chart.getPlotOptions().getLine().setStacking("percent");
				}
			}

			// for 3D
			if(info.getChart().isThreeD()) {
				final Options3D option = chart.getOptions3D();
				option.setEnabled(true);
				option.setAlpha(6);
				option.setBeta(6);
				option.setDepth(50);
				option.setViewDistance(25);
				chart.setOptions3D(option);
			}
		}),
		OF_PIE("ofpie"),
		PIE(ZssCharts.PIE, (charts, info) -> {
			final PlotOptions pOptions = charts.getPlotOptions();
			pOptions.getPie().setShowInLegend(true);
			final List<SColor> colors = ((AbstractChartAdv) info.getChart()).getSeriesColors();
			if (!colors.isEmpty()) {
				pOptions.getPie().setColors(colors.stream().map((c) -> new Color(c.getHtmlColor()))
						.collect(Collectors.toList()));
			}

			// for 3D
			if(info.getChart().isThreeD()) {
				final Options3D option = charts.getOptions3D();
				option.setEnabled(true);
				option.setAlpha(50);
				option.setBeta(0);
				pOptions.getPie().setDepth(35);
				charts.setOptions3D(option);
			}
		}),
		RADAR("radar"),
		SCATTER(ZssCharts.SCATTER, (chart, info) -> {
			// for 3D
			if(info.getChart().isThreeD()) {
				final Options3D option = chart.getOptions3D();
				option.setEnabled(true);
				option.setAlpha(10);
				option.setBeta(30);
				option.setDepth(250);
				option.setViewDistance(5);
				chart.setOptions3D(option);
			}
		}),
		SURFACE("surface"),
		OTHER("");

		String innerType;
		Setting setting;
		
		TypeMapper(String innerType) {
			this.innerType = innerType;
		}
		
		TypeMapper(String innerType, Setting setting) {
			this.innerType = innerType;
			this.setting = setting;
		}
		
		void doSetting(ZssCharts charts, SChartData info) {
			charts.setType(innerType);
			if (setting != null) {
				setting.doSetting(charts, info);
			}
		}
		
		interface Setting {
			void doSetting(ZssCharts charts, SChartData info);
		}
	}
}