/******************************************************************************
 ** $Id: Verlaufsgrafik.java 1640 2019-01-05 10:55:34Z wmh $
 ** Diese Datei ist Bestandteil der Java-Quelltexte des Würfelspiels JaFuffy.
 ******************************************************************************
 ** Copyright (C) Wolfgang Hauck <wolfgang.hauck@3kelvin.de>
 ******************************************************************************
 ** This program is free software: you can redistribute it and/or modify
 ** it under the terms of the GNU General Public License as published by
 ** the Free Software Foundation, either version 3 of the License, or
 ** (at your option) any later version.
 **
 ** This program is distributed in the hope that it will be useful,
 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 ** GNU General Public License for more details.
 **
 ** You should have received a copy of the GNU General Public License
 ** along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************
 ** Die aktuellste Version von JaFuffy findet sich im Internet unter
 ** <http://jafuffy.3kelvin.de>.
 **
 ** Kommentare, Fehler oder Erweiterungswünsche bitte per E-Mail senden an
 ** <jafuffy@3kelvin.de>.
 ******************************************************************************/
package jafuffy.bedienung;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;

import javax.swing.JPanel;

import com.orsoncharts.Chart3D;
import com.orsoncharts.Chart3DFactory;
import com.orsoncharts.Chart3DPanel;
import com.orsoncharts.Range;
import com.orsoncharts.axis.NumberAxis3D;
import com.orsoncharts.axis.StandardCategoryAxis3D;
import com.orsoncharts.data.DefaultKeyedValues;
import com.orsoncharts.data.category.StandardCategoryDataset3D;
import com.orsoncharts.graphics3d.Offset2D;
import com.orsoncharts.graphics3d.ViewPoint3D;
import com.orsoncharts.graphics3d.swing.DisplayPanel3D;
import com.orsoncharts.interaction.Chart3DMouseEvent;
import com.orsoncharts.interaction.Chart3DMouseListener;
import com.orsoncharts.label.StandardCategoryItemLabelGenerator;
import com.orsoncharts.plot.CategoryPlot3D;
import com.orsoncharts.renderer.category.BarRenderer3D;
import com.orsoncharts.renderer.category.StandardCategoryColorSource;
import com.orsoncharts.table.StandardRectanglePainter;

import jafuffy.logik.Spieler;
import jafuffy.logik.Statistik;

/** Anzeige des Turnierverlaufs in Form eines räumlichen Balkendiagramms. */
@SuppressWarnings("serial")
class Verlaufsgrafik extends JPanel {

    /** Bestimmt Balkenlänge in Richtung der Spielreihe. */
    private static final double ZLAENGE = 0.8;
    /** Bestimmt Balkenlänge in Richtung der Turnierabfolge. */
    private static final double XLAENGE = 0.5;
    /** Verschiebung im Fenster. */
    private static final Offset2D VERSCHIEBUNG = new Offset2D(0, -15);
    /** Wertebereich für die darzustellenden Punktzahlen. */
    private static final Range WERTEBEREICH = new Range(0, 350);
    /** Kameraabstand. */
    private static final int RHO = 20;
    /** Kameraperspektive. */
    private static final ViewPoint3D PERSPEKTIVE = ViewPoint3D.createAboveViewPoint(RHO);
    static {
        PERSPEKTIVE.panLeftRight(Math.PI / 12);
        PERSPEKTIVE.moveUpDown(-Math.PI / 30);
        PERSPEKTIVE.roll(Math.PI / 24);
    }
    /** Projektionseigenschaft. */
    private static final int PROJEKTIONSDISTANZ = 2000;
    /** Darstellung der Gitterlinien in den Koordinatenwänden. */
    private static final BasicStroke GITTERLINIENSTIL = new BasicStroke(0.25f);
    /** Farbe der Koordinatenwände. */
    private static final Color WANDFARBE = new Color(176, 192, 192, 64);
    /** Beschriftungsfarbe für Punktzahlen. */
    private static final Color ZAHLSCHRIFTFARBE = Color.GRAY;
    /** Beschriftungsfarbe für Achsenbeschriftung. */
    private static final Color ACHSSCHRIFTFARBE = Color.LIGHT_GRAY;
    /** Beschriftungszeichensatz für Punktzahlen. */
    private static final Font ZAHLSCHRIFTART = new Font(Font.SANS_SERIF, Font.PLAIN, 12);
    /** Punktzahlenzeichensatz für Punktzahlen. */
    private static final Font ACHSSCHRIFTART = new Font(Font.SANS_SERIF, Font.BOLD, 12);
    /** Für Platzhalterberechnung. */
    private static final int P = 250;
    /** Für Platzhalterberechnung. */
    private static final int N = 5;
    /** Für Platzhalterberechnung. */
    private static final int S = 3;

    /** Bereitet Diagramm zu einem leeren Datensatz vor, welcher später ersetzt wird. */
    private static Chart3D chart() {
        Chart3D chart = Chart3DFactory.createBarChart(null, null, platzhalter(), null, "Spielnummer", "Punktzahl");
        chart.setAntiAlias(true);
        chart.setLegendBuilder(null);
        chart.setChartBoxColor(WANDFARBE);
        chart.setProjDistance(PROJEKTIONSDISTANZ);
        chart.setViewPoint(new ViewPoint3D(PERSPEKTIVE.getPoint(), PERSPEKTIVE.calcRollAngle()));
        chart.setTranslate2D(VERSCHIEBUNG);
        chart.setBackground(new StandardRectanglePainter(Report.HINTERGRUNDFARBE));
        return chart;
    }

    /**
     * Liefert einen Datensatz zur Statistik. Der Datensatz besteht aus Datenreihen, die durch den Spieler indiziert
     * sind; eine Datenreihe besteht aus den Punktzahlen für einen Spieler über das bisherige Turnier (Series: Spieler,
     * Row: Spieler, Column: Spielnummer).
     */
    private static StandardCategoryDataset3D<String, Integer, Integer> datensatz(Statistik statistik) {
        StandardCategoryDataset3D</* Series */ String, /* Row */ Integer, /* Col */ Integer> datensatz;
        datensatz = new StandardCategoryDataset3D<>();
        for (Spieler spieler : statistik.teilnehmer()) {
            DefaultKeyedValues</* Spielnummer */ Integer, /* Punktzahl */ Integer> reihe = new DefaultKeyedValues<>();
            for (int i = 0; i < statistik.spielanzahl(); i++) {
                reihe.put(/* Spielnummer */ i + 1, /* Punktzahl */ statistik.verlaeufe().get(spieler.index()).get(i));
            }
            datensatz.addSeriesAsRow(spieler.identitaet(), spieler.index(), reihe);
        }
        return datensatz;
    }

    /** Konfiguriert Koordinatensystem und Darstellung. */
    private static void konfiguriere(CategoryPlot3D plot) {
        plot.getRowAxis().setVisible(false);
        plot.setGridlineStrokeForValues(GITTERLINIENSTIL);
        plot.setToolTipGenerator(new StandardCategoryItemLabelGenerator(
                "<html><strong>%1$s</strong> hat im %3$d. Spiel <strong>%4$.0f Punkte</strong> erzielt.</html>"));
        StandardCategoryAxis3D turnierachse = (StandardCategoryAxis3D) plot.getColumnAxis();
        turnierachse.setLabelFont(ACHSSCHRIFTART);
        turnierachse.setLabelColor(ACHSSCHRIFTFARBE);
        turnierachse.setTickLabelFont(ZAHLSCHRIFTART);
        turnierachse.setTickLabelColor(ZAHLSCHRIFTFARBE);
        NumberAxis3D punktzahlachse = (NumberAxis3D) plot.getValueAxis();
        punktzahlachse.setLabelFont(ACHSSCHRIFTART);
        punktzahlachse.setLabelColor(ACHSSCHRIFTFARBE);
        punktzahlachse.setTickLabelFont(ZAHLSCHRIFTART);
        punktzahlachse.setTickLabelColor(ZAHLSCHRIFTFARBE);
        punktzahlachse.setAutoRangeIncludeZero(true);
        punktzahlachse.setAutoRangeStickyZero(true);
        punktzahlachse.setRange(WERTEBEREICH);
        BarRenderer3D renderer = (BarRenderer3D) plot.getRenderer();
        renderer.setColors(Report.BALKENFARBEN);
        renderer.setTopColorSource(new StandardCategoryColorSource(Color.YELLOW));
        renderer.setBarXWidth(XLAENGE);
        renderer.setBarZWidth(ZLAENGE);
    }

    /** Liefert das Zeichenfeld, wo das Diagramm dargestellt wird. */
    private static Chart3DPanel panel(Chart3D chart) {
        Chart3DPanel panel = new Chart3DPanel(chart);
        panel.addMouseListener(new Hinweissteuerung());
        panel.addChartMouseListener(new Chart3DMouseListener() {

            @Override
            public void chartMouseClicked(Chart3DMouseEvent event) {
                if (event.getTrigger().getClickCount() > 1) {
                    chart.setViewPoint(new ViewPoint3D(PERSPEKTIVE.getPoint(), PERSPEKTIVE.calcRollAngle()));
                    chart.setTranslate2D(VERSCHIEBUNG);
                    panel.zoomToFit();
                }
            }

            @Override
            public void chartMouseMoved(Chart3DMouseEvent event) {
            }
        });
        return panel;
    }

    /** Berechnet initialen Platzhalter für die räumlich Darstellung zwecks Größenabschätzung. */
    private static StandardCategoryDataset3D<String, Integer, Integer> platzhalter() {
        StandardCategoryDataset3D<String, Integer, Integer> platzhalter = new StandardCategoryDataset3D<>();
        DefaultKeyedValues</* Spielnummer */ Integer, /* Punktzahl */ Integer> reihe;
        for (int s = 1; s <= S; s++) {
            reihe = new DefaultKeyedValues<>();
            for (int n = 1; n <= N; n++) {
                reihe.put(n, P);
            }
            platzhalter.addSeriesAsRow("Name " + s + " (Spieler " + s + ")", s, reihe);
        }
        return platzhalter;
    }

    /** Verwaltung aller Diagrammbestandteile. */
    private final Chart3D chart;
    /** Räumliche Darstellung der Werte zu zwei Kategorien (Spieler, Spieler). */
    private final CategoryPlot3D plot;
    /** Das Zeichenfeld zur Swing-Integration. */
    private final Chart3DPanel panel;
    /** Zeichenfeld mit Toolbar und Kontextmenü. */
    private final DisplayPanel3D display;

    /** Datensatz zur räumlichen Darstellung. */
    private StandardCategoryDataset3D<String, Integer, Integer> datensatz;

    /** Konstruktor. */
    Verlaufsgrafik() {
        super(new BorderLayout());
        chart = chart();
        plot = (CategoryPlot3D) chart.getPlot();
        konfiguriere(plot);
        panel = panel(chart);
        display = new DisplayPanel3D(panel, true, true);
        add(display);
    }

    /**
     * Aktualisiert die Darstellung zum Stand der letzten vollendeten Runde.
     * 
     * @param statistik
     *            Die Statistik, welche den Turnierverlauf enthält.
     */
    void aktualisiere(Statistik statistik) {
        for (Spieler spieler : statistik.teilnehmer()) {
            int punktzahl = statistik.verlaeufe().get(spieler.index()).get(statistik.spielanzahl() - 1);
            datensatz.setValue(punktzahl, spieler.identitaet(), spieler.index(), statistik.spielanzahl());
        }
    }

    /**
     * Passt die Darstellung an das Ende eines Spiels im Turnier an.
     * 
     * @param statistik
     *            Die Statistik, welche den Turnierverlauf enthält.
     */
    void beende(Statistik statistik) {
        if (statistik.rest() > 0) {
            for (Spieler spieler : statistik.teilnehmer()) {
                datensatz.setValue(0, spieler.identitaet(), spieler.index(), statistik.spielanzahl());
            }
        }
    }

    /**
     * Startet eine neue Darstellung mit der gegebenen Statistik.
     *
     * @param statistik
     *            Die darzustellende Statistik.
     */
    void starte(Statistik statistik) {
        datensatz = datensatz(statistik);
        plot.setDataset(datensatz);
        panel.zoomToFit();
    }

}
