package de.duehl.swing.ui.text.html;

/*
 * Copyright 2022 Christian Dühl. All rights reserved.
 *
 * This program is free software. You can redistribute it and/or
 * modify it under the same terms as perl:
 *
 * general:  http://dev.perl.org/licenses/
 * GPL:      http://dev.perl.org/licenses/gpl1.html
 * artistic: http://dev.perl.org/licenses/artistic.html
 */

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.net.URL;

import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.JViewport;
import javax.swing.border.EmptyBorder;

import de.duehl.swing.logic.Quitter;
import de.duehl.swing.ui.colors.Colorizer;
import de.duehl.swing.ui.key.BindKeysOnRootPane;
import de.duehl.swing.ui.layout.ExtendedFlowLayout;

/**
 * Diese Klasse stellt ein Panel zur Verfügung, das Html darstellt und fügt Buttons zum Beenden
 * und zum Umschalten zwischen HTML und dem Quellcode hinzu.
 *
 * @version 1.01     2022-02-25
 * @author Christian Dühl
 */

public class HtmlAndSourceCodePanel {

    /** Panel in dem das HTML dargestellt wird. */
    private final HtmlCardPanel htmlCardPanel;

    /** Gesamter Panel. */
    private final JPanel panel;

    /** Button zum Umschalten zwischen der Anzeige des HTMLs und dessen Quellcode. */
    private final JButton toggleHtmlAndSourceCodeButton;

    /**
     * Panel mit den Buttons auf der rechten Seite.
     *
     * Hier kann ein Aufrufer einen weiteren Button hinzufügen.
     */
    private final JPanel rightButtonsPanel;

    /**
     * Panel mit den Buttons auf der linken Seite.
     *
     * Hier kann ein Aufrufer einen weiteren Button hinzufügen.
     */
    private final JPanel leftButtonsPanel;

    /** Konstruktor. */
    private HtmlAndSourceCodePanel() {
        htmlCardPanel = new HtmlCardPanel();

        toggleHtmlAndSourceCodeButton = new JButton();
        initToggleHtmlAndSourceCodeButton();

        rightButtonsPanel = new JPanel();
        leftButtonsPanel = new JPanel();
        initLeftAndRightButtonPanels();

        panel = new JPanel();
        initPanel();
    }

    private void initToggleHtmlAndSourceCodeButton() {
        showRightToggleButtonText();
        toggleHtmlAndSourceCodeButton.addActionListener(e -> toggle());
    }

    /**
     * Schaltet um zwischen der HTML- und der Code-Ansicht.
     *
     * Diese Methode ist public, damit man sie auch aufrufen kann, wenn der
     * toggleHtmlAndSourceCodeButton nicht angezeigt wird (siehe Erstellung mittels create().)
     */
    public void toggle() {
        htmlCardPanel.switchCard();
        showRightToggleButtonText();
    }

    private void showRightToggleButtonText() {
        HtmlPanelCard currentCard = htmlCardPanel.getCurrentCard();
        toggleHtmlAndSourceCodeButton.setText(currentCard.getTextForToggleButton());
    }

    private void initLeftAndRightButtonPanels() {
        leftButtonsPanel.setLayout(new ExtendedFlowLayout(ExtendedFlowLayout.LEFT, 5, 0));
        rightButtonsPanel.setLayout(new ExtendedFlowLayout(ExtendedFlowLayout.RIGHT, 5, 0));
        // Nicht .setLayout(new FlowLayout(FlowLayout.LEFT)); - das macht die anderen Buttons größer
        // ginge aber auch mit ",5 ,0"
    }

    private void initPanel() {
        panel.setLayout(new BorderLayout());

        panel.add(htmlCardPanel.getCardPanel(), BorderLayout.CENTER);
        panel.add(createButtonPart(), BorderLayout.SOUTH);
    }

    private Component createButtonPart() {
        JPanel panel = new JPanel();
        panel.setLayout(new GridLayout(0, 2));

        panel.add(leftButtonsPanel, BorderLayout.WEST);
        panel.add(rightButtonsPanel, BorderLayout.EAST);

        return panel;
    }

    /** Setzt die Farben, falls ein Colorizer übergeben wird. */
    public void setColors(Colorizer colorizer) {
        if (null != colorizer) {
            htmlCardPanel.setColors(colorizer);
            colorizer.setColors(panel);
            colorizer.setColors(rightButtonsPanel);
            colorizer.setColors(leftButtonsPanel);
            // TODO eigentlich fehlen da noch einige andere Komponenten, bisher wird die Methode
            // aber auch nicht genutzt...
        }
    }

    /** Fügt einen Rahmen um die beiden Textteile ein. */
    public void createBorderAroundTextFields(int size) {
        JPanel cardPanel = htmlCardPanel.getCardPanel();
        cardPanel.setBorder(new EmptyBorder(size, size, size, size));
    }

    /** Fügt einen weiteren Button rechts unten zum Dialog hinzu. */
    public void addButtonOnRight(Component button) {
        rightButtonsPanel.add(button);
    }

    /** Fügt einen weiteren Button links unten zum Dialog hinzu, rechts von den bestehenden. */
    public void addButtonOnLeft(Component button) {
        leftButtonsPanel.add(button);
    }

    /** Fügt einen weiteren Button links unten zum Dialog hinzu, links von den bestehenden. */
    public void addButtonAtFrontOnLeft(Component button) {
        leftButtonsPanel.add(button, 0);
    }

    /** Fügt links den Button zum Umschalten zwischen HTML und Quellcode hinzu. */
    public void addToggleButtonToLeft() {
        addButtonOnLeft(toggleHtmlAndSourceCodeButton);
    }

    /** Fügt rechts den Button zum Beenden hinzu. */
    public void addQuitButtonToRight(Quitter quitter) {
        addButtonOnRight(createQuitButton(quitter));
    }

    private Component createQuitButton(Quitter quitter) {
        JButton button = new JButton();
        button.setText("Beenden");
        button.addActionListener(e -> quitter.quit());
        return button;
    }

    /** Getter für den gesamten Panel. */
    public Component getComponent() {
        return panel;
    }

    /** Erstellt Tastenkombinationen. */
    public void setKeyBindings(JRootPane rootPane) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(rootPane);

        //bind.setKeyBindingHome(() -> htmlCardPanel.setScrollPaneToMinimum());
        //bind.setKeyBindingEnd(() -> htmlCardPanel.setScrollPaneToMaximum());
        /* Home und End funktionieren nicht, werden wohl von anderem überschrieben. */

        bind.setKeyBindingCtrlHome(() -> htmlCardPanel.scrollScrollbarToMinimumLater());
        bind.setKeyBindingCtrlEnd(() -> htmlCardPanel.scrollScrollbarToMaximumLater());
        bind.setKeyBindingPageUp(() -> htmlCardPanel.scrollScrollbarToPreviousSectionLater());
        bind.setKeyBindingPageDown(() -> htmlCardPanel.scrollScrollbarToNextSectionLater());
    }

    /** Scrollt zum Anfang. */
    public void scrollScrollbarToMinimumLater() {
        htmlCardPanel.scrollScrollbarToMinimumLater();
    }

    /** Scrollt zum Ende. */
    public void scrollScrollbarToMaximumLater() {
        htmlCardPanel.scrollScrollbarToMaximumLater();
    }

    /** Scrollt weiter nach oben. */
    public void scrollScrollbarToPreviousSectionLater() {
        htmlCardPanel.scrollScrollbarToPreviousSectionLater();
    }

    /** Scrollt weiter nach unten. */
    public void scrollScrollbarToNextSectionLater() {
        htmlCardPanel.scrollScrollbarToNextSectionLater();
    }

    /** Gibt die Länge des Textes zurück. */
    public int getTextLength() {
        return htmlCardPanel.getTextLength();
    }

    /**
     * Schreibt den Text der Seite hinter der URL in den Hauptteil des Dialogs und setzt den
     * Textcursor auf den Anfang des Dokuments.
     *
     * @param url
     *            URL, aus der der Inhalt gelesen wird.
     */
    public void showHtml(URL url) {
        htmlCardPanel.showHtml(url);
    }

    /**
     * Schreibt das HTML in den Anzeigebereich und setzt den Textcursor auf den Anfang des
     * Dokuments.
     *
     * @param html
     *            Darzustellender Text.
     */
    public void setText(String html) {
        htmlCardPanel.setText(html);
    }

    /**
     * Setzt den Text des Editors mit Scrollbalken und scrollt hinterher den Sichtbaren Bereich des
     * Textes zum Anfang des Textes.
     *
     * @param text
     *            Zu setzender Text.
     */
    public void setTextAndScrollToTop(String text) {
        htmlCardPanel.setTextAndScrollToTop(text);
    }

    /** Zeichnet den Editor neu. */
    public void repaint() {
        htmlCardPanel.repaint();
    }

    /**
     * Setzt die bevorzugte Größe des Editors.
     *
     * @param size
     *            Gewünschte Dimension.
     */
    public void setPreferredSize(Dimension size) {
        htmlCardPanel.setPreferredSize(size);
    }

    /**
     * Setzt die bevorzugte Größe des Editors.
     *
     * @param width
     *            Gewünschte Breite.
     * @param height
     *            Gewünschte Höhe.
     */
    public void setPreferredSize(int width, int height) {
        htmlCardPanel.setPreferredSize(width, height);
    }

    /**
     * Turns on or off automatic drag handling. In order to enable automatic drag handling, this
     * property should be set to {@code true}, and the component's {@code TransferHandler} needs to
     * be {@code non-null}. The default value of the {@code dragEnabled} property is {@code false}.
     * <p>
     * The job of honoring this property, and recognizing a user drag gesture, lies with the look
     * and feel implementation, and in particular, the component's {@code TextUI}. When automatic
     * drag handling is enabled, most look and feels (including those that subclass
     * {@code BasicLookAndFeel}) begin a drag and drop operation whenever the user presses the
     * mouse button over a selection and then moves the mouse a few pixels. Setting this property
     * to {@code true} can therefore have a subtle effect on how selections behave.
     * <p>
     * If a look and feel is used that ignores this property, you can still begin a drag and drop
     * operation by calling {@code exportAsDrag} on the component's {@code TransferHandler}.
     *
     * @param enable
     *            whether or not to enable automatic drag handling
     */
    public void setDragEnabled(boolean enable) {
        htmlCardPanel.setDragEnabled(enable);
    }

    /**
     * Setzt die Position der Eingabemarke im Editor.
     *
     * @param position
     *            Zu setzende Position der Eingabemarke (0 bis Länge des Textes).
     */
    public void setCaretPosition(int position) {
        htmlCardPanel.setCaretPosition(position);
    }

    /**
     * Returns the position of the text insertion caret for the text component.
     *
     * @return the position of the text insertion caret for the text component &ge; 0
     */
    public int getCaretPosition() {
        return htmlCardPanel.getCaretPosition();
    }

    /**
     * Returns the <code>Component</code>'s "visible rectangle" - the intersection of this
     * component's visible rectangle, <code>new Rectangle(0, 0, getWidth(), getHeight())</code>,
     * and all of its ancestors' visible rectangles.
     *
     * @return the visible rectangle
     */
    public Rectangle getVisibleRect() {
        return htmlCardPanel.getVisibleRect();
    }

    /**
     * Forwards the <code>scrollRectToVisible()</code> message to the <code>JComponent</code>'s
     * parent. Components that can service the request, such as <code>JViewport</code>, override
     * this method and perform the scrolling.
     *
     * @param rectangle
     *            the visible <code>Rectangle</code>
     * @see JViewport
     */
    public void scrollRectToVisible(Rectangle rectangle) {
        htmlCardPanel.scrollRectToVisible(rectangle);
    }

    /**
     * Returns the selected text's start position. Return 0 for an empty document, or the value of
     * dot if no selection.
     *
     * @return the start position &ge; 0
     */
    public int getSelectionStart() {
        return htmlCardPanel.getSelectionStart();
    }

    /**
     * Returns the selected text's end position. Return 0 if the document is empty, or the value of
     * dot if there is no selection.
     *
     * @return the end position &ge; 0
     */
    public int getSelectionEnd() {
        return htmlCardPanel.getSelectionEnd();
    }

    /**
     * Sets the selection start to the specified position. The new starting point is constrained to
     * be before or at the current selection end. <p>
     *
     * This is available for backward compatibility to code that called this method on
     * <code>java.awt.TextComponent</code>. This is implemented to forward to the
     * <code>Caret</code> implementation which is where the actual selection is maintained.
     *
     * @param start
     *            the start position of the text &ge; 0
     */
    public void setSelectionStart(int start) {
        htmlCardPanel.setSelectionStart(start);
    }

    /**
     * Sets the selection end to the specified position. The new end point is constrained to be at
     * or after the current selection start. <p>
     *
     * This is available for backward compatibility to code that called this method on
     * <code>java.awt.TextComponent</code>. This is implemented to forward to the
     * <code>Caret</code> implementation which is where the actual selection is maintained.
     *
     * @param end
     *            the end position of the text &ge; 0
     */
    public void setSelectionEnd(int end) {
        htmlCardPanel.setSelectionEnd(end);
    }

    /**
     * Gibt den in der text-Komponente selektierten Text zurück.
     *
     * Falls dieser nicht ermittelt werden kann, wird der leere String zurückgegeben.
     */
    public String getSelectedText() {
        return htmlCardPanel.getSelectedText();
    }

    /** Löscht die Selektion. */
    public void clearSelection() {
        htmlCardPanel.clearSelection();
    }

    /** Gibt an, ob das Element den Fokus hat. */
    public boolean hasFocus() {
        return htmlCardPanel.hasFocus();
    }

    /** Setzt die Hintergrundfarben für die Darstellung. */
    public void setBackground(Color backgroundColor) {
        htmlCardPanel.setBackground(backgroundColor);
    }

    /**
     * Lässt die Tastenkombinationen Page-Up und Page-Down an die übergeordnete Komponente
     * weiterreichen.
     */
    public void ignorePageUpAndPageDown() {
        htmlCardPanel.ignorePageUpAndPageDown();
    }

    /**
     * Lässt die Tastenkombinationen Pfeiltaste nach oben und Pfeiltaste nach unten im Textfeld an
     * die übergeordnete Komponente weiterreichen.
     */
    public void ignoreUpAndDown() {
        htmlCardPanel.ignoreUpAndDown();
    }

    /**
     * Lässt die Tastenkombinationen Ctrl-Pos1 und Ctrl-End im Textfeld an
     * die übergeordnete Komponente weiterreichen.
     */
    public void ignoreCtrlPos1AndCtrlEnd() {
        htmlCardPanel.ignoreCtrlPos1AndCtrlEnd();
    }

    /** Zeigt den Cursor an (rot und nicht blinkend). */
    public void showCursor() {
        htmlCardPanel.showCursor();
    }

    /* --- */

    /** Erzeugt ein einen HtmlPanel für HTML mit Umschalter, OK-Button und Scrollbalken. */
    public static HtmlAndSourceCodePanel createWithOkButtonAndToggleButton(Quitter quitter) {
        HtmlAndSourceCodePanel htmlPanel = createWithToggleButton();
        htmlPanel.addQuitButtonToRight(quitter);
        return htmlPanel;
    }

    /** Erzeugt ein einen HtmlPanel für HTML mit Umschalter und Scrollbalken. */
    public static HtmlAndSourceCodePanel createWithToggleButton() {
        HtmlAndSourceCodePanel htmlPanel = create();
        htmlPanel.addToggleButtonToLeft();
        return htmlPanel;
    }

    /** Erzeugt ein einen HtmlPanel für HTML mit Scrollbalken. */
    public static HtmlAndSourceCodePanel create() {
        HtmlAndSourceCodePanel htmlPanel = new HtmlAndSourceCodePanel();
        return htmlPanel;
    }

    /** Speichert die vertikale Position des Textes. */
    public void storeVerticalPosition() {
        htmlCardPanel.storeVerticalPosition();
    }

    /** Stellt die gespeicherte vertikale Position des Textes wieder her. */
    public void restoreVerticalPosition() {
        htmlCardPanel.restoreVerticalPosition();
    }

    /** Gibt den HTML-Text zurück. */
    public String getHtmlText() {
        return htmlCardPanel.getHtmlText();
    }

    /** Vergrößert (oder verkleinert bei negativer Größe) die Schriftgröße des Code-Panels. */
    public void sourceCodePanelBiggerFont(int addSize) {
        htmlCardPanel.sourceCodePanelBiggerFont(addSize);
    }

}
