package org.jboss.seam.pdf.ui;

import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Stroke;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import org.jboss.seam.pdf.ITextUtils;
import org.jboss.seam.ui.graphicImage.GraphicImageResource;
import org.jboss.seam.ui.graphicImage.GraphicImageStore;
import org.jboss.seam.ui.graphicImage.GraphicImageStore.ImageWrapper;
import org.jboss.seam.ui.graphicImage.Image.Type;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.general.Dataset;

import com.lowagie.text.*;
import com.lowagie.text.pdf.DefaultFontMapper;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfTemplate;
import com.lowagie.text.pdf.PdfWriter;
import com.lowagie.text.pdf.AsianFontMapper;

public abstract class UIChart extends ITextComponent {
    private Image image = null;
    private JFreeChart chart = null;

    private byte[] imageData;

    private int height = 300;
    private int width = 400;

    private boolean legend;
    private boolean is3D = false;

    private String title;
    private String borderBackgroundPaint;
    private String borderPaint;
    private String borderStroke;
    private boolean borderVisible = true;

    private String plotBackgroundPaint;
    private Float plotBackgroundAlpha;
    private Float plotForegroundAlpha;
    private String plotOutlineStroke;
    private String plotOutlinePaint;

    protected Dataset dataset;

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

    public String getTitle() {
        return (String) valueBinding("title", title);
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getHeight() {
        return (Integer) valueBinding(FacesContext.getCurrentInstance(), "height", height);
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getWidth() {
        return (Integer) valueBinding(FacesContext.getCurrentInstance(), "width", width);
    }

    public void setLegend(boolean legend) {
        this.legend = legend;
    }

    public boolean getLegend() {
        return (Boolean) valueBinding("legend", legend);
    }

    public void setIs3D(boolean is3D) {
        this.is3D = true;
    }

    public boolean getIs3D() {
        return (Boolean) valueBinding("is3D", is3D);
    }

    public void setBorderBackgroundPaint(String backgroundPaint) {
        this.borderBackgroundPaint = backgroundPaint;
    }

    public String getBorderBackgroundPaint() {
        return (String) valueBinding(FacesContext.getCurrentInstance(), "borderBackgroundPaint", borderBackgroundPaint);
    }

    public void setBorderPaint(String borderPaint) {
        this.borderPaint = borderPaint;
    }

    public String getBorderPaint() {
        return (String) valueBinding(FacesContext.getCurrentInstance(), "borderPaint", borderPaint);
    }

    public void setBorderStroke(String borderStroke) {
        this.borderStroke = borderStroke;
    }

    public String getBorderStroke() {
        return (String) valueBinding(FacesContext.getCurrentInstance(), "borderStroke", borderStroke);
    }

    public void setBorderVisible(boolean borderVisible) {
        this.borderVisible = borderVisible;
    }

    public boolean getBorderVisible() {
        return (Boolean) valueBinding(FacesContext.getCurrentInstance(), "borderVisible", borderVisible);
    }

    public void setPlotBackgroundAlpha(Float plotBackgroundAlpha) {
        this.plotBackgroundAlpha = plotBackgroundAlpha;
    }

    public Float getPlotBackgroundAlpha() {
        return (Float) valueBinding(FacesContext.getCurrentInstance(), "plotBackgroundAlpha", plotBackgroundAlpha);
    }

    public void setPlotBackgroundPaint(String plotBackgroundPaint) {
        this.plotBackgroundPaint = plotBackgroundPaint;
    }

    public String getPlotBackgroundPaint() {
        return (String) valueBinding(FacesContext.getCurrentInstance(), "plotBackgroundPaint", plotBackgroundPaint);
    }

    public void setPlotForegroundAlpha(Float plotForegroundAlpha) {
        this.plotForegroundAlpha = plotForegroundAlpha;
    }

    public Float getPlotForegroundAlpha() {
        return (Float) valueBinding(FacesContext.getCurrentInstance(), "plotForegroundAlpha", plotForegroundAlpha);
    }

    public void setPlotOutlinePaint(String plotOutlinePaint) {
        this.plotOutlinePaint = plotOutlinePaint;
    }

    public String getPlotOutlinePaint() {
        return (String) valueBinding(FacesContext.getCurrentInstance(), "plotOutlinePaint", plotOutlinePaint);
    }

    public void setPlotOutlineStroke(String plotOutlineStroke) {
        this.plotOutlineStroke = plotOutlineStroke;
    }

    public String getPlotOutlineStroke() {
        return (String) valueBinding(FacesContext.getCurrentInstance(), "plotOutlineStroke", plotOutlineStroke);
    }

    public void setDataset(Dataset dataset) {
        this.dataset = dataset;
    }

    public Dataset getDataset() {
        return (Dataset) valueBinding(FacesContext.getCurrentInstance(), "dataset", dataset);
    }
    
    public void setChart(JFreeChart chart) {
       this.chart = chart;
   }

   public JFreeChart getChart() {
       return (JFreeChart) valueBinding(FacesContext.getCurrentInstance(), "chart", chart);
   }
    
    @Override
    public void restoreState(FacesContext context, Object state) {
        Object[] values = (Object[]) state;
        super.restoreState(context, values[0]);

        height = (Integer) values[1];
        width = (Integer) values[2];
        borderBackgroundPaint = (String) values[3];
        borderPaint = (String) values[4];
        borderStroke = (String) values[5];
        borderVisible = (Boolean) values[6];
        plotBackgroundPaint = (String) values[7];
        plotBackgroundAlpha = (Float) values[8];
        plotForegroundAlpha = (Float) values[9];
        plotOutlineStroke = (String) values[10];
        plotOutlinePaint = (String) values[11];
        title = (String) values[12];
        is3D = (Boolean) values[13];
        legend = (Boolean) values[14];
    }

    @Override
    public Object saveState(FacesContext context) {
        Object[] values = new Object[15];

        values[0] = super.saveState(context);
        values[1] = height;
        values[2] = width;
        values[3] = borderBackgroundPaint;
        values[4] = borderPaint;
        values[5] = borderStroke;
        values[6] = borderVisible;
        values[7] = plotBackgroundPaint;
        values[8] = plotBackgroundAlpha;
        values[9] = plotForegroundAlpha;
        values[10] = plotOutlineStroke;
        values[11] = plotOutlinePaint;
        values[12] = title;
        values[13] = is3D;
        values[14] = legend;

        return values;
    }

    public static Paint findColor(String name) {
        if (name == null || name.length() == 0) {
            return null;
        }
        UIComponent component = FacesContext.getCurrentInstance().getViewRoot()
                .findComponent(name);

        if (component != null) {
            if (component instanceof UIColor) {
                return ((UIColor) component).getPaint();
            } else {
                throw new RuntimeException();
            }
        }

        return ITextUtils.colorValue(name);
    }

    public static Stroke findStroke(String id) {
        if (id == null || id.length() == 0) {
            return null;
        }

        UIComponent component = FacesContext.getCurrentInstance().getViewRoot().findComponent(id);

        if (component instanceof UIStroke) {
            return ((UIStroke) component).getStroke();
        } else {
            throw new RuntimeException();

        }
    }

    public abstract JFreeChart createChart(FacesContext context);

    @Override
    public void createITextObject(FacesContext context) {

        if (getBorderBackgroundPaint() != null) {
            chart.setBackgroundPaint(findColor(getBorderBackgroundPaint()));
        }

        if (getBorderPaint() != null) {
            chart.setBorderPaint(findColor(getBorderPaint()));
        }

        if (getBorderStroke() != null) {
            chart.setBorderStroke(findStroke(getBorderStroke()));
        }

        chart.setBorderVisible(getBorderVisible());

        configurePlot(chart.getPlot());

        try {
            UIDocument doc = (UIDocument) findITextParent(getParent(),
                    UIDocument.class);
            if (doc != null) {
                PdfWriter writer = (PdfWriter) doc.getWriter();
                PdfContentByte cb = writer.getDirectContent();
                PdfTemplate tp = cb.createTemplate(getWidth(), getHeight());

                UIFont font = (UIFont) findITextParent(this, UIFont.class);

                DefaultFontMapper fontMapper;
                if (font == null) {
                    fontMapper = new DefaultFontMapper();
                } else {
                    fontMapper = new AsianFontMapper(font.getName(), font.getEncoding());
                }

                Graphics2D g2 = tp.createGraphics(getWidth(), getHeight(), fontMapper);
                chart.draw(g2, new Rectangle2D.Double(0, 0, getWidth(), getHeight()));
                g2.dispose();

                image = new ImgTemplate(tp);
            } else {
                ByteArrayOutputStream stream = new ByteArrayOutputStream();
                ChartUtilities.writeChartAsJPEG(stream, chart, getWidth(),
                        getHeight());

                imageData = stream.toByteArray();
                stream.close();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void configurePlot(Plot plot) {
        if (getPlotBackgroundAlpha() != null) {
            plot.setBackgroundAlpha(getPlotBackgroundAlpha());
        }
        if (getPlotForegroundAlpha() != null) {
            plot.setForegroundAlpha(getPlotForegroundAlpha());
        }
        if (getPlotBackgroundPaint() != null) {
            plot.setBackgroundPaint(findColor(getPlotBackgroundPaint()));
        }
        if (getPlotOutlinePaint() != null) {
            plot.setOutlinePaint(findColor(getPlotOutlinePaint()));
        }
        if (getPlotOutlineStroke() != null) {
            plot.setOutlineStroke(findStroke(getPlotOutlineStroke()));
        }
    }

    public PlotOrientation plotOrientation(String orientation) {
        if (orientation != null && orientation.equalsIgnoreCase("horizontal")) {
            return PlotOrientation.HORIZONTAL;
        } else {
            return PlotOrientation.VERTICAL;
        }
    }

    @Override
    public void encodeBegin(FacesContext context) throws IOException {
        // bypass super to avoid createITextObject() before the chart is ready
        dataset = createDataset();
        
        chart = getChart();
        if (chart == null) {
            chart = createChart(context);
        }
    }

    @Override
    public void encodeEnd(FacesContext context) throws IOException {
        // call create here so that we'll have a valid chart
        createITextObject(context);

        if (imageData != null) {
            ResponseWriter response = context.getResponseWriter();
            response.startElement("img", null);
            GraphicImageStore store = GraphicImageStore.instance();
            String key = store.put(new ImageWrapper(imageData, Type.IMAGE_JPEG));
            String url = context.getExternalContext().getRequestContextPath()
                    + GraphicImageResource.GRAPHIC_IMAGE_RESOURCE_PATH + "/"
                    + key + Type.IMAGE_JPEG.getExtension();

            response.writeAttribute("src", url, null);

            response.writeAttribute("height", getHeight(), null);
            response.writeAttribute("width", getWidth(), null);

            response.endElement("img");
        }

        super.encodeEnd(context);
    }

    @Override
    public Object getITextObject() {
        return image;
    }

    @Override
    public void handleAdd(Object arg0) {
        throw new RuntimeException("No children allowed");
    }

    @Override
    public void removeITextObject() {
        image = null;
        chart = null;
    }

    public abstract Dataset createDataset();
}
