// $Id: QedeqPane.java,v 1.7 2004/09/21 22:40:18 m31 Exp $
//
// This file is part of the program suite "Principia Mathematica II"
// which is a working prototype for the main project:
// "Hilbert II" - http://www.qedeq.org
//
// Copyright 2001-2004  Michael Meyling <mime@qedeq.org>.
//
// "Principia Mathematica II" 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 2 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.

package com.meyling.principia.gui.jtabbedpane;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Point;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.plaf.metal.MetalLookAndFeel;
import javax.swing.text.BadLocationException;

import java.io.File;
import java.io.IOException;

import javax.swing.*;

import com.meyling.principia.io.Utility;

import com.meyling.principia.io.ParsingException;
import com.meyling.principia.log.Trace;
import com.meyling.principia.module.*;

/**
 * View for {@link ModuleProperties}.
 *
 * @author      Michael Meyling
 */

public class QedeqPane extends JPanel {

    private ModuleProperties prop;

    private JTextArea qedeq = new JTextArea();

    private JTextArea error = new JTextArea();

    private JScrollPane qedeqScroller = new JScrollPane();

    private JSplitPane splitPane = new JSplitPane(JSplitPane
        .VERTICAL_SPLIT);


    /**
     * Creates new Panel.
     */
    public QedeqPane(final ModuleProperties prop) {
        super(false);
        this.prop = prop;
        setupView();
        updateView();
    }


    /**
     * Assembles the gui components of the panel.
     */
    private final void setupView() {
        qedeq.setDragEnabled(true);
        qedeq.setFont(new Font("monospaced", Font.PLAIN, 12));
        qedeq.setAutoscrolls(true);
        qedeq.setCaretPosition(0);
        qedeq.setEditable(false);
        qedeq.getCaret().setVisible(false);
        qedeq.setLineWrap(true);
        qedeq.setWrapStyleWord(true);
        qedeq.setFocusable(true);

        error.setFont(new Font("monospaced", Font.PLAIN, 12));
        error.setAutoscrolls(true);
        error.setCaretPosition(0);
        error.setEditable(false);
        error.getCaret().setVisible(false);
        error.setFocusable(true);
        error.setForeground(Color.RED);
        error.addMouseListener( new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                updateView();
            }

        });

        final JViewport port = this.qedeqScroller.getViewport();
        port.add(qedeq);

        final JScrollPane errorScroller = new JScrollPane();
        final JViewport port2 = errorScroller.getViewport();
        port2.add(error);

        this.setLayout(new BorderLayout(1, 1));

        splitPane.setTopComponent(qedeqScroller);
        splitPane.setBottomComponent(errorScroller);
        splitPane.setResizeWeight(1);
        splitPane.setOneTouchExpandable(true);

        this.add(splitPane);
        splitPane.setDividerLocation(this.getHeight());

        this.addComponentListener(new ComponentAdapter() {
            public void componentHidden(ComponentEvent e) {
                Trace.trace(this, "componentHidden", e);
            }
            public void componentShown(ComponentEvent e) {
                Trace.trace(this, "componentHidden", e);
            }
        });

    }


    /**
     * Set new model. To make the new model visible {@link #updateView} must be called.
     *
     * @param   prop
     */
    final public void setModel(final ModuleProperties prop) {
        Trace.trace(this, "setModel", prop);
        this.prop = prop;
    }


    final public ModuleProperties getModel() {
        return this.prop;
    }


    final public void setLineWrap(final boolean wrap) {
        qedeq.setLineWrap(wrap);
    }

    final public boolean getLineWrap() {
        return qedeq.getLineWrap();
    }


    /**
     * Update from model.
     */
    final public synchronized void updateView() {
        Trace.traceBegin(this, "updateView");
        if (this.prop != null) {
            try {
                final File file = new File(this.prop.getModuleAddress().getLocalName());
                if (file.canRead()) {
                    final StringBuffer buffer = new StringBuffer();
                        Utility.loadFile(file, buffer);
                    this.qedeqScroller.getViewport().setViewPosition(new Point(0, 0));
                    qedeq.setText(buffer.toString());
                    if (prop.getState().isFailure()
                            && prop.getException() instanceof ParsingException) {
                        final ParsingException pe = (ParsingException) prop.getException();
                        if (this.prop.getModuleAddress().isFileAddress()) {
                            qedeq.setEditable(true);
                        } else {
                            qedeq.setEditable(false);
                        }
                        qedeq.setCaretPosition(0);
                        qedeq.getCaret().setSelectionVisible(true);
                        qedeq.setSelectedTextColor(Color.RED);
                        qedeq.setSelectionColor(Color.YELLOW);
                        highlightLine(pe.getPosition());
                        // reserve 3 text lines for error description
                        splitPane.setDividerLocation(splitPane.getHeight()
                            - splitPane.getDividerSize() -
                            error.getFontMetrics(error.getFont()).getHeight() * 3 - 4);
                        final StringBuffer pointer = Utility.getSpaces(pe.getColumn() - 1);
                        pointer.append("^");
                        error.setText(pe.getDescription() + "\n"
                                + pe.getLine() + "\n"
                                + pointer);
                    } else {    // TODO if file module is edited status must change
                        if (this.prop.getModuleAddress().isFileAddress()) {
                            qedeq.setEditable(true);
                        } else {
                            qedeq.setEditable(false);
                        }
                        qedeq.getCaret().setSelectionVisible(false);
                        qedeq.setCaretPosition(0);
                        // TODO is this a valid setting for the default colors?
                        qedeq.setSelectedTextColor(MetalLookAndFeel.getHighlightedTextColor());
                        qedeq.setSelectionColor(MetalLookAndFeel.getTextHighlightColor());
                        error.setText("");
                        splitPane.setDividerLocation(this.getHeight());
                    }
                    Trace.trace(this, "updateView", "Text updated");
                } else {
                    throw new IOException("File " + file.getCanonicalPath() + " not readable!");
                }
            } catch (IOException ioException) {
                qedeq.setEditable(false);
                qedeq.setText("");
                error.setText("");
                splitPane.setDividerLocation(this.getHeight());
                Trace.trace(this, "updateView", ioException);
            }
        } else {
            qedeq.setEditable(false);
            qedeq.setText("");
            error.setText("");
            splitPane.setDividerLocation(this.getHeight());
            Trace.traceEnd(this, "updateView");
        }
        this.repaint();
    }


    /**
     * Get qedeq text, if qedeq text is editable.
     * 
     * @return  qedeq text
     * @throws  IllegalStateException   qedeq text is not editable
     */
    public final String getEditedQedeq() {
        if (qedeq.isEditable()) {
            return this.qedeq.getText();
        } else {
            throw new IllegalStateException("no editable qedeq text");
        }
    }


    /**
     * Was the content changed?
     * 
     * @return  content modified?
     */
    public final boolean isContentChanged() {
        return !this.qedeq.isEditable();
    }


// not used any longer
/*
    private final int findCaretPosition(final int i) {
        if (i == 1) {
            return 0;
        }
        final String s = qedeq.getText();
        int j = 0;
        int k = 0;
        for (; j < s.length(); j++) {
            if (s.charAt(j) == '\n') {
                k++;
            }
            if (k == i - 1) {
                return j + 1;
            }
        }
        return 0;
    }
*/

    private final void highlightLine(final int i) {
        Trace.trace(this, "highlightLine", "position=" + i);
        int line;
        try {
            line = qedeq.getLineOfOffset(i);
        } catch (BadLocationException e) {
            Trace.trace(this, "highlightLine", e);
            line = 0;
        }
        int j;
        try {
            j = qedeq.getLineStartOffset(line);
        } catch (BadLocationException e) {
            Trace.trace(this, "highlightLine", e);
            j = 0;
        }
        int k;
        try {
            k = qedeq.getLineEndOffset(line);
        } catch (BadLocationException e) {
            Trace.trace(this, "highlightLine", e);
            k = qedeq.getText().length();
        }
        Trace.trace(this, "highlightLine", "from " + j + " to " + k);

//// old, but working code:
//        final String s = qedeq.getText();
//        int j = i;
//        for (; j > 0 && s.charAt(j) != '\n'; j--) {
//        }
//        int k = i;
//        for (; k < s.length() && s.charAt(k) != '\n'; k++) {
//        }
//
//        qedeq.setCaretPosition(j);
//        qedeq.moveCaretPosition(k);
    }


}
