/******************************************************************************
 ** $Id: Tabelle.java 743 2015-05-31 00:46:40Z wmh $
 ** Diese Datei ist Bestandteil der Java-Quelltexte des Würfelspiels JaFuffy.
 ** Lauffähig ab Java 6.
 ******************************************************************************
 ** 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 jafuffy.Eigenschaften;
import jafuffy.logik.Auswertung;
import jafuffy.logik.Punkte;
import jafuffy.logik.Tabzeile;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Font;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.NumberFormat;
import java.util.Enumeration;
import java.util.Locale;

import javax.swing.BorderFactory;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;

/** Darstellung der Tabelle für die Punktstände. */
@SuppressWarnings("serial")
class Tabelle extends JTable implements ChangeListener {
    /** Renderer zur Darstellung der Endsumme. */
    private class EndsummeRenderer extends ZwischensummeRenderer {
        /** Konstruktor. */
        EndsummeRenderer() {
            farbe = MITTEL;
            markiert = ZELLENFARBE;
        }

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value,
                boolean isSelected, boolean hasFocus, int row, int col) {
            super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
            setForeground(getForeground().darker());
            return this;
        }
    }

    /** Popup-Listener für jede Zelle. */
    private class PopupListener extends MouseAdapter {
        /** Popup-Menü, für das dieser Listener zuständig ist. */
        private final JPopupMenu popup;
        /** Nummer der Tabellenzeile, über der der Mauszeiger steht. */
        private int row;
        /** Nummer der Tabellenspalte, über der der Mauszeiger steht. */
        private int col;

        /**
         * Konstruktor.
         *
         * @param popup
         *            Popup-Menü, für das dieser Listener zuständig ist.
         */
        PopupListener(JPopupMenu popup) {
            this.popup = popup;
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            int x = e.getX();
            int y = e.getY();
            Point point = new Point(x, y);
            Punkte punkte = (Punkte) ((Tabelle) e.getComponent()).getModel();
            Auswertung auswertung = punkte.auswertung();
            boolean tipps = auswertung.tipps();

            row = rowAtPoint(point);
            col = columnAtPoint(point);

            if (e.isPopupTrigger()) {
                if (punkte.aktiv(col) && punkte.isCellEditable(row, col)) {
                    popupUebernehmen.setEnabled(tipps && punkte.offen(col));
                    int wert = auswertung.wert(Tabzeile.eintrag(row));
                    popupSetzen
                            .setText(wert + (wert != 1 ? " Punkte setzen" : " Punkt setzen"));
                    popup.show(e.getComponent(), x, y);
                }
            }
        }

        /** @return Nummer der Tabellenzeile, über welcher das Kontextmenü aufgerufen wurde. */
        int getRow() {
            return row;
        }
    }

    /** Renderer zur Darstellung der Punktetabelle. */
    private class PunktzahlRenderer extends DefaultTableCellRenderer {
        protected Color farbe = MARKE0;
        protected Color markiert = MARKE1;
        private static final float HUE = 0.125F;
        private static final float SAT = 1F;
        private static final float BRIGHT = 0.95F;
        /** Abstand Zellenseite zu Inhalt. */
        private static final int PUNKTEZELLENSEITENABSTAND = 8;

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value,
                boolean isSelected, boolean hasFocus, int row, int col) {
            Font font = basisfont(this);

            Punkte punkte = (Punkte) getModel();
            Auswertung auswertung = punkte.auswertung();
            Tabzeile zeile = Tabzeile.eintrag(row);
            String text = null;

            if (punkte.aktiv(col)) {
                setBackground(markiert);
                if (punkte.angefangen(col)) {
                    setToolTipText(auswertung.spicker(zeile));
                }
                text = auswertung.text(zeile);
            } else {
                setBackground(farbe);
            }

            boolean tipps = auswertung.tipps();

            // Punktzahleintrag (nur falls Feld schon gesetzt oder Setztipp)
            if (text != null) {
                // Sondertext
                setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
                setHorizontalAlignment(SwingConstants.CENTER);
                setForeground(TEXTFARBE);
            } else if (!punkte.setzbar(row, col)) {
                // schon belegter Eintrag
                if (((Integer) value).intValue() != 0 || zeile.alle()) {
                    // Punkteintrag
                    text = value.toString();
                }
            } else if (punkte.aktiv(col) && table.isCellEditable(row, col)) {
                if (tipps) {
                    if (auswertung.vorgeschlagen(zeile)) {
                        setBorder(BorderFactory.createCompoundBorder(BorderFactory
                                .createLineBorder(
                                        auswertung.bester(zeile) ? ZELLENLINIENFARBE.darker()
                                                : ZELLENLINIENFARBE.brighter(), 2),
                                BorderFactory.createEmptyBorder(0, PUNKTEZELLENSEITENABSTAND,
                                        0, PUNKTEZELLENSEITENABSTAND)));
                    }
                    // Setztipps
                    float wichtung = auswertung.wichtung(zeile);
                    text = NF.format(wichtung);
                    setForeground(Color.getHSBColor(
                            auswertung.bester(zeile) ? 0.6F * HUE : HUE,
                            (0.2F + wichtung * 0.8F) * SAT, BRIGHT));
                } else if (Eigenschaften.GLOBAL.aktiv("Zwischenstand")) {
                    setFont(font.deriveFont(Font.PLAIN));
                    setForeground(Color.YELLOW.darker());
                    // Zwischenstand
                    text = Integer.toString(auswertung.wert(zeile));
                }
            }
            setText(text);

            return this;
        }
    }

    /** Renderer zur Darstellung des Spaltenkopfes. */
    private class SpaltenkopfRenderer extends DefaultTableCellRenderer {
        /** Abstand Zellendeckel zu Inhalt. */
        private static final int SPALTENDECKELABSTAND = 1;

        /** Konstruktor. */
        SpaltenkopfRenderer() {
            setHorizontalAlignment(CENTER);
            setBorder(BorderFactory.createCompoundBorder(BorderFactory
                    .createRaisedBevelBorder(), BorderFactory.createEmptyBorder(
                    SPALTENDECKELABSTAND, 0, SPALTENDECKELABSTAND, 0)));
        }

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value,
                boolean isSelected, boolean hasFocus, int row, int col) {
            setText(value.toString());
            return this;
        }
    }

    /** Renderer zur Darstellung der Zwischensumme. */
    private class ZwischensummeRenderer extends PunktzahlRenderer {
        /** Konstruktor. */
        ZwischensummeRenderer() {
            farbe = HELL;
            markiert = MARKE2;
        }

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value,
                boolean isSelected, boolean hasFocus, int row, int col) {
            super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
            setForeground(getForeground().darker());
            return this;
        }
    }

    /** Bevorzugte Spaltenbreite. */
    private static final int SPALTENBREITE_VORZUG = 80;
    /** Maximale Spaltenbreite. */
    private static final int SPALTENBREITE_MAX = Integer.MAX_VALUE;
    /** Luft beim Ermitteln der minimalen Spaltenbreite. */
    private static final int TABELLENKOPF_INSET = 5;
    /** Abstand Zellenseite zu Inhalt. */
    private static final int ZELLENSEITENABSTAND = 10;

    private static final Color HELL = new Color(208, 224, 224);
    private static final Color MITTEL = new Color(176, 192, 192);
    private static final Color MARKE0 = new Color(252, 254, 254);
    private static final Color MARKE1 = new Color(232, 248, 240);
    private static final Color MARKE2 = new Color(192, 208, 208);
    private static final Color ZELLENFARBE = new Color(160, 176, 176);
    private static final Color ZELLENLINIENFARBE = ZELLENFARBE.darker();
    private static final Color TEXTFARBE = Color.PINK;
    private static final Color PUNKTFARBE = new Color(80, 96, 128);
    private static final NumberFormat NF;
    static {
        NF = NumberFormat.getPercentInstance(Locale.GERMAN);
    }
    private static final ToolTipManager TTM = ToolTipManager.sharedInstance();
    private static final int TTM_INIT_DELAY = TTM.getInitialDelay();
    private static final int INIT_DELAY = 250;
    private static final int TTM_DISMISS_DELAY = TTM.getDismissDelay();
    private static final int DISMISS_DELAY = 1500;

    private final PunktzahlRenderer punktzahlRenderer = new PunktzahlRenderer();
    private final ZwischensummeRenderer zwischensummeRenderer = new ZwischensummeRenderer();
    private final EndsummeRenderer endsummeRenderer = new EndsummeRenderer();
    private final JMenuItem popupUebernehmen;
    private final JMenuItem popupSetzen;
    private final JPopupMenu popupMenue;
    private final PopupListener popupListener;
    /** Die aktuelle bevorzugte Breite der Tabelle. */
    private int breite;

    /**
     * Konstruktor.
     *
     * @param punkte
     *            Tabellenmodell.
     * @param auswahl
     *            Auswahl.
     */
    Tabelle(Punkte punkte, Auswahl auswahl) {
        super(punkte);

        konfiguriereTabellenkopf();
        konfiguriereHinweiszeiten();

        popupUebernehmen = erzeugeUebernehmenMenueEintrag();
        popupSetzen = erzeugeSetzenMenueEintrag();
        popupMenue = erzeugeMenue();
        popupListener = new PopupListener(popupMenue);

        addMouseListener(popupListener);
        setColumnSelectionAllowed(false);
        setRowSelectionAllowed(false);
        setBorder(BorderFactory.createLineBorder(ZELLENLINIENFARBE));
        setFillsViewportHeight(false);
        putClientProperty("JTable.autoStartsEdit", Boolean.FALSE);
        setInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
        setDefaultEditor(Object.class, auswahl);
    }

    @Override
    public TableCellRenderer getCellRenderer(int row, int col) {
        switch (Tabzeile.eintrag(row)) {
        case GESAMT:
        case OSUMME:
        case USUMME: // Zwischensummen
            return zwischensummeRenderer;
        case ESUMME: // Endsumme
            return endsummeRenderer;
        default: // normale Einträge
            return punktzahlRenderer;
        }
    }

    @Override
    public void stateChanged(ChangeEvent ce) {
        Punkte punkte = (Punkte) getModel();
        if (punkte.auswertung().tipps()) {
            punkte.aktiv();
        }
    }

    @Override
    public void updateUI() {
        super.updateUI();
        setGridColor(ZELLENLINIENFARBE);
        setShowGrid(true);
        if (popupMenue != null) {
            SwingUtilities.updateComponentTreeUI(popupMenue);
        }
        ermittleBreite(minSpaltenBreite());
    }

    /**
     * Ermittelt minimale Basisspaltenbreite.
     *
     * @param text
     *            Text für minimale Spaltenbreite.
     * @param zeichner
     *            Zeichner für Zellendarstellung.
     * @return Minimale Basisbreite.
     */
    private int basisbreite(String text, Container zeichner) {
        Insets insets = zeichner.getInsets();
        return zeichner.getFontMetrics(zeichner.getFont()).stringWidth(text) + insets.left
                + insets.right + 2 * getIntercellSpacing().width + TABELLENKOPF_INSET;
    }

    /**
     * Dekoriert Zeichner und liefert Basisfont.
     *
     * @param zeichner
     *            Zeichner, mit dem Zellen dargestellt werden.
     * @return Basisfont
     */
    private Font basisfont(DefaultTableCellRenderer zeichner) {
        zeichner.setOpaque(true);
        zeichner.setHorizontalAlignment(SwingConstants.RIGHT);
        zeichner.setBorder(BorderFactory.createEmptyBorder(0, ZELLENSEITENABSTAND, 0,
                ZELLENSEITENABSTAND));
        zeichner.setToolTipText(null);
        zeichner.setForeground(PUNKTFARBE);
        Font font = zeichner.getFont().deriveFont(Font.BOLD);
        zeichner.setFont(font);
        return font;
    }

    /** @return Kontextmenü für die Tabelle. */
    private JPopupMenu erzeugeMenue() {
        JPopupMenu menue = new JPopupMenu();
        menue.add(popupUebernehmen);
        menue.addSeparator();
        menue.add(popupSetzen);
        return menue;
    }

    /**
     * @return Kontextmenüeintrag.
     */
    private JMenuItem erzeugeSetzenMenueEintrag() {
        JMenuItem menueeintrag = new JMenuItem();
        menueeintrag.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                int row = popupListener.getRow();
                Tabelle tabelle = Tabelle.this;
                Auswahl auswahl;
                auswahl = (Auswahl) tabelle.getDefaultEditor(Auswahl.class);
                auswahl.aktiviere(Tabzeile.eintrag(row));
                auswahl.doClick();
            }
        });
        return menueeintrag;
    }

    /**
     * @return Kontextmenüeintrag.
     */
    private JMenuItem erzeugeUebernehmenMenueEintrag() {
        JMenuItem menueeintrag = new JMenuItem("Vorschlag übernehmen");
        menueeintrag.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                ((Punkte) getModel()).auswertung().vorschlagen(
                        Tabzeile.eintrag(popupListener.getRow()));
            }
        });
        menueeintrag.setToolTipText("<html>"
                + "<p>Würfelauswahl dem Vorschlag entsprechend vornehmen.</p>"
                + "<p>Umrahmte Einträge haben gleichen Auswahlvorschlag.</p>" + "</html>");
        return menueeintrag;
    }

    /** Konfiguriert die Anzeigezeigzeiten für die Hilfetext (Tool-Tip-Texte). */
    private void konfiguriereHinweiszeiten() {
        addMouseListener(new MouseAdapter() {
            private boolean enabled;

            @Override
            public void mouseEntered(MouseEvent event) {
                enabled = ToolTipManager.sharedInstance().isEnabled();
                TTM.setInitialDelay(INIT_DELAY);
                TTM.setDismissDelay(DISMISS_DELAY);
                ToolTipManager.sharedInstance().setEnabled(true);
            }

            @Override
            public void mouseExited(MouseEvent event) {
                TTM.setInitialDelay(TTM_INIT_DELAY);
                TTM.setDismissDelay(TTM_DISMISS_DELAY);
                ToolTipManager.sharedInstance().setEnabled(enabled);
            }
        });
    }

    /** Konfiguriert den Tabellenkopf. */
    private void konfiguriereTabellenkopf() {
        JTableHeader tabellenkopf = getTableHeader();
        tabellenkopf.setDefaultRenderer(new SpaltenkopfRenderer());
        tabellenkopf.setReorderingAllowed(false);
        tabellenkopf.setResizingAllowed(false);
    }

    /**
     * Ermittelt minimale Basisspaltenbreite.
     *
     * @return Minimale Basisbreite.
     */
    private int minSpaltenBreite() {
        DefaultTableCellRenderer zeichner = new DefaultTableCellRenderer();
        basisfont(zeichner);
        return basisbreite("100%", zeichner);
    }

    /** @return Tabellenbreite. */
    int breite() {
        return breite;
    }

    /**
     * Ermittelt die Tabellenbreite.
     *
     * @param min
     *            Minimale Spaltenbreite.
     */
    void ermittleBreite(int min) {
        int vorzugsbreite = 0;
        for (Enumeration<TableColumn> spalten = getColumnModel().getColumns(); spalten
                .hasMoreElements();) {
            TableColumn spalte = spalten.nextElement();
            spalte.setMinWidth(min);
            spalte.setMaxWidth(SPALTENBREITE_MAX);
            String spaltentitel = spalte.getHeaderValue().toString();
            int ind = spalte.getModelIndex();
            Container zeichner =
                    (Container) getTableHeader().getDefaultRenderer()
                            .getTableCellRendererComponent(this, spaltentitel, false, false, 0,
                                    ind);
            int basisbreite = basisbreite(spaltentitel, zeichner);
            if (basisbreite > vorzugsbreite) {
                vorzugsbreite = basisbreite;
            }
        }
        if (vorzugsbreite < min) {
            vorzugsbreite = min;
        }
        if (vorzugsbreite > SPALTENBREITE_VORZUG) {
            vorzugsbreite = SPALTENBREITE_VORZUG;
        }
        breite = getModel().getColumnCount() * vorzugsbreite;
    }

}
