package railo.runtime.tag;

import java.awt.Color;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartRenderingInfo;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.Axis;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.block.ColumnArrangement;
import org.jfree.chart.block.LineBorder;
import org.jfree.chart.imagemap.ImageMapUtilities;
import org.jfree.chart.imagemap.ToolTipTagFragmentGenerator;
import org.jfree.chart.imagemap.URLTagFragmentGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PiePlot;
import org.jfree.chart.plot.PiePlot3D;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.category.BarRenderer3D;
import org.jfree.chart.renderer.category.CategoryItemRenderer;
import org.jfree.chart.renderer.category.LineAndShapeRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.Range;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.time.Second;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.XYDataset;
import org.jfree.ui.HorizontalAlignment;
import org.jfree.ui.RectangleAnchor;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;
import org.jfree.util.ShapeUtilities;

import railo.commons.color.ColorCaster;
import railo.commons.io.IOUtil;
import railo.commons.io.res.Resource;
import railo.commons.io.res.util.ResourceUtil;
import railo.commons.lang.Md5;
import railo.commons.lang.StringUtil;
import railo.runtime.chart.BarRenderer3DWrap;
import railo.runtime.chart.CategoryToolTipGeneratorImpl;
import railo.runtime.chart.EmptyURLTagFragmentGenerator;
import railo.runtime.chart.LabelFormatUtil;
import railo.runtime.chart.PieSectionLabelGeneratorImpl;
import railo.runtime.chart.PieSectionLegendLabelGeneratorImpl;
import railo.runtime.chart.PieToolTipGeneratorImpl;
import railo.runtime.chart.TickUnitsImpl;
import railo.runtime.chart.ToolTipTagFragmentGeneratorImpl;
import railo.runtime.converter.JavaConverter;
import railo.runtime.engine.ThreadLocalPageContext;
import railo.runtime.exp.ApplicationException;
import railo.runtime.exp.ExpressionException;
import railo.runtime.exp.PageException;
import railo.runtime.ext.tag.BodyTagImpl;
import railo.runtime.functions.dateTime.DateAdd;
import railo.runtime.img.Image;
import railo.runtime.op.Caster;
import railo.runtime.op.date.DateCaster;
import railo.runtime.type.dt.DateTime;


public final class Chart extends BodyTagImpl implements Serializable {

	
	
	public static final Color COLOR_999999=new Color(0x99,0x99,0x99);
	public static final Color COLOR_666666=new Color(0x66,0x66,0x66);
	public static final Color COLOR_333333=new Color(0x33,0x33,0x33);
	
	public static final String FONT_ARIAL = "arial";
	public static final String FONT_TIMES = "times";
	public static final String FONT_COURIER = "courier";
	public static final String FONT_ARIAL_UNICODE_MS = "arialunicodems";

	public static final int FORMAT_GIF = 0;
	public static final int FORMAT_JPG = 1;
	public static final int FORMAT_PNG = 2;
	public static final int FORMAT_FLASH=3;


	public static final int PIE_SLICE_STYLE_SOLID = 0;
	public static final int PIE_SLICE_STYLE_SLICED = 1;
	
	public static final int SERIES_PLACEMENT_DEFAULT = 0;
	public static final int SERIES_PLACEMENT_CLUSTER = 1;
	public static final int SERIES_PLACEMENT_STACKED = 2;
	public static final int SERIES_PLACEMENT_PERCENT = 3;

	public static final int TIP_STYLE_NONE = 0;
	public static final int TIP_STYLE_FORMATS = 1;
	public static final int TIP_STYLE_MOUSEDOWN = 2;
	public static final int TIP_STYLE_MOUSEOVER = 3;
	
	private static final int NONE = 0;
	private static final int YES = 1;
	private static final int NO = 2;
	

	private static int chartIndex=0;
	
	private Color backgroundcolor=Color.WHITE;
	private Color databackgroundcolor=Color.WHITE;
	private Color foregroundcolor=Color.BLACK;
	private Color tipbgcolor=Color.WHITE;
	private String xaxistitle=null;
	private String yaxistitle=null;
	

	private int chartheight=240;
	private int chartwidth=320;
	
	private String font=FONT_ARIAL;
	private int fontstyle=0;
	private int fontsize=11;
	
	private int format=FORMAT_PNG;
	private int gridlines=10;
	
	private int labelFormat=LabelFormatUtil.LABEL_FORMAT_NUMBER;
	private int markersize=-1;
	
	private String name=null;
	
	private int pieslicestyle=PIE_SLICE_STYLE_SLICED;

	private double scalefrom=Double.NaN;
	private double scaleto=Double.NaN;
	private boolean legendMultiLine=false;
	
	private int seriesplacement=SERIES_PLACEMENT_DEFAULT;

	private boolean show3d=false;
	private boolean showtooltip=true;
	private boolean showborder=false;
	private boolean showlegend=true;
	private boolean showmarkers=true;
	private int showxgridlines=NONE;
	private boolean showygridlines=false;
	private boolean sortxaxis=false;

	private String style=null;
	private String title="";
	
	private int tipstyle=TIP_STYLE_MOUSEOVER;
	private List<ChartSeriesBean> _series=new ArrayList<ChartSeriesBean>();

	private String url;
	private double xoffset=0.1;
	private double yoffset=0.1;
	private String xaxistype="category";
	private String yaxistype="category";
	private double smallest;
	private double biggest;
	private boolean showXLabel=true;
	private String source;
	
	public void release() {
		_series.clear();

		url=null;
		xoffset=0.1;
		yoffset=0.1;
		xaxistype="category";
		yaxistype="category";
		
		
		
		xaxistitle="";
		yaxistitle="";
		legendMultiLine=false;
		// TODO super.release();
		backgroundcolor=Color.WHITE;
		databackgroundcolor=Color.WHITE;
		foregroundcolor=Color.BLACK;
		tipbgcolor=Color.WHITE;
		
		chartheight=240;
		chartwidth=320;
		
		font=FONT_ARIAL;
		fontstyle=0;
		fontsize=11;
		
		format=FORMAT_PNG;
		gridlines=10;
		
		labelFormat=LabelFormatUtil.LABEL_FORMAT_NUMBER;
		markersize=-1;
		name=null;
		
		pieslicestyle=PIE_SLICE_STYLE_SLICED;
		
		scalefrom=Double.NaN;
		scaleto=Double.NaN;
		seriesplacement=SERIES_PLACEMENT_DEFAULT;
		
		show3d=false;
		showborder=false;
		showlegend=true;
		showmarkers=true;
		showxgridlines=NONE;
		showygridlines=false;
		sortxaxis=false;
		showXLabel=true;
		showtooltip=true;
		style=null;
		title="";
		source=null;
		tipstyle=TIP_STYLE_MOUSEOVER;
	}
	
	

	public void setShowxlabel(boolean showXLabel) {
		this.showXLabel = showXLabel;
	}
	public void setSource(String source) {
		this.source = source;
	}
	public void setShowtooltip(boolean showtooltip) {
		this.showtooltip = showtooltip;
	}
	public void setBackgroundcolor(String strBackgroundColor) throws ExpressionException {
		this.backgroundcolor = ColorCaster.toColor(strBackgroundColor);
	}
	
	public void setDatabackgroundcolor(String strDatabackgroundcolor) throws ExpressionException {
		this.databackgroundcolor = ColorCaster.toColor(strDatabackgroundcolor);
	}

	public void setForegroundcolor(String strForegroundcolor) throws ExpressionException {
		this.foregroundcolor = ColorCaster.toColor(strForegroundcolor);
	}

	public void setTipbgcolor(String strTipbgcolor) throws ExpressionException {
		this.tipbgcolor = ColorCaster.toColor(strTipbgcolor);
	}
	
	public void setChartheight(double chartheight) {
		this.chartheight = (int) chartheight;
	}

	public void setChartwidth(double chartwidth) {
		this.chartwidth = (int) chartwidth;
	}

	public void setFont(String strFont) {
		strFont=strFont.trim().toLowerCase();
		if("arial".equals(strFont))font=FONT_ARIAL;
		else if("times".equals(strFont))font=FONT_TIMES;
		else if("courier".equals(strFont))font=FONT_COURIER;
		else if("arialunicodems".equals(strFont))font=FONT_ARIAL_UNICODE_MS;
		else font=strFont;
		//else throw new ExpressionException("invalid value ["+strFont+"] for attribute font, for this attribute only the following values are supported [arial,times,courier,arialunicodeMS]");
	}

	public void setFontbold(boolean fontbold) {
		if(fontbold)fontstyle+=Font.BOLD;
	}

	public void setFontitalic(boolean fontitalic) {
		if(fontitalic)fontstyle+=Font.ITALIC;
	}

	public void setFontsize(double fontsize) {
		this.fontsize = (int) fontsize;
	}

	public void setFormat(String strFormat) throws ExpressionException {
		strFormat=strFormat.trim().toLowerCase();
		if("gif".equals(strFormat))			format=FORMAT_GIF;
		else if("jpg".equals(strFormat))	format=FORMAT_JPG;
		else if("jpeg".equals(strFormat))	format=FORMAT_JPG;
		else if("jpe".equals(strFormat))	format=FORMAT_JPG;
		else if("png".equals(strFormat))	format=FORMAT_PNG;
		//else if("flash".equals(strFormat))	format=FORMAT_FLASH;
		//else if("swf".equals(strFormat))	format=FORMAT_FLASH;
		
		else throw new ExpressionException("invalid value ["+strFormat+"] for attribute format, for this attribute only the following values are supported [gif,jpg,png]");
	}

	public void setGridlines(double gridlines) {
		this.gridlines = (int) gridlines;
	}

	public void setLabelformat(String strLabelFormat) throws ExpressionException {
		strLabelFormat=strLabelFormat.trim().toLowerCase();
		if("number".equals(strLabelFormat))			labelFormat=LabelFormatUtil.LABEL_FORMAT_NUMBER;
		else if("numeric".equals(strLabelFormat))	labelFormat=LabelFormatUtil.LABEL_FORMAT_NUMBER;
		else if("currency".equals(strLabelFormat))	labelFormat=LabelFormatUtil.LABEL_FORMAT_CURRENCY;
		else if("date".equals(strLabelFormat))		labelFormat=LabelFormatUtil.LABEL_FORMAT_DATE;
		else if("percent".equals(strLabelFormat))	labelFormat=LabelFormatUtil.LABEL_FORMAT_PERCENT;
		//else if("integer".equals(strLabelFormat))	labelFormat=LabelFormatUtil.LABEL_FORMAT_INTEGER;
		
		else throw new ExpressionException("invalid value ["+strLabelFormat+"] for attribute labelFormat, for this attribute only the following values are supported [date,percent,currency,number]");
	}

	public void setMarkersize(double markersize) throws ExpressionException {
		if(markersize<1) throw new ExpressionException("invalid value ["+markersize+"] for attribute markersize, value must be a positive integer greater than 0");
		this.markersize=(int) markersize;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void setPieslicestyle(String strPieslicestyle) throws ExpressionException {
		strPieslicestyle=strPieslicestyle.trim().toLowerCase();
		if("sliced".equals(strPieslicestyle))		pieslicestyle=PIE_SLICE_STYLE_SLICED;
		else if("slice".equals(strPieslicestyle))	pieslicestyle=PIE_SLICE_STYLE_SLICED;
		else if("solid".equals(strPieslicestyle))	pieslicestyle=PIE_SLICE_STYLE_SOLID;
		
		else throw new ExpressionException("invalid value ["+strPieslicestyle+"] for attribute pieSliceStyle, for this attribute only the following values are supported [sliced,solid]");
	}

	public void setScaleto(double scaleto) {
		//if(scaleto<0) throw new ExpressionException("invalid value ["+scaleto+"] for attribute scaleto, value must be a positive integer greater or equal than 0");
		this.scaleto =  scaleto;
	}

	public void setScalefrom(double scaletrom)  {
		//if(scaletrom<0) throw new ExpressionException("invalid value ["+scaletrom+"] for attribute scaletrom, value must be a positive integer greater or equal than 0");
		this.scalefrom =  scaletrom;
	}

	public void setSeriesplacement(String strSeriesplacement) throws ExpressionException {
		strSeriesplacement=strSeriesplacement.trim().toLowerCase();
		if("default".equals(strSeriesplacement))	seriesplacement=SERIES_PLACEMENT_DEFAULT;
		else if("cluster".equals(strSeriesplacement))seriesplacement=SERIES_PLACEMENT_CLUSTER;
		else if("stacked".equals(strSeriesplacement))seriesplacement=SERIES_PLACEMENT_STACKED;
		else if("percent".equals(strSeriesplacement))seriesplacement=SERIES_PLACEMENT_PERCENT;
		
		else throw new ExpressionException("invalid value ["+strSeriesplacement+"] for attribute seriesplacement, for this attribute only the following values are supported [default,cluster,percent,stacked]");
	}

	public void setShow3d(boolean show3d) {
		this.show3d = show3d;
	}

	public void setShowborder(boolean showborder) {
		this.showborder = showborder;
	}

	public void setShowlegend(boolean showlegend) {
		this.showlegend = showlegend;
	}

	public void setShowmarkers(boolean showmarkers) {
		this.showmarkers = showmarkers;
	}

	public void setShowxgridlines(boolean showxgridlines) {
		this.showxgridlines = showxgridlines?YES:NO;
	}

	public void setShowygridlines(boolean showygridlines) {
		this.showygridlines = showygridlines;
	}

	public void setSortxaxis(boolean sortxaxis) {
		this.sortxaxis = sortxaxis;
	}

	public void setStyle(String style) {
		this.style = style;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public void setTipstyle(String strTipstyle) throws ExpressionException {
		strTipstyle=strTipstyle.trim().toLowerCase();
		if("mousedown".equals(strTipstyle))		tipstyle=TIP_STYLE_MOUSEDOWN;
		else if("mouseover".equals(strTipstyle))tipstyle=TIP_STYLE_MOUSEOVER;
		else if("none".equals(strTipstyle))	   	tipstyle=TIP_STYLE_NONE;
		else if("formats".equals(strTipstyle))	tipstyle=TIP_STYLE_FORMATS;
		
		else throw new ExpressionException("invalid value ["+strTipstyle+"] for attribute Tipstyle, for this attribute only the following values are supported [mouseover,mousedown,one,formats]");
	}
	

	
	/**
	 * @param xaxistitle the xaxistitle to set
	 */
	public void setXaxistitle(String xaxistitle) {
		this.xaxistitle = xaxistitle;
	}

	/**
	 * @param yaxistitle the yaxistitle to set
	 */
	public void setYaxistitle(String yaxistitle) {
		this.yaxistitle = yaxistitle;
	}

	public void addChartSeries(ChartSeriesBean series) {
		_series.add(series);
	}
	

	public int doStartTag()	{
		return EVAL_BODY_INCLUDE;
	}

	@Override
	public int doEndTag() throws PageException {
		if(_series.size()==0) throw new ApplicationException("at least one cfchartseries tag required inside cfchart"); 
		//if(_series.size()>1) throw new ApplicationException("only one cfchartseries tag allowed inside cfchart"); 
		//doSingleSeries((ChartSeriesBean) _series.get(0));
		ChartSeriesBean first= _series.get(0);
		
		try {
				
			if(first.getType()==ChartSeriesBean.TYPE_BAR)
				//throw new ApplicationException("type bar is not supported");
				chartBar();
			else if(first.getType()==ChartSeriesBean.TYPE_TIME)
				chartTimeLine();
			else if(first.getType()==ChartSeriesBean.TYPE_AREA)
				chartArea();
			else if(first.getType()==ChartSeriesBean.TYPE_CONE)
				throw new ApplicationException("type cone is not supported");
			else if(first.getType()==ChartSeriesBean.TYPE_CURVE)
				chartLine();
				//throw new ApplicationException("type curve is not supported");
			else if(first.getType()==ChartSeriesBean.TYPE_CYLINDER)
				throw new ApplicationException("type cylinder is not supported");
			else if(first.getType()==ChartSeriesBean.TYPE_HORIZONTALBAR)
				throw new ApplicationException("type horizontalbar is not supported");
			else if(first.getType()==ChartSeriesBean.TYPE_LINE)
				chartLine();
				//throw new ApplicationException("type line is not supported");
			else if(first.getType()==ChartSeriesBean.TYPE_PIE)
				chartPie();
			else if(first.getType()==ChartSeriesBean.TYPE_PYRAMID)
				throw new ApplicationException("type pyramid is not supported");
			else if(first.getType()==ChartSeriesBean.TYPE_SCATTER)
				throw new ApplicationException("type scatter is not supported");
			else if(first.getType()==ChartSeriesBean.TYPE_STEP)
				throw new ApplicationException("type step is not supported");
		}
		catch(IOException ioe) {
			throw Caster.toPageException(ioe);
		}
		
		return EVAL_PAGE;
	}

	private void chartPie() throws PageException, IOException {
		// do dataset
		DefaultPieDataset dataset = new DefaultPieDataset();
		ChartSeriesBean csb =  _series.get(0);
        
		ChartDataBean cdb;
        
		List datas=csb.getDatas();
		if(sortxaxis)Collections.sort(datas);
        Iterator itt = datas.iterator();
    	while(itt.hasNext()) {
    		cdb=(ChartDataBean) itt.next();
    		dataset.setValue(cdb.getItemAsString(), cdb.getValue());
    	}	
		
    	
        JFreeChart chart = show3d?
        		ChartFactory.createPieChart3D	(title, dataset, false, true, true):
        		ChartFactory.createPieChart		(title, dataset, false, true, true);
        
        Plot p = chart.getPlot();
		PiePlot pp = (PiePlot)p;
        
		Font _font = getFont();
        pp.setLegendLabelGenerator(new PieSectionLegendLabelGeneratorImpl(_font,chartwidth));
        pp.setBaseSectionOutlinePaint(Color.GRAY); // rand der stﾟcke
        pp.setLegendItemShape(new Rectangle(7,7));
        pp.setLabelFont(new Font(font,0,11));
        pp.setLabelLinkPaint(COLOR_333333);
        pp.setLabelLinkMargin(-0.05);
        pp.setInteriorGap(0.123);
        pp.setLabelGenerator(new PieSectionLabelGeneratorImpl(labelFormat));
        
        
        
        
        databackgroundcolor=backgroundcolor;
        
        setBackground(chart,p);
		setBorder(chart,p);
		setLegend(chart,p,_font);
		set3d(p);
		setFont(chart, _font);
        setTooltip(chart);
        setScale(chart);
        
        // Slice Type and colors
        boolean doSclice=pieslicestyle==PIE_SLICE_STYLE_SLICED;
        Color[] colors = csb.getColorlist();
        Iterator it = csb.getDatas().iterator();
        int count=0;
    	while(it.hasNext()) {
    		cdb=(ChartDataBean) it.next();
            if(doSclice)pp.setExplodePercent(cdb.getItemAsString(), 0.13);
            
            if(count<colors.length){
            	pp.setSectionPaint(cdb.getItemAsString(), colors[count]);
            }
            count++;
    	}
        
        writeOut(chart);
	}
	

	private void set3d(Plot plot) {
        if(!show3d) return;
        
        plot.setForegroundAlpha(0.6f);
        
        if(plot instanceof CategoryPlot) {
            plot.setForegroundAlpha(0.8f);
        	CategoryPlot cp=(CategoryPlot) plot;
        	CategoryItemRenderer renderer = cp.getRenderer();
        	if(renderer instanceof BarRenderer3D) {
        		BarRenderer3D br3d=(BarRenderer3D) renderer;
        		cp.setRenderer(new BarRenderer3DWrap(br3d,xoffset,yoffset));
        	}
        	
        }
        else if(plot instanceof PiePlot3D) {
        	PiePlot3D pp3d=(PiePlot3D) plot;
            pp3d.setDepthFactor(0.10);    
        }
        
        

        //CategoryItemRenderer renderer = plot.getRenderer();
        
	}

	private void setFont(JFreeChart chart, Font font) {
		// title
		TextTitle title=chart.getTitle();
		if(title!=null) {
			title.setFont(font);
			title.setPaint(foregroundcolor);
			chart.setTitle(title);
		}
		
		// axis fonts
		Plot plot = chart.getPlot();
		if(plot instanceof CategoryPlot) {
			CategoryPlot cp = (CategoryPlot)plot;
			setAxis(cp.getRangeAxis(),font);
			setAxis(cp.getDomainAxis(),font);
		}
		if(plot instanceof XYPlot) {
			XYPlot cp = (XYPlot)plot;
			setAxis(cp.getRangeAxis(),font);
			setAxis(cp.getDomainAxis(),font);
		}
		
		
	}
	
	
	private void setAxis(Axis axis, Font font) {
		if(axis!=null) {
			axis.setLabelFont(font);
			axis.setLabelPaint(foregroundcolor);
			
			axis.setTickLabelFont(font);
	        axis.setTickLabelPaint(foregroundcolor);
	        axis.setTickLabelsVisible(true);
		}
	}



	private void setLegend(JFreeChart chart, Plot plot, Font font) {
		if(!showlegend) return;
			
			
		Color bg = backgroundcolor==null?databackgroundcolor:backgroundcolor;
		if(font==null)font=getFont();
		
		
		
		LegendTitle legend = legendMultiLine?
        		new LegendTitle(plot,new ColumnArrangement(), new ColumnArrangement()):
        		new LegendTitle(plot);
        legend.setBackgroundPaint(bg);
        legend.setMargin(new RectangleInsets(1.0, 1.0, 1.0, 1.0));
        legend.setFrame(new LineBorder());
        legend.setPosition(RectangleEdge.BOTTOM);
        legend.setHorizontalAlignment(HorizontalAlignment.LEFT);
        
        legend.setWidth(chartwidth-20);// geht nicht
        legend.setItemFont(font);
		legend.setItemPaint(foregroundcolor);

		//RectangleInsets labelPadding;
		legend.setItemLabelPadding(new RectangleInsets(2,2,2,2));
		legend.setBorder(0,0,0,0); 
		legend.setLegendItemGraphicLocation(RectangleAnchor.TOP_LEFT);
		legend.setLegendItemGraphicPadding(new RectangleInsets(8,10,0,0));
		chart.addLegend(legend);
		
	}



	private void setBorder(JFreeChart chart, Plot plot) {
		chart.setBorderVisible(false);
		chart.setBorderPaint(foregroundcolor);
		plot.setOutlinePaint(foregroundcolor);
	}



	private void setBackground(JFreeChart chart, Plot plot) {
		//Color bg = backgroundcolor==null?databackgroundcolor:backgroundcolor;

		chart.setBackgroundPaint(backgroundcolor);
		plot.setBackgroundPaint(databackgroundcolor);
		chart.setBorderPaint(databackgroundcolor);
		
		
        plot.setOutlineVisible(false);
		
		// Pie
		if(plot instanceof PiePlot) {
			PiePlot pp=(PiePlot) plot;
			pp.setLabelOutlinePaint(backgroundcolor); 
	        pp.setLabelBackgroundPaint(backgroundcolor);
	        pp.setLabelShadowPaint(backgroundcolor);
	        pp.setShadowPaint(backgroundcolor);
		}
		// Bar
		/*if(plot instanceof CategoryPlot) {
			CategoryPlot cp=(CategoryPlot) plot;
			
		}*/
	}



	
 
	private Font getFont() {
		return new Font(font,fontstyle,fontsize);
	}

	private void writeOut(JFreeChart jfc) throws PageException, IOException {
		final ChartRenderingInfo info=new ChartRenderingInfo();
        
		// map name
		chartIndex++;
		if(chartIndex<0)chartIndex=0;
		String mapName="chart_"+(chartIndex++);
		
		// write out to variable
		if(!StringUtil.isEmpty(name)){
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			copy(baos, jfc,info);
			pageContext.setVariable(name, baos.toByteArray());
			return;
		}
		
		// write out as link
		String id=Md5.getDigestAsString(JavaConverter.serialize(this));
		Resource graph = pageContext.getConfig().getTempDirectory().getRealResource("graph");
		Resource res = graph.getRealResource(id);
		if(!res.exists()) {
			clean(graph);
			copy(res.getOutputStream(),jfc,info);
		}
		
		String src="/railo-context/graph.cfm?img="+id+"&type="+formatToString(format);
		if(!StringUtil.isEmpty(source)) {
			pageContext.setVariable(source, src);
			return;
		}
		try {
			// Tooltip
			if(showtooltip) {
				ToolTipTagFragmentGenerator tttfg = new ToolTipTagFragmentGeneratorImpl(url);
				URLTagFragmentGenerator utfg=new EmptyURLTagFragmentGenerator();
				
				String map=ImageMapUtilities.getImageMap(mapName, info,tttfg,utfg).trim();
				pageContext.write(map);
			}
			pageContext.write("<img border=\"0\" usemap=\"#"+mapName+"\" src=\""+src+"\">");
		} 
		catch (IOException e) {
			throw Caster.toPageException(e);
		}		
	}

	private void copy(OutputStream os, JFreeChart jfc, ChartRenderingInfo info) throws ApplicationException, IOException, ExpressionException {
		//OutputStream os = null;
		try {
			//os = res.getOutputStream();
			BufferedImage bi = jfc.createBufferedImage(chartwidth,chartheight,info);
			Image img;
			
			// add border
			if(showborder) {
				try {
					img = new Image(bi);
					img.addBorder(1,Color.BLACK,Image.BORDER_TYPE_CONSTANT);
					bi=img.getBufferedImage();
				}
				catch (PageException e) {}
			}
			if(format==FORMAT_PNG)		ChartUtilities.writeBufferedImageAsPNG(os, bi);
			else if(format==FORMAT_JPG)	ChartUtilities.writeBufferedImageAsJPEG(os, bi);
			else if(format==FORMAT_GIF)	{
				img = new railo.runtime.img.Image(bi);
				img.writeOut(os, "gif",1,true);
				
				//throw new ApplicationException("format gif not supported");
			}
			else if(format==FORMAT_FLASH)throw new ApplicationException("format flash not supported");
		}
		finally {
			IOUtil.flushEL(os);
			IOUtil.closeEL(os);
		}
	}

	private String formatToString(int format) {
		if(format==FORMAT_GIF) return "gif";
		if(format==FORMAT_JPG) return "jpeg";
		if(format==FORMAT_PNG) return "png";
		return "swf";
	}

	private void clean(Resource graph) throws IOException {
		if(!graph.exists())graph.createDirectory(true);
		else if(graph.isDirectory() && ResourceUtil.getRealSize(graph)>(1024*1024)) {
			
			Resource[] children = graph.listResources();
			long maxAge=System.currentTimeMillis()-(1000*60);
			for(int i=0;i<children.length;i++) {
				if(children[i].lastModified()<maxAge)
					children[i].delete();
			}
		}
	}

	private void chartBar() throws PageException, IOException {
		// create the chart...
        final JFreeChart chart = show3d?
        	ChartFactory.createBarChart3D(title,xaxistitle,yaxistitle,createDatasetCategory(),PlotOrientation.VERTICAL,false,true,false):
        	ChartFactory.createBarChart  (title,xaxistitle,yaxistitle,createDatasetCategory(),PlotOrientation.VERTICAL,false,true,false);
        Plot p = chart.getPlot();
        Font _font = getFont();
        // settings
        
        
        setBackground(chart,p);
		setBorder(chart,p);
		set3d(p);
		setFont(chart,_font);
		setLabelFormat(chart);
		setLegend(chart, p, _font);
        setTooltip(chart);
		setScale(chart);
        setAxis(chart);
        setColor(chart);
        
        writeOut(chart);
	}
	




	private void chartLine() throws PageException, IOException {
		// create the chart...
        final JFreeChart chart = show3d?
        	ChartFactory.createLineChart3D(title,xaxistitle,yaxistitle,createDatasetCategory(),PlotOrientation.VERTICAL,false,true,false):
        	ChartFactory.createLineChart(title,xaxistitle,yaxistitle,createDatasetCategory(),PlotOrientation.VERTICAL,false,true,false);
        Plot p = chart.getPlot();
        Font _font = getFont();
        
        // settings
        setMarker(chart,p);
        setBackground(chart,p);
		setBorder(chart,p);
		set3d(p);
		setFont(chart,_font);
		setLabelFormat(chart);
		setLegend(chart, p, _font);
        setTooltip(chart);
		setScale(chart);
        setAxis(chart);
        setColor(chart);
        
        writeOut(chart);
	}
	
	private void chartArea() throws PageException, IOException {
		// create the chart...
        final JFreeChart chart = ChartFactory.createAreaChart(title,xaxistitle,yaxistitle,createDatasetCategory(),PlotOrientation.VERTICAL,false,true,false);
        Plot p = chart.getPlot();
        Font _font = getFont();
        
        // settings
        setMarker(chart,p);
        setBackground(chart,p);
		setBorder(chart,p);
		set3d(p);
		setFont(chart,_font);
		setLabelFormat(chart);
		setLegend(chart, p, _font);
        setTooltip(chart);
		setScale(chart);
        setAxis(chart);
        setColor(chart);
        
        writeOut(chart);
	}
	
	private void chartTimeLine() throws PageException, IOException {
		// create the chart...
        final JFreeChart chart = ChartFactory.createTimeSeriesChart(title,xaxistitle,yaxistitle,createTimeSeriesCollection(),false,true,false);
        Plot p = chart.getPlot();
        Font _font = getFont();
        
        // settings
        setMarker(chart,p);
        setBackground(chart,p);
		setBorder(chart,p);
		set3d(p);
		setFont(chart,_font);
		setLabelFormat(chart);
		setLegend(chart, p, _font);
        setTooltip(chart);
		setScale(chart);
        setAxis(chart);
        setColor(chart);
        
        writeOut(chart);
	}



	private void setMarker(JFreeChart chart, Plot p) {
		if(!showmarkers) return;
		
		if(markersize<1 || markersize>100) markersize=4;
		
		
		
		if(p instanceof XYPlot) {
			XYPlot xyp=(XYPlot) p;
			XYItemRenderer r = xyp.getRenderer();
			if (r instanceof XYLineAndShapeRenderer) {
				XYLineAndShapeRenderer xyr = (XYLineAndShapeRenderer) r;
				xyr.setBaseShapesVisible(true);
				xyr.setBaseShapesFilled(true);
				
				int seriesCount=_series.size();
				for(int i=0;i<seriesCount;i++){
					xyr.setSeriesShapesVisible(i, true);
					xyr.setSeriesItemLabelsVisible(i, true);
					xyr.setSeriesShape(i, ShapeUtilities.createDiamond(markersize));
					xyr.setUseFillPaint(true);
					xyr.setBaseFillPaint(databackgroundcolor);
				}
			}
		}
		else if(p instanceof CategoryPlot) {
			CategoryPlot cp=(CategoryPlot) p;
			CategoryItemRenderer r = cp.getRenderer();
			if (r instanceof LineAndShapeRenderer) {
				LineAndShapeRenderer lsr = (LineAndShapeRenderer)r;
			
				int seriesCount=_series.size();
				for(int i=0;i<seriesCount;i++){
					lsr.setSeriesShapesVisible(i, true);
					lsr.setSeriesItemLabelsVisible(i, true);
					lsr.setSeriesShape(i, ShapeUtilities.createDiamond(markersize));
			        lsr.setUseFillPaint(true);
			        lsr.setBaseFillPaint(databackgroundcolor);
				}
			}
		}
	}



	private void setAxis(JFreeChart chart) {
		Plot plot = chart.getPlot();
		if(plot instanceof CategoryPlot) {
			CategoryPlot cp=(CategoryPlot)plot;
			
			// Y
			cp.setDomainGridlinesVisible(showygridlines);
			if(showygridlines) cp.setDomainGridlinePaint(foregroundcolor);
			
			cp.setRangeGridlinesVisible(showxgridlines!=NO);
			if(showxgridlines==NONE)cp.setRangeGridlinePaint(Color.GRAY);
			else if(showxgridlines==YES)cp.setRangeGridlinePaint(foregroundcolor);
		}
		else if(plot instanceof XYPlot) {
			XYPlot cp=(XYPlot)plot;
			
			// Y
			cp.setDomainGridlinesVisible(showygridlines);
			if(showygridlines) cp.setDomainGridlinePaint(foregroundcolor);
			
			cp.setRangeGridlinesVisible(showxgridlines!=NO);
			if(showxgridlines==NONE)cp.setRangeGridlinePaint(Color.GRAY);
			else if(showxgridlines==YES)cp.setRangeGridlinePaint(foregroundcolor);
		}
	}



	private void setTooltip(JFreeChart chart) {
		Plot plot = chart.getPlot();
		if(plot instanceof PiePlot) {
			PiePlot pp = (PiePlot)plot;		
			
			pp.setToolTipGenerator(new PieToolTipGeneratorImpl(labelFormat));
			
		}
		else if(plot instanceof CategoryPlot) {
			CategoryPlot cp=(CategoryPlot) plot;
			CategoryItemRenderer renderer = cp.getRenderer();
			renderer.setBaseToolTipGenerator(new CategoryToolTipGeneratorImpl(labelFormat));
		}
		/*else if(plot instanceof XYPlot) {
			XYPlot cp=(XYPlot) plot;
			XYItemRenderer renderer = cp.getRenderer();
			renderer.setBaseToolTipGenerator(new XYToolTipGeneratorImpl(labelFormat));
		}*/
		
	}



	private void setScale(JFreeChart chart) {
		Plot plot = chart.getPlot();
		if(plot instanceof CategoryPlot) {
			CategoryPlot cp=(CategoryPlot) plot;
			ValueAxis rangeAxis = cp.getRangeAxis();
			Range r=rangeAxis.getRange();
			double lower=r.getLowerBound();
			double upper=r.getUpperBound();
			
			if(labelFormat==LabelFormatUtil.LABEL_FORMAT_DATE && rangeAxis.getRange().getLowerBound()==0) {
				lower = smallest;
				upper=biggest;
				try	{
					DateTime d = Caster.toDate(Caster.toDouble(lower),true,null,null);
					lower = DateAdd.call(pageContext,"yyyy", -1, d).castToDoubleValue(lower);	
				}
				catch (PageException e) {}
			}
			if(!Double.isNaN(scalefrom))lower=scalefrom;
			if(!Double.isNaN(scaleto))upper=scaleto;
			rangeAxis.setRange(new Range(lower,upper),true,true);
		}
		else if(plot instanceof XYPlot) {
			XYPlot cp=(XYPlot) plot;
			ValueAxis rangeAxis = cp.getRangeAxis();
			Range r=rangeAxis.getRange();
			double lower=r.getLowerBound();
			double upper=r.getUpperBound();
			
			if(labelFormat==LabelFormatUtil.LABEL_FORMAT_DATE && rangeAxis.getRange().getLowerBound()==0) {
				lower = smallest;
				upper=biggest;
				try	{
					DateTime d = Caster.toDate(Caster.toDouble(lower),true,null,null);
					lower = DateAdd.call(pageContext,"yyyy", -1, d).castToDoubleValue(lower);	
				}
				catch (PageException e) {}
			}
			if(!Double.isNaN(scalefrom))lower=scalefrom;
			if(!Double.isNaN(scaleto))upper=scaleto;
			rangeAxis.setRange(new Range(lower,upper),true,true);
		}
	}

	private void setLabelFormat(JFreeChart chart) {
		Plot plot = chart.getPlot();
		if(plot instanceof CategoryPlot) {
			CategoryPlot cp=(CategoryPlot) plot;
			ValueAxis rangeAxis = cp.getRangeAxis();
			rangeAxis.setAutoTickUnitSelection(true);
			rangeAxis.setStandardTickUnits(new TickUnitsImpl(rangeAxis.getStandardTickUnits(),labelFormat));
			CategoryItemRenderer r = cp.getRenderer();
			r.setBaseItemLabelsVisible(false);
			
			CategoryAxis da = cp.getDomainAxis();
			if(!showXLabel)da.setTickLabelsVisible(false);
			//da.setVisible(false);
		}
		if(plot instanceof XYPlot) {
			XYPlot cp=(XYPlot) plot;
			ValueAxis rangeAxis = cp.getRangeAxis();
			rangeAxis.setAutoTickUnitSelection(true);
			rangeAxis.setStandardTickUnits(new TickUnitsImpl(rangeAxis.getStandardTickUnits(),labelFormat));
			XYItemRenderer r = cp.getRenderer();
			r.setBaseItemLabelsVisible(false);
			
			ValueAxis da = cp.getDomainAxis();
			if(!showXLabel)da.setTickLabelsVisible(false);
			//da.setVisible(false);
		}
	}



	// set individual colors for series
	private void setColor(JFreeChart chart) {
		Plot p = chart.getPlot();
		if(p instanceof CategoryPlot) {
			CategoryPlot cp=(CategoryPlot) p;
			
			CategoryItemRenderer renderer = cp.getRenderer();
	        
			
			
			Iterator<ChartSeriesBean> cs = _series.iterator();
			//int seriesCount=_series.size();
			ChartSeriesBean csb;
			GradientPaint gp;
			Color c=null;
			Color[] ac;
			
			int index=0;
			while(cs.hasNext()) {
				csb= cs.next();
				// more than 1 series
				//if(seriesCount>1) {
					c=csb.getSeriesColor();
					if(c==null) {
						ac=csb.getColorlist();
						if(ac!=null && ac.length>0)c=ac[0];
					}
					
				//}
				if(c==null) continue;
				gp = new GradientPaint(0.0f, 0.0f, c, 0.0f, 0.0f,c);
				renderer.setSeriesPaint(index++, gp);
			}
		}
		else if(p instanceof XYPlot) {
			XYPlot cp=(XYPlot) p;
			
			XYItemRenderer renderer = cp.getRenderer();
	        
			
			
			Iterator<ChartSeriesBean> cs = _series.iterator();
			//int seriesCount=_series.size();
			ChartSeriesBean csb;
			GradientPaint gp;
			Color c=null;
			Color[] ac;
			
			int index=0;
			while(cs.hasNext()) {
				csb= cs.next();
				// more than 1 series
				//if(seriesCount>1) {
					c=csb.getSeriesColor();
					if(c==null) {
						ac=csb.getColorlist();
						if(ac!=null && ac.length>0)c=ac[0];
					}
					
				//}
				if(c==null) continue;
				gp = new GradientPaint(0.0f, 0.0f, c, 0.0f, 0.0f,c);
				renderer.setSeriesPaint(index++, gp);
			}
		}
	}



	private DefaultPieDataset createDatasetPie() {
		DefaultPieDataset dataset = new DefaultPieDataset();
		ChartSeriesBean csb =  _series.get(0);
        
		ChartDataBean cdb;
        // write data set
        Iterator itt = csb.getDatas().iterator();
    	while(itt.hasNext()) {
    		cdb=(ChartDataBean) itt.next();
    		dataset.setValue(cdb.getItemAsString(), cdb.getValue());
    	}	
    	return dataset;
    }
	

	
	

	private CategoryDataset createDatasetCategory() {
        final DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        Iterator<ChartSeriesBean> it = _series.iterator();
        //int seriesCount=_series.size();
        Iterator itt;
        List datas;
        ChartSeriesBean csb;
        ChartDataBean cdb;
        int count=0;
        smallest=Double.MAX_VALUE;
        biggest = Double.MIN_VALUE;
        String label;
        boolean hasLabels=false;
        while(it.hasNext()) {
        	count++;
        	csb= it.next();
        	label=csb.getSeriesLabel();
        	if(StringUtil.isEmpty(label))label=""+count;
        	else hasLabels=true;
        	datas = csb.getDatas();
        	if(sortxaxis)Collections.sort(datas);
            itt=datas.iterator();
            while(itt.hasNext()) {
        		cdb=(ChartDataBean) itt.next();
        		if(smallest>cdb.getValue())smallest=cdb.getValue();
        		if(biggest<cdb.getValue())biggest=cdb.getValue();
        		//if(seriesCount>1)
        		
        		dataset.addValue(cdb.getValue(), label,cdb.getItemAsString());
        		
        		//else dataset.addValue(cdb.getValue(), cdb.getItem(),"");
        		
            	
        	}
        }
        if(!hasLabels)showlegend=false;
        return dataset;
    }
	private XYDataset createTimeSeriesCollection() {
		TimeZone tz = ThreadLocalPageContext.getTimeZone();
		final TimeSeriesCollection coll=new TimeSeriesCollection(tz);
		
        //final DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        Iterator<ChartSeriesBean> it = _series.iterator();
        //int seriesCount=_series.size();
        Iterator itt;
        List datas;
        ChartSeriesBean csb;
        ChartDataBean cdb;
        int count=0;
        smallest=Double.MAX_VALUE;
        biggest = Double.MIN_VALUE;
        String label;
        boolean hasLabels=false;
        while(it.hasNext()) {
        	count++;
        	csb=it.next();
        	label=csb.getSeriesLabel();
        	if(StringUtil.isEmpty(label))label=""+count;
        	else hasLabels=true;
        	datas = csb.getDatas();
        	if(sortxaxis)Collections.sort(datas);
            itt=datas.iterator();
            TimeSeries ts=new TimeSeries(label,Second.class);
            while(itt.hasNext()) {
        		cdb=(ChartDataBean) itt.next();
        		if(smallest>cdb.getValue())smallest=cdb.getValue();
        		if(biggest<cdb.getValue())biggest=cdb.getValue();
        		//if(seriesCount>1)
        		ts.addOrUpdate(new Second(DateCaster.toDateSimple(cdb.getItem(),false,false, tz,null)), cdb.getValue());
        		
        		//else dataset.addValue(cdb.getValue(), cdb.getItem(),"");
        		
            	
        	}
            coll.addSeries(ts);
        }
        if(!hasLabels)showlegend=false;
        return coll;
    }

	/**
	 * @param url the url to set
	 */
	public void setUrl(String url) {
		this.url = url;
	}

	/**
	 * @param xoffset the xoffset to set
	 */
	public void setXoffset(double xoffset) {
		this.xoffset = xoffset;
	}

	/**
	 * @param yoffset the yoffset to set
	 */
	public void setYoffset(double yoffset) {
		this.yoffset = yoffset;
	}

	/**
	 * @param yaxistype the yaxistype to set
	 */
	public void setYaxistype(String yaxistype) {
		this.yaxistype = yaxistype;
	}
	/**
	 * @param yaxistype the yaxistype to set
	 */
	public void setXaxistype(String xaxistype) {
		this.xaxistype = xaxistype;
	}
	
}
