/******************************************************************************
 ** $Id: Zwischenstand.java 1628 2019-01-04 00:09:14Z 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.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.Rectangle2D;

import javax.swing.JPanel;

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

/** Balkendiagramm zur Anzeige aller erwürfelten Punkte, welches nach jeder Runde aktualisiert werden kann. */
@SuppressWarnings("serial")
class Zwischenstand extends JPanel {

    /** Der Standardhilfetext im Balkendiagramm, falls kein Balken überfahren wird. */
    private static final String STANDARDHILFE = "Alle bisher im Turnier erspielten Punkte der einzelnen Spieler";
    /** Gesamtbreite des Balkendiagramms. */
    private static final int BREITE = 400;
    /** Dicke der Balken. */
    private static final int BALKENDICKE = 15;
    /** Stärke des räumlichen Effektes bei der Darstellung der Balken. */
    private static final int RAUMEFFEKT = 2;
    /** Lücke zwischen den Balken. */
    private static final int LUECKE = 6 + RAUMEFFEKT;
    /** Schriftgröße der Balkenbeschriftung. */
    private static final int SCHRIFTGROESSE = 12;
    /** Font für die Schriftgröße. */
    private static final Font FONT = new Font("Dialog", Font.BOLD, SCHRIFTGROESSE);

    /** Erwartete Punktzahl eines Spieler für das ganze Turnier zur relativen Darstellung. */
    private int ausschnitt;
    /**
     * Zwischenspeicher für die Stände der einzelnen Spieler, kann nach jeder Runde auf die tatsächliche Zwischenstände
     * gebracht werden.
     */
    private int[] staende;
    /** Die Namen der Spieler. */
    private String[] namen;
    /** Die Rechtecke, in denen sich die sichtbaren Vorderseiten der Balken befinden. */
    private Rectangle2D[] balkenrechtecke;
    /** Die Rechtecke, in denen sich die Beschriftungen befinden. */
    private Rectangle2D[] beschriftungsrechtecke;

    /** Konstruktor. */
    Zwischenstand() {
        setBackground(Color.WHITE);
        setToolTipText(STANDARDHILFE);
        addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent event) {
                verteile();
                repaint();
            }
        });
        addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseMoved(MouseEvent event) {
                beschreibe(event);
            }
        });
        addMouseListener(new Hinweissteuerung());
    }

    @Override
    public Dimension getPreferredSize() {
        if (staende != null) {
            // Die tatsächliche Größe für das laufende Turnier.
            return new Dimension(BREITE, staende.length * (BALKENDICKE + LUECKE) + LUECKE);
        } else {
            // Vorhalt für die maximal mögliche Größe, welche bei Konstruktion bestimmt wird.
            return new Dimension(BREITE, Spieler.SPIELER * (BALKENDICKE + LUECKE) + LUECKE);
        }
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (balkenrechtecke != null) {
            g.setFont(FONT);
            ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            final int n = staende.length;
            for (int i = 0; i < n; i++) {
                final int l = (int) balkenrechtecke[i].getWidth();
                final int b = (int) balkenrechtecke[i].getHeight();
                final int x = (int) balkenrechtecke[i].getX();
                final int y = ((int) balkenrechtecke[i].getY()) - (RAUMEFFEKT - 1);
                zeichne(g, x, y, l, b, Report.BALKENFARBEN[i]);
                final int d = (int) beschriftungsrechtecke[i].getX(); // Distanz
                final int m = (int) beschriftungsrechtecke[i].getY() + (int) beschriftungsrechtecke[i].getHeight() - 1;
                g.drawString(String.valueOf(staende[i]), d, m);
            }
        }
    }

    /**
     * Passt den Ausschnitt an den aktuellen Stand an.
     *
     * @param statistik
     *            Die Statistik, über die im Turnierverlauf Buch geführt wird.
     */
    private void anpasse(Statistik statistik) {
        boolean istAusschnittUeberschritten = false;
        for (int i = 0; i < staende.length; i++) {
            staende[i] = statistik.stand(statistik.teilnehmer().get(i));
            if (staende[i] > ausschnitt) {
                istAusschnittUeberschritten = true;
                ausschnitt = staende[i];
            }
        }
        if (istAusschnittUeberschritten) {
            if (statistik.maximalanzahl() == 0) {
                ausschnitt *= 2;
            } else {
                int anzahl = statistik.rest();
                ausschnitt += statistik.abweichung() + statistik.mittelwert() * anzahl
                        + 2 * statistik.abweichung() * ((int) Math.sqrt(anzahl));
            }
        }
    }

    /**
     * Beschreibt mittels Kontexthilfe die Balken in Abhängigkeit der Mausposition.
     *
     * @param event
     *            Das Mausereignis, welches die Mausposition angibt.
     */
    private void beschreibe(MouseEvent event) {
        setToolTipText(STANDARDHILFE);
        for (int i = 0; i < staende.length; i++) {
            if (balkenrechtecke[i].contains(event.getX(), event.getY())
                    || beschriftungsrechtecke[i].contains(event.getX(), event.getY())) {
                setToolTipText("<html>" + "<p>" + "Alle bisher im Turnier erspielten Punkte von " + "<strong>"
                        + namen[i] + "</strong>" + "</p>" + "</html>");
            }
        }
    }

    /** Verteilt die Balken über das Zeichenfeld und merkt sich die Balkenrechtecke. */
    private void verteile() {
        if (balkenrechtecke != null) {
            final int n = staende.length;
            for (int i = 0; i < n; i++) {
                final FontMetrics fm = getFontMetrics(FONT);
                final int s = fm.stringWidth(String.valueOf(ausschnitt));
                final int w = fm.charWidth(' ');
                final int a = fm.getAscent();
                final int l = (getWidth() - s - 2 * w - RAUMEFFEKT) * staende[i] / ausschnitt;
                final int y = (getHeight() - n * BALKENDICKE) * (i + 1) / (n + 1) + i * BALKENDICKE;
                balkenrechtecke[i].setRect(0, y + RAUMEFFEKT - 1, l, BALKENDICKE);
                beschriftungsrechtecke[i].setRect(l + RAUMEFFEKT + w, y + RAUMEFFEKT - 2 + (BALKENDICKE - a + 1) / 2, s,
                        a);
            }
        }
    }

    /**
     * Zeichnet einen Balken mit angedeutetem räumlichen Effekt.
     *
     * @param g
     *            Das Grafikobjekt, auf dem die Aktion stattfindet.
     * @param x
     *            Linke obere Ecke.
     * @param y
     *            Rechte obere Ecke.
     * @param b
     *            Breite ohne Effekte.
     * @param l
     *            Länge ohne Effekte.
     * @param farbe
     *            Balkenfarbe.
     */
    private void zeichne(Graphics g, int x, int y, int b, int l, Color farbe) {
        g.setColor(farbe);
        for (int i = 0; i < RAUMEFFEKT; i++) {
            g.fill3DRect(x + RAUMEFFEKT - 1 - i, y + i, b, l, true);
        }
    }

    /**
     * Aktualisiert das Balkendiagramm mit den aktuellen Gesamtpunktständen.
     *
     * @param statistik
     *            Die Statistik, über die im Turnierverlauf Buch geführt wird.
     */
    void aktualisiere(Statistik statistik) {
        anpasse(statistik);
        verteile();
    }

    /**
     * Startet die Darstellung von vorn, nämlich für einen Neubeginn eines Turniers.
     *
     * @param statistik
     *            Die Statistik, über die im Turnierverlauf Buch geführt wird.
     */
    void starte(Statistik statistik) {
        final int n = statistik.teilnehmer().size();
        staende = new int[n];
        namen = new String[n];
        balkenrechtecke = new Rectangle2D[n];
        beschriftungsrechtecke = new Rectangle2D[n];
        final int anzahl = Integer.max(1, statistik.maximalanzahl());
        for (int i = 0; i < n; i++) {
            namen[i] = statistik.teilnehmer().get(i).identitaet();
            balkenrechtecke[i] = new Rectangle2D.Double();
            beschriftungsrechtecke[i] = new Rectangle2D.Double();
        }
        // Ausschnitt berechnet sich aus Mittelwert plus doppelter Standardabweichung
        ausschnitt = statistik.mittelwert() * anzahl + 2 * statistik.abweichung() * ((int) Math.sqrt(anzahl));
        aktualisiere(statistik);
        revalidate();
    }

}
