/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you under the Apache License, Version 2.0
 * (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.apache.org/licenses/LICENSE-2.0
 *
 * 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 org.apache.jmeter.visualizers;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LayoutManager;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.Stroke;
import java.math.BigDecimal;
import java.math.RoundingMode;

import javax.swing.JPanel;

import org.apache.jmeter.util.JMeterUtils;
import org.jCharts.axisChart.AxisChart;
import org.jCharts.chartData.AxisChartDataSet;
import org.jCharts.chartData.ChartDataException;
import org.jCharts.chartData.DataSeries;
import org.jCharts.properties.AxisProperties;
import org.jCharts.properties.ChartProperties;
import org.jCharts.properties.DataAxisProperties;
import org.jCharts.properties.LabelAxisProperties;
import org.jCharts.properties.LegendAreaProperties;
import org.jCharts.properties.LegendProperties;
import org.jCharts.properties.LineChartProperties;
import org.jCharts.properties.PointChartProperties;
import org.jCharts.properties.PropertyException;
import org.jCharts.properties.util.ChartFont;
import org.jCharts.types.ChartType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RespTimeGraphChart extends JPanel {

    private static final long serialVersionUID = 281L;

    private static final Logger log = LoggerFactory.getLogger(RespTimeGraphChart.class);

    protected double[][] data;

    protected String title;

    protected String xAxisTitle;

    protected String yAxisTitle;

    protected String yAxisLabel;

    protected String[] xAxisLabels;

    protected int width;

    protected int height;

    protected int incrYAxisScale;

    protected String[] legendLabels = { JMeterUtils.getResString("aggregate_graph_legend") }; // $NON-NLS-1$

    protected int maxYAxisScale;

    protected Font titleFont;

    protected Font legendFont;

    protected Color[] color;

    protected boolean showGrouping = true;

    protected int legendPlacement = LegendAreaProperties.BOTTOM;

    protected Shape pointShape = PointChartProperties.SHAPE_CIRCLE;

    protected float strokeWidth = 3.5f;

    /**
    * Constructor
    */
   public RespTimeGraphChart() {
       super();
   }

    /**
     * Constructor
     *
     * @param layout
     *            The {@link LayoutManager} to be used
     */
   public RespTimeGraphChart(LayoutManager layout) {
       super(layout);
   }

    /**
     * Constructor
     *
     * @param layout
     *            The {@link LayoutManager} to be used
     * @param isDoubleBuffered
     *            Flag whether double buffering should be used
     */
   public RespTimeGraphChart(LayoutManager layout, boolean isDoubleBuffered) {
       super(layout, isDoubleBuffered);
   }

    public void setData(double[][] data) {
        this.data = data;
    }

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

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

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

    public void setXAxisLabels(String[] labels) {
        this.xAxisLabels = labels;
    }

    public void setYAxisLabels(String label) {
        this.yAxisLabel = label;
    }

    public void setLegendLabels(String[] labels) {
        this.legendLabels = labels;
    }

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

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

    /**
     * @param incrYAxisScale the incrYAxisScale to set
     */
    public void setIncrYAxisScale(int incrYAxisScale) {
        this.incrYAxisScale = incrYAxisScale;
    }

    /**
     * @return the maxYAxisScale
     */
    public int getMaxYAxisScale() {
        return maxYAxisScale;
    }

    /**
     * @param maxYAxisScale the maxYAxisScale to set
     */
    public void setMaxYAxisScale(int maxYAxisScale) {
        this.maxYAxisScale = maxYAxisScale;
    }

    /**
     * @return the color
     */
    public Color[] getColor() {
        return color;
    }

    /**
     * @param color the color to set
     */
    public void setColor(Color[] color) {
        this.color = color;
    }

    /**
     * @return the titleFont
     */
    public Font getTitleFont() {
        return titleFont;
    }

    /**
     * @param titleFont the titleFont to set
     */
    public void setTitleFont(Font titleFont) {
        this.titleFont = titleFont;
    }

    /**
     * @return the legendFont
     */
    public Font getLegendFont() {
        return legendFont;
    }

    /**
     * @param legendFont the legendFont to set
     */
    public void setLegendFont(Font legendFont) {
        this.legendFont = legendFont;
    }

    /**
     * @return the legendPlacement
     */
    public int getLegendPlacement() {
        return legendPlacement;
    }

    /**
     * @param legendPlacement the legendPlacement to set
     */
    public void setLegendPlacement(int legendPlacement) {
        this.legendPlacement = legendPlacement;
    }

    /**
     * @return the pointShape
     */
    public Shape getPointShape() {
        return pointShape;
    }

    /**
     * @param pointShape the pointShape to set
     */
    public void setPointShape(Shape pointShape) {
        this.pointShape = pointShape;
    }

    /**
     * @return the strokeWidth
     */
    public float getStrokeWidth() {
        return strokeWidth;
    }

    /**
     * @param strokeWidth the strokeWidth to set
     */
    public void setStrokeWidth(float strokeWidth) {
        this.strokeWidth = strokeWidth;
    }

    /**
     * @return the showGrouping
     */
    public boolean isShowGrouping() {
        return showGrouping;
    }

    /**
     * @param showGrouping the showGrouping to set
     */
    public void setShowGrouping(boolean showGrouping) {
        this.showGrouping = showGrouping;
    }

    private void drawSample(String _title, String[] _xAxisLabels,
            String _yAxisTitle, String[] _legendLabels,
            double[][] _data, int _width, int _height, int _incrScaleYAxis,
            Color[] _color, Font legendFont, Graphics g) {

        double max = maxYAxisScale > 0 ? maxYAxisScale : getTopValue(findMax(_data), RoundingMode.UP); // define max scale y axis
        try {
            // if the title graph is empty, we can assume some default
            if (_title.length() == 0 ) {
                _title = JMeterUtils.getResString("graph_resp_time_title"); //$NON-NLS-1$
            }
            this.setPreferredSize(new Dimension(_width,_height));
            DataSeries dataSeries = new DataSeries( _xAxisLabels, null, _yAxisTitle, _title ); // replace _xAxisTitle to null (don't display x axis title)

            // Stroke and shape line settings
            Stroke[] strokes = new Stroke[_legendLabels.length];
            for (int i = 0; i < _legendLabels.length; i++) {
                strokes[i] = new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 5f);
            }
            Shape[] shapes = new Shape[_legendLabels.length];
            for (int i = 0; i < _legendLabels.length; i++) {
                shapes[i] = pointShape;
            }
            LineChartProperties lineChartProperties= new LineChartProperties(strokes, shapes);

            // Lines colors
            Paint[] paints = new Paint[_color.length];
            System.arraycopy(_color, 0, paints, 0, _color.length);

            // Define chart type (line)
            AxisChartDataSet axisChartDataSet =
                new AxisChartDataSet( _data, _legendLabels, paints, ChartType.LINE, lineChartProperties );
            dataSeries.addIAxisPlotDataSet(axisChartDataSet);

            ChartProperties chartProperties= new ChartProperties();
            LabelAxisProperties xaxis = new LabelAxisProperties();
            DataAxisProperties yaxis = new DataAxisProperties();
            yaxis.setUseCommas(showGrouping);

            if (legendFont != null) {
                yaxis.setAxisTitleChartFont(new ChartFont(legendFont, new Color(20)));
                yaxis.setScaleChartFont(new ChartFont(legendFont, new Color(20)));
                xaxis.setAxisTitleChartFont(new ChartFont(legendFont, new Color(20)));
                xaxis.setScaleChartFont(new ChartFont(legendFont, new Color(20)));
            }
            if (titleFont != null) {
                chartProperties.setTitleFont(new ChartFont(titleFont, new Color(0)));
            }

            // Y Axis ruler
            try {
                double numInterval = _height / 50d; // ~a tic every 50 px
                double incrYAxis = max / numInterval;
                double incrTopValue = _incrScaleYAxis;
                if (_incrScaleYAxis == 0) {
                    incrTopValue = getTopValue(incrYAxis, RoundingMode.HALF_UP);
                }
                if (incrTopValue < 1) {
                    incrTopValue = 1.0d; // Increment cannot be < 1
                }
                yaxis.setUserDefinedScale(0, incrTopValue);
                yaxis.setNumItems((int)(max / incrTopValue) + 1);
                yaxis.setShowGridLines(1);
            } catch (PropertyException e) {
                log.warn("Exception while setting Y axis properties.", e);
            }

            AxisProperties axisProperties= new AxisProperties(xaxis, yaxis);
            axisProperties.setXAxisLabelsAreVertical(true);
            LegendProperties legendProperties= new LegendProperties();
            legendProperties.setBorderStroke(null);
            legendProperties.setPlacement(legendPlacement);
            legendProperties.setIconBorderPaint(Color.WHITE);
            legendProperties.setIconBorderStroke(new BasicStroke(0f, BasicStroke.CAP_SQUARE, BasicStroke.CAP_SQUARE));
            // Manage legend placement
            legendProperties.setNumColumns(LegendAreaProperties.COLUMNS_FIT_TO_IMAGE);
            if (legendPlacement == LegendAreaProperties.RIGHT || legendPlacement == LegendAreaProperties.LEFT) {
                legendProperties.setNumColumns(1);
            }
            if (legendFont != null) {
                legendProperties.setFont(legendFont);
            }
            AxisChart axisChart = new AxisChart(
                    dataSeries, chartProperties, axisProperties,
                    legendProperties, _width, _height );
            axisChart.setGraphics2D((Graphics2D) g);
            axisChart.render();
        } catch (ChartDataException | PropertyException e) {
            log.warn("Exception while rendering axis chart.", e);
        }
    }

    private static int getTopValue(double value, RoundingMode roundingMode) {
        String maxStr = String.valueOf(Math.round(value));
        StringBuilder divValueStr = new StringBuilder(maxStr.length()+1);
        divValueStr.append("1");
        for (int i = 1; i < maxStr.length(); i++) {
            divValueStr.append("0"); //$NON-NLS-1$
        }
        int divValueInt = Integer.parseInt(divValueStr.toString());
        BigDecimal round = BigDecimal.valueOf(value / divValueInt);
        round = round.setScale(0, roundingMode);
        int topValue = round.intValue() * divValueInt;
        return topValue;
    }

    @Override
    public void paintComponent(Graphics graphics) {
        if (data != null && this.title != null && this.xAxisLabels != null &&
                this.yAxisLabel != null && this.yAxisTitle != null) {
            drawSample(this.title, this.xAxisLabels,
                    this.yAxisTitle, this.legendLabels,
                    this.data, this.width, this.height, this.incrYAxisScale, this.color,
                    this.legendFont, graphics);
        }
    }

    /**
     * Find max in datas
     * @param datas array of positive or NaN doubles
     * @return double
     */
    private static double findMax(double[][] datas) {
        double max = 0;
        for (double[] data : datas) {
            for (final double value : data) {
                if (!Double.isNaN(value) && (value > max)) {
                    max = value;
                }
            }
        }
        return max;
    }
}
