/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 */

package com.queplix.core.server.app.rpc;

import java.awt.Color;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Paint;
import java.io.IOException;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartRenderingInfo;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.entity.CategoryItemEntity;
import org.jfree.chart.entity.ChartEntity;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.entity.PieSectionEntity;
import org.jfree.chart.labels.StandardCategoryToolTipGenerator;
import org.jfree.chart.labels.StandardPieToolTipGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.DefaultDrawingSupplier;
import org.jfree.chart.plot.DrawingSupplier;
import org.jfree.chart.plot.PiePlot3D;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.servlet.ServletUtilities;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.CategoryToPieDataset;
import org.jfree.ui.RectangleInsets;
import org.jfree.util.TableOrder;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.queplix.core.client.app.rpc.ChartService;
import com.queplix.core.client.app.rpc.DisplayableException;
import com.queplix.core.client.app.rpc.DisplayableStackTraceException;
import com.queplix.core.client.app.vo.ChartFieldDataRequest;
import com.queplix.core.client.app.vo.ChartFieldOnDemandData;
import com.queplix.core.client.app.vo.FieldOnDemandData;
import com.queplix.core.client.app.vo.chart.ChartData;
import com.queplix.core.client.app.vo.chart.ChartDataItem;
import com.queplix.core.client.app.vo.chart.ChartDetails;
import com.queplix.core.client.app.vo.chart.ChartMeta;
import com.queplix.core.client.app.vo.chart.ChartOrientation;
import com.queplix.core.client.app.vo.chart.ChartRequestObject;
import com.queplix.core.client.app.vo.chart.ChartResponseObject;
import com.queplix.core.client.app.vo.chart.ChartType;
import com.queplix.core.integrator.ActionContext;
import com.queplix.core.integrator.IntegratorHelper;
import com.queplix.core.integrator.chart.ChartDataManager;
import com.queplix.core.integrator.security.LogonSession;
import com.queplix.core.integrator.security.WebLoginManager;
import com.queplix.core.modules.eql.error.EQLException;
import com.queplix.core.server.chart.QBarRenderer3D;
import com.queplix.core.server.chart.QDrawingSupplier;
import com.queplix.core.server.chart.UnsupportedChartException;
import com.queplix.core.server.chart.UnsupportedChartTypeException;
import com.queplix.core.utils.StringHelper;
import com.queplix.core.utils.log.AbstractLogger;
import com.queplix.core.utils.log.Log;
import com.queplix.core.utils.www.ServletHelper;

public class ChartServiceImpl extends RemoteServiceServlet
        implements ChartService {

    private static final AbstractLogger logger = Log.getLog(
            ChartServiceImpl.class);

    private static final String INIT_PARAM_CHART_SERVLET_PATH_PATTERN
            = "ChartServletPathPattern";

    private static String CHART_SERVLET_PATH_PATTERN;
    private static Paint CHART_BACKGROUND_PAINT;

    private static final Font TITLE_FONT = new Font("Arial", Font.PLAIN, 15);

    public ChartServiceImpl() {
    }

    public void init(ServletConfig config) throws ServletException {

        super.init(config);

        // Gets default chart's settings
        CHART_SERVLET_PATH_PATTERN = ServletHelper.getInitParamAsString(
                config, INIT_PARAM_CHART_SERVLET_PATH_PATTERN);
    }

    public static FieldOnDemandData buildChart(ChartFieldDataRequest request,
            HttpServletRequest httpRequest, String chartServletPathPattern)
                    throws DisplayableStackTraceException{
        
        ChartFieldOnDemandData response = null;
        try {
            long time = System.currentTimeMillis();

            LogonSession ls = WebLoginManager.getLogonSession(httpRequest);
            ActionContext ctx = IntegratorHelper.getActionContext(httpRequest);

            ChartMeta meta = ChartDataManager.buildChartMeta(ls, ctx, request);

            ChartDataManager.IntegratedChartData data
                    = ChartDataManager.getChartData(ls, ctx, meta);

            JFreeChart chart = getChart(ls, ctx, meta, data);

            // Generates chart image
            ChartRenderingInfo info = new ChartRenderingInfo();
            String fileName = null;

            // Attemps to avoid the error
            // ChartServlet threw exception javax.servlet.ServletException:
            // File '<user tmp dir>/jfreechart-40929.png' does not exist
            int ioCount = 0;
            while(true) {
                try {
                    fileName = ServletUtilities.saveChartAsPNG(chart,
                            meta.getWidth(), meta.getHeight(), info,
                            httpRequest.getSession());
                    // TODO add check on real file existance
                    if(fileName == null) {
                        throw new IllegalStateException(
                                "Can't create a chart image for the meta="
                                        + meta);
                    }

                    break;
                } catch (IOException ioe) {
                    if(ioCount > 1)    // three attempts only
                        throw ioe;

                    ioCount++;
                }
            }

            String url = MessageFormat.format(chartServletPathPattern,
                    new Object[]{
                            httpRequest.getContextPath(),
                            fileName
                    });

            ChartData chartData = createChartData(url, info, data);
            ChartDataItem[] dataItems = (ChartDataItem[]) chartData.getDataItems()
                    .toArray(new ChartDataItem[0]);
            ChartType type = request.getChartType();
            if (type == null)
                type = meta.getType();

            response = new ChartFieldOnDemandData(request.getElementID(),
                    type, url, dataItems);


            logger.INFO("Chart id=" + meta.getID() + ", fileName=" + fileName
                    + " completed in "
                    + (System.currentTimeMillis() - time) / (float) 1000
                    + " sec.");

        } catch (Exception e) {
            IntegratorHelper.reThrowException(e);
        }

        return response;
    }
    public ChartResponseObject buildChart(ChartRequestObject request)
            throws DisplayableException {

        ChartResponseObject response = null;

        try {
            long time = System.currentTimeMillis();

            LogonSession ls = WebLoginManager.getLogonSession(
                    getThreadLocalRequest());
            ChartMeta meta = request.getMeta();

            HttpServletRequest httpRequest = getThreadLocalRequest();
            ActionContext ctx = IntegratorHelper.getActionContext(httpRequest);

            ChartDataManager.IntegratedChartData data
                    = ChartDataManager.getChartData(ls, ctx, meta);

            JFreeChart chart = getChart(ls, ctx, meta, data);

            // Generates chart image
            ChartRenderingInfo info = new ChartRenderingInfo();
            String fileName = null;

            // Attemps to avoid the error
            // ChartServlet threw exception javax.servlet.ServletException:
            // File '<user tmp dir>/jfreechart-40929.png' does not exist
            int ioCount = 0;
            while(true) {
                try {
                    fileName = ServletUtilities.saveChartAsPNG(
                            chart, meta.getWidth(), meta.getHeight(), info,
                            httpRequest.getSession());
                    // TODO add check on real file existance
                    if(fileName == null) {
                        throw new IllegalStateException(
                                "Can't create a chart image for the meta="
                                        + meta);
                    }

                    break;
                } catch (IOException ioe) {
                    if(ioCount > 1)    // three attempts only
                        throw ioe;

                    ioCount++;
                }
            }

            String url = MessageFormat.format(CHART_SERVLET_PATH_PATTERN,
                    new Object[]{
                            httpRequest.getContextPath(),
                            fileName
                    });

            response = new ChartResponseObject(createChartData(url, info,
                    data));

            logger.INFO("Chart id=" + meta.getID() + ", fileName=" + fileName
                    + " completed in "
                    + (System.currentTimeMillis() - time) / (float) 1000
                    + " sec.");

        } catch (Exception e) {
            IntegratorHelper.reThrowException(e);
        }

        return response;
    }

    private static ChartData createChartData(
            String url, ChartRenderingInfo info,
            ChartDataManager.IntegratedChartData data) {

        // Exclude first entity from processing - first entity represents chart title
        EntityCollection entities = info.getEntityCollection();
        List<ChartDataItem> dataItems = new ArrayList<ChartDataItem>();
        for(int i = 1; i < entities.getEntityCount(); i++) {
            ChartEntity entity = entities.getEntity(i);

            int dataIdx = -1;

            if (entity instanceof CategoryItemEntity) {
                dataIdx = ((CategoryItemEntity) entity).getCategoryIndex();
            }

            if (entity instanceof PieSectionEntity) {
                dataIdx = ((PieSectionEntity) entity).getSectionIndex();
            }

            if (dataIdx == -1)
                continue;

            dataItems.add(new ChartDataItem(entity.getShapeType(),
                    entity.getShapeCoords(), entity.getToolTipText(),
                    data.getFieldData(dataIdx)));
        }

        return new ChartData(url, dataItems);
    }

    private static Paint getBackgroundPaint(ChartMeta meta) {
        if(CHART_BACKGROUND_PAINT == null) {
            CHART_BACKGROUND_PAINT = new GradientPaint(
                    0, 0, Color.WHITE, 0, meta.getHeight(), Color.decode(
                    "0xEAEAEA"));
        }
        return CHART_BACKGROUND_PAINT;
    }

    private static JFreeChart getChart(LogonSession ls, ActionContext ctx,
                                ChartMeta meta,
                                ChartDataManager.IntegratedChartData chartData)
            throws EQLException, UnsupportedChartException,
            UnsupportedChartTypeException {

        JFreeChart ch = null;

        ChartDetails details = ChartDataManager.getChartDetails(ls, ctx,
                meta.getID());

        ChartType type = meta.getType();
        if(ChartType.BAR.equals(type)) {
            ch = getBarChart(ls, ctx, details, chartData.getCategoryDataset());
        } else if(ChartType.PIE.equals(type)) {
            ch = getPieChart(ls, ctx, details, chartData.getCategoryDataset());
        } else {
            throw new UnsupportedChartTypeException(type);
        }

        // Sets up title font
        ch.getTitle().setFont(TITLE_FONT);
        // Sets up background
        ch.setBackgroundPaint(getBackgroundPaint(meta));

        return ch;
    }

    private static DrawingSupplier getDrawingSupplier(ChartDetails details) {
        String colors = details.getColors();
        if(StringHelper.isEmpty(colors)) {
            return new DefaultDrawingSupplier();
        }

        List<Paint> paints = new ArrayList<Paint>();
        for(String color : StringHelper.split(colors, ",")) {
            paints.add(Color.decode(color.trim()));
        }
        return new QDrawingSupplier(paints.toArray(new Paint[0]));
    }

    private final static String CATEGORY_TOOLTIP_FORMAT_STRING
            = "{1} = {2} ({3})";
    private static final String PIE_TOOLTIP_FORMAT_STRING = "{0} = {1} ({2})";

    private static PlotOrientation getOrientation(ChartDetails details) {
        return ChartOrientation.HORIZONTAL.equals(details.getOrientation())
                ? PlotOrientation.HORIZONTAL:PlotOrientation.VERTICAL;
    }

    private static JFreeChart getBarChart(LogonSession ls, ActionContext ctx,
                                   ChartDetails details,
                                   CategoryDataset dataSet)
            throws EQLException, UnsupportedChartException {

        JFreeChart barChart = ChartFactory.createBarChart3D(
                details.getTitle(),
                null,   // Category axis label
                null,   // Value axis label
                dataSet, // Chart's data
                getOrientation(details),
                false,  // Legend visibility
                true,   // Tooltip visibility
                false    // Drilldown url
        );

        CategoryPlot plot = barChart.getCategoryPlot();

        QBarRenderer3D renderer = new QBarRenderer3D();
        renderer.setBaseToolTipGenerator(
                new StandardCategoryToolTipGenerator(
                        CATEGORY_TOOLTIP_FORMAT_STRING,
                        NumberFormat.getInstance()));
        plot.setRenderer(renderer);

        plot.setDrawingSupplier(getDrawingSupplier(details));

        // Prevents library reduce space for the category label
        CategoryAxis axes = plot.getDomainAxis();
        axes.setMaximumCategoryLabelWidthRatio(1.0f);

        return barChart;
    }

    private static JFreeChart getPieChart(LogonSession ls, ActionContext ctx,
                                   ChartDetails details,
                                   CategoryDataset dataSet)
            throws EQLException, UnsupportedChartException {

        JFreeChart pieChart = ChartFactory.createPieChart3D(
                details.getTitle(),
                new CategoryToPieDataset(dataSet, TableOrder.BY_ROW, 0),
                false,  // Legend visibility
                true,   // Tooltip visibility
                false    // Drilldown url
        );
        PiePlot3D plot = (PiePlot3D) pieChart.getPlot();
        plot.setInsets(new RectangleInsets(5, 10, 10, 10));
        plot.setToolTipGenerator(new StandardPieToolTipGenerator(
                PIE_TOOLTIP_FORMAT_STRING));
        plot.setDrawingSupplier(getDrawingSupplier(details));

        return pieChart;
    }

}
