/*
 * @(#)FloatingTextField.java 5.1
 *
 */

package CH.ifa.draw.util;

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Rectangle;
import java.awt.event.KeyListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import javax.swing.BorderFactory;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;


/**
 * A text field overlay that is used to edit a TextFigure.
 * A FloatingTextField requires a two-step initialization:
 * In a first step the overlay is created and in a
 * second step it can be positioned.
 * <br>
 * {@link CH.ifa.draw.figures.TextFigure}
 */
public class FloatingTextField {
    /**
     * Logger for the class FloatingTextField.
     * Only a single logger instance exists at any given time. If a logger for FloatingTextField
     * already exists it is returned or else a new instance is created.
     */
    public static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(FloatingTextField.class);
    private Rectangle _minimum;

    /**
     * The JScrollPane with the JTextArea.
     * Horizontal as well as vertical scrollbars are deactivated.
     * Size of the pane depends on the amount of text to display.
     */
    protected JScrollPane scrollPane;

    /** The JTextArea contained in the JScrollPane. */
    protected JTextArea fEditWidget;
    private Container _container;

    /**
     * Constructs a new FloatingTextField with 1 row and 10 columns expanding
     * if more space to display text is needed.
     */
    public FloatingTextField() {
        fEditWidget = new JTextArea(1, 10);
        // to change the view area when text changed:
        fEditWidget.getDocument().addDocumentListener(new DocumentListener() {
            @Override
            public void insertUpdate(DocumentEvent e) {
                updateOverlay();
            }

            @Override
            public void removeUpdate(DocumentEvent e) {
                updateOverlay();
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                updateOverlay();
            }
        });
        scrollPane = new JScrollPane(
            fEditWidget, ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER,
            ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPane.setBorder(BorderFactory.createLineBorder(Color.BLACK));
        _minimum = null;
    }

    /**
     * Updates the bounds of the JTextArea, when text has changed.
     */
    protected void updateOverlay() {
        // Executing this asynchronously on the AWT event dispatching thread
        // avoids a bug where the edit widget would return a too large
        // preferred size when inserting text with linebreaks because the text
        // was not internally laid out before calculating the size.
        SwingUtilities.invokeLater(() -> {
            Dimension d = fEditWidget.getPreferredSize();

            //set minimum (+5 for scrollPane)
            if (_minimum != null) {
                scrollPane.setBounds(
                    _minimum.x, _minimum.y, (Math.max(d.width, _minimum.width)) + 5,
                    (Math.max(d.height, _minimum.height)) + 5);
            }
            scrollPane.repaint();
            scrollPane.validate();
        });
    }

    /**
     * Creates the overlay for the given Container using a specific font.
     *
     * @param container the container where the overlay will be displayed in
     * @param minimum the minimum size of the overlay, defines the x and y position as well
     * @param font the font used in the overlay
     */
    public void createOverlay(Container container, Rectangle minimum, Font font) {
        this._minimum = minimum;
        _container = container;

        _container.add(scrollPane, 0);
        if (font != null) {
            fEditWidget.setFont(font);
        }

        scrollPane.setLocation(minimum.x, minimum.y);
        scrollPane.repaint();
        scrollPane.validate();
        scrollPane.setVisible(true);
        fEditWidget.selectAll();
        fEditWidget.requestFocus();
        updateOverlay();
    }

    /**
     * Removes the overlay.
     */
    public void endOverlay() {
        _container.requestFocus();
        if (fEditWidget == null) {
            return;
        }
        scrollPane.setVisible(false);
        _container.remove(scrollPane);
    }

    /**
     * Gets the text contents of the overlay.
     *
     * @return the text of the overlay
     */
    public String getText() {
        return fEditWidget.getText();
    }

    /**
     * Sets the text contents of the overlay.
     *
     * @param text the text to be displayed in the overlay
     */
    public void setText(String text) {
        fEditWidget.setText(text);
    }

    /**
     * Places the caret in a specific position.
     *
     * @param line the line the caret will be set in
     * @param column the column the caret will be set in
     */
    public void setCaretPosition(int line, int column) {
        int pos = position(fEditWidget.getText(), line, column);
        fEditWidget.select(pos, pos); // clear selection
        fEditWidget.setCaretPosition(pos);
    }

    /**
     * Selects text in the JTextField.
     *
     * @param startLine line where selection starts
     * @param startColumn column where selection starts
     * @param endLine line where selection ends
     * @param endColumn column where selection ends
     */
    public void select(int startLine, int startColumn, int endLine, int endColumn) {
        int startPos = position(fEditWidget.getText(), startLine, startColumn);
        int endPos = position(fEditWidget.getText(), endLine, endColumn);
        fEditWidget.select(startPos, endPos);
    }

    private static int position(String str, int line, int col) {
        int pos = 0;
        BufferedReader read = new BufferedReader(new StringReader(str));
        while (--line > 0) {
            try {
                String oneLine = read.readLine();
                pos += oneLine.length() + 1;
            } catch (IOException e) {
                LOGGER.error(e.getMessage(), e);
            }
        }
        return pos + col - 1;
    }

    /** Allows to add a key listener to the <code>JTextArea</code> widget.
     *
     * @param keyListener - the key listener to be added to the widget.
     */
    public void addKeyListener(KeyListener keyListener) {
        fEditWidget.addKeyListener(keyListener);
    }
}
