/*
 *  ====================================================================
 *    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.poi.xslf.usermodel;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.util.List;

import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xddf.usermodel.chart.AxisCrossBetween;
import org.apache.poi.xddf.usermodel.chart.AxisCrosses;
import org.apache.poi.xddf.usermodel.chart.AxisOrientation;
import org.apache.poi.xddf.usermodel.chart.AxisPosition;
import org.apache.poi.xddf.usermodel.chart.AxisTickMark;
import org.apache.poi.xddf.usermodel.chart.BarDirection;
import org.apache.poi.xddf.usermodel.chart.BarGrouping;
import org.apache.poi.xddf.usermodel.chart.Grouping;
import org.apache.poi.xddf.usermodel.chart.LayoutMode;
import org.apache.poi.xddf.usermodel.chart.LayoutTarget;
import org.apache.poi.xddf.usermodel.chart.LegendPosition;
import org.apache.poi.xddf.usermodel.chart.RadarStyle;
import org.apache.poi.xddf.usermodel.chart.ScatterStyle;
import org.apache.poi.xddf.usermodel.chart.XDDFBarChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFChartLegend;
import org.apache.poi.xddf.usermodel.chart.XDDFDataSource;
import org.apache.poi.xddf.usermodel.chart.XDDFDataSourcesFactory;
import org.apache.poi.xddf.usermodel.chart.XDDFLineChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFManualLayout;
import org.apache.poi.xddf.usermodel.chart.XDDFNumericalDataSource;
import org.apache.poi.xddf.usermodel.chart.XDDFPieChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFRadarChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFScatterChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFValueAxis;
import org.apache.poi.xslf.XSLFTestDataSamples;
import org.junit.jupiter.api.Test;

/**
 * a modified version from POI-examples
 */
class TestXSLFChart {
    @Test
    void testFillPieChartTemplate() throws IOException {
        XMLSlideShow pptx = XSLFTestDataSamples.openSampleDocument("pie-chart.pptx");
        XSLFChart chart = findChart(pptx.getSlides().get(0));
        List<XDDFChartData> data = findChartData(chart);

        XDDFPieChartData pie = (XDDFPieChartData) data.get(0);
        XDDFPieChartData.Series firstSeries = (XDDFPieChartData.Series) pie.getSeries(0);
        firstSeries.setExplosion(25L);
        assertEquals(Long.valueOf(25), firstSeries.getExplosion());

        fillChartData(chart, pie);
        pptx.close();
    }

    @Test
    void testFillBarChartTemplate() throws IOException {
        XMLSlideShow pptx = XSLFTestDataSamples.openSampleDocument("bar-chart.pptx");
        XSLFSlide slide = pptx.getSlides().get(0);
        // duplicate slide and chart before applying "destructive" tests to it
        XSLFChart chart2 = findChart(pptx.createSlide().importContent(slide));
        XSLFChart chart = findChart(slide);

        List<XDDFChartData> data = findChartData(chart);
        XDDFBarChartData bar = (XDDFBarChartData) data.get(0);
        assertEquals(BarDirection.BAR, bar.getBarDirection());
        assertEquals(BarGrouping.CLUSTERED, bar.getBarGrouping());
        assertEquals(Integer.valueOf(100), bar.getGapWidth());
        fillChartData(chart, bar);

        XDDFBarChartData column = (XDDFBarChartData) findChartData(chart2).get(0);
        column.setBarDirection(BarDirection.COL);
        assertEquals(BarDirection.COL, column.getBarDirection());
        column.getCategoryAxis().setOrientation(AxisOrientation.MIN_MAX);
        column.getValueAxes().get(0).setPosition(AxisPosition.BOTTOM);
        fillChartData(chart2, column);

        pptx.close();
    }

    @Test
    void testFillLineChartTemplate() throws IOException {
        XMLSlideShow pptx = XSLFTestDataSamples.openSampleDocument("line-chart.pptx");
        XSLFChart chart = findChart(pptx.getSlides().get(0));
        List<XDDFChartData> data = findChartData(chart);

        XDDFLineChartData line = (XDDFLineChartData) data.get(0);
        assertEquals(Grouping.STANDARD, line.getGrouping());
        line.setGrouping(Grouping.PERCENT_STACKED);
        assertEquals(Grouping.PERCENT_STACKED, line.getGrouping());

        fillChartData(chart, line);
        pptx.close();
    }

    @Test
    void testFillRadarChartTemplate() throws IOException {
        XMLSlideShow pptx = XSLFTestDataSamples.openSampleDocument("radar-chart.pptx");
        XSLFChart chart = findChart(pptx.getSlides().get(0));
        List<XDDFChartData> data = findChartData(chart);

        XDDFRadarChartData radar = (XDDFRadarChartData) data.get(0);
        assertEquals(RadarStyle.MARKER, radar.getStyle());
        radar.setStyle(RadarStyle.FILLED);
        assertEquals(RadarStyle.FILLED, radar.getStyle());

        fillChartData(chart, radar);
        pptx.close();
    }

    @Test
    void testFillScatterChartTemplate() throws IOException {
        XMLSlideShow pptx = XSLFTestDataSamples.openSampleDocument("scatter-chart.pptx");
        XSLFChart chart = findChart(pptx.getSlides().get(0));
        List<XDDFChartData> data = findChartData(chart);

        XDDFScatterChartData scatter = (XDDFScatterChartData) data.get(0);
        assertEquals(ScatterStyle.LINE_MARKER, scatter.getStyle());
        scatter.setStyle(ScatterStyle.SMOOTH);
        assertEquals(ScatterStyle.SMOOTH, scatter.getStyle());

        fillChartData(chart, scatter);
        pptx.close();
    }

    private void fillChartData(XSLFChart chart, XDDFChartData data) {
        final int numOfPoints = 3;
        final String[] categories = {"First", "Second", "Third"};
        final Integer[] values = {1, 3, 4};

        final String categoryDataRange = chart.formatRange(new CellRangeAddress(1, numOfPoints, 0, 0));
        final String valuesDataRange = chart.formatRange(new CellRangeAddress(1, numOfPoints, 1, 1));

        final XDDFChartData.Series series = data.getSeries(0);
        final XDDFDataSource<?> categoryData = XDDFDataSourcesFactory.fromArray(categories, categoryDataRange);
        final XDDFNumericalDataSource<Integer> valuesData = XDDFDataSourcesFactory.fromArray(values, valuesDataRange);
        series.replaceData(categoryData, valuesData);
        final String title = "Apache POI";
        series.setTitle(title, chart.setSheetTitle(title, 0));
        chart.plot(data);
    }

    private XSLFChart findChart(XSLFSlide slide) {
        // find chart in the slide
        XSLFChart chart = null;
        for(POIXMLDocumentPart part : slide.getRelations()){
            if(part instanceof XSLFChart){
                chart = (XSLFChart) part;
                break;
            }
        }

        if(chart == null) {
            throw new IllegalStateException("chart not found in the template");
        }

        checkLegendOperations(chart);
        return chart;
    }

    private List<XDDFChartData> findChartData(XSLFChart chart) {
        List<XDDFChartData> data = chart.getChartSeries();
        assertNotNull(data);
        assertEquals(1, data.size());

        XDDFChartData firstSeries = data.get(0);
        assertNotNull(firstSeries);
        if (firstSeries instanceof XDDFScatterChartData) {
            assertNull(firstSeries.getCategoryAxis());
            assertEquals(2, firstSeries.getValueAxes().size());
            checkAxisOperations(firstSeries.getValueAxes().get(0));
            checkAxisOperations(firstSeries.getValueAxes().get(1));
        } else if (!(firstSeries instanceof XDDFPieChartData)) {
            assertNotNull(firstSeries.getCategoryAxis());
            assertEquals(1, firstSeries.getValueAxes().size());
            checkAxisOperations(firstSeries.getValueAxes().get(0));
        }

        return data;
    }

    private void checkLegendOperations(XSLFChart chart) {
        XDDFChartLegend legend = chart.getOrAddLegend();
        assertFalse(legend.isOverlay());
        legend.setOverlay(true);
        assertTrue(legend.isOverlay());
        legend.setPosition(LegendPosition.TOP_RIGHT);
        assertEquals(LegendPosition.TOP_RIGHT, legend.getPosition());

        XDDFManualLayout layout = legend.getOrAddManualLayout();
        assertNotNull(layout.getTarget());
        assertNotNull(layout.getXMode());
        assertNotNull(layout.getYMode());
        assertNotNull(layout.getHeightMode());
        assertNotNull(layout.getWidthMode());
        /*
         * According to interface, 0.0 should be returned for
         * uninitialized double properties.
         */
        assertEquals(0.0, layout.getX(), 0.0);
        assertEquals(0.0, layout.getY(), 0.0);
        assertEquals(0.0, layout.getWidthRatio(), 0.0);
        assertEquals(0.0, layout.getHeightRatio(), 0.0);

        final double newRatio = 1.1;
        final double newCoordinate = 0.3;
        final LayoutMode nonDefaultMode = LayoutMode.FACTOR;
        final LayoutTarget nonDefaultTarget = LayoutTarget.OUTER;

        layout.setWidthRatio(newRatio);
        assertEquals(layout.getWidthRatio(), newRatio, 0.0);

        layout.setHeightRatio(newRatio);
        assertEquals(layout.getHeightRatio(), newRatio, 0.0);

        layout.setX(newCoordinate);
        assertEquals(layout.getX(), newCoordinate, 0.0);

        layout.setY(newCoordinate);
        assertEquals(layout.getY(), newCoordinate, 0.0);

        layout.setXMode(nonDefaultMode);
        assertSame(layout.getXMode(), nonDefaultMode);

        layout.setYMode(nonDefaultMode);
        assertSame(layout.getYMode(), nonDefaultMode);

        layout.setWidthMode(nonDefaultMode);
        assertSame(layout.getWidthMode(), nonDefaultMode);

        layout.setHeightMode(nonDefaultMode);
        assertSame(layout.getHeightMode(), nonDefaultMode);

        layout.setTarget(nonDefaultTarget);
        assertSame(layout.getTarget(), nonDefaultTarget);
    }

    private void checkAxisOperations(XDDFValueAxis axis) {
        axis.setCrossBetween(AxisCrossBetween.MIDPOINT_CATEGORY);
        assertEquals(AxisCrossBetween.MIDPOINT_CATEGORY, axis.getCrossBetween());

        axis.setCrosses(AxisCrosses.AUTO_ZERO);
        assertEquals(AxisCrosses.AUTO_ZERO, axis.getCrosses());

        final String numberFormat = "General";
        axis.setNumberFormat(numberFormat);
        assertEquals(numberFormat, axis.getNumberFormat());

        axis.setPosition(AxisPosition.BOTTOM);
        assertEquals(AxisPosition.BOTTOM, axis.getPosition());

        axis.setMajorTickMark(AxisTickMark.NONE);
        assertEquals(AxisTickMark.NONE, axis.getMajorTickMark());

        axis.setMajorTickMark(AxisTickMark.IN);
        assertEquals(AxisTickMark.IN, axis.getMajorTickMark());

        axis.setMajorTickMark(AxisTickMark.OUT);
        assertEquals(AxisTickMark.OUT, axis.getMajorTickMark());

        axis.setMajorTickMark(AxisTickMark.CROSS);
        assertEquals(AxisTickMark.CROSS, axis.getMajorTickMark());

        axis.setMinorTickMark(AxisTickMark.NONE);
        assertEquals(AxisTickMark.NONE, axis.getMinorTickMark());

        axis.setMinorTickMark(AxisTickMark.IN);
        assertEquals(AxisTickMark.IN, axis.getMinorTickMark());

        axis.setMinorTickMark(AxisTickMark.OUT);
        assertEquals(AxisTickMark.OUT, axis.getMinorTickMark());

        axis.setMinorTickMark(AxisTickMark.CROSS);
        assertEquals(AxisTickMark.CROSS, axis.getMinorTickMark());

        axis.setVisible(true);
        assertTrue(axis.isVisible());

        axis.setVisible(false);
        assertFalse(axis.isVisible());

        final double EPSILON = 1E-7;
        axis.setLogBase(Math.E);
        assertTrue(Math.abs(axis.getLogBase() - Math.E) < EPSILON);

        final double newValue = 10.0;

        axis.setMinimum(newValue);
        assertTrue(Math.abs(axis.getMinimum() - newValue) < EPSILON);

        axis.setMaximum(newValue);
        assertTrue(Math.abs(axis.getMaximum() - newValue) < EPSILON);

        IllegalArgumentException iae = null;
        try {
            axis.setLogBase(0.0);
        } catch (IllegalArgumentException e) {
            iae = e;
        }
        assertNotNull(iae);

        iae = null;
        try {
            axis.setLogBase(30000.0);
        } catch (IllegalArgumentException e) {
            iae = e;
        }
        assertNotNull(iae);
    }
}
