/**
 *#########################################################################
 *
 * A component of the Gatherer application, part of the Greenstone digital
 * library suite from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * Copyright (C) 1999 New Zealand Digital Library Project
 *
 * 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 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *########################################################################
 */

package org.greenstone.gatherer.gui;

import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.Dictionary;

import org.fife.ui.rsyntaxtextarea.*;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.undo.*;

/**
 * A textarea with the line number next to each line of the text.
 * It provides undo and redo buttons that are already hooked up to listeners
 * You can add these buttons to a JComponent and they will behave just like the 
 * existing RSyntaxArea's Ctrl-Z and Ctrl-Y shortcuts and undo/redo popup menus.
 */
public class NumberedJTextArea extends RSyntaxTextArea /* JTextArea */
    implements UndoableEditListener, ActionListener
{
    
    String id = null;
    
    public final GLIButton undoButton;
    public final GLIButton redoButton;

    public NumberedJTextArea() {
      this("", "", SyntaxConstants.SYNTAX_STYLE_XML);
    }
    
    public NumberedJTextArea(String tooltip) {
      this("", tooltip, SyntaxConstants.SYNTAX_STYLE_XML);
    }
  
  public NumberedJTextArea(String id, String tooltip) {
      this(id, tooltip, SyntaxConstants.SYNTAX_STYLE_XML);
    }
    
  public NumberedJTextArea (String id, String tooltip, String syntax_type) {
	super();
	
	this.id = id;
	
	// maybe use this textarea's id field to customise the undo/redo button tooltips?
	undoButton = new GLIButton(Dictionary.get("General.Undo"), Dictionary.get("General.Undo_Tooltip"));
	undoButton.setEnabled(false);
	
	redoButton = new GLIButton(Dictionary.get("General.Redo"), Dictionary.get("General.Redo_Tooltip"));
	redoButton.setEnabled(false);
	
	undoButton.addActionListener(this);
	redoButton.addActionListener(this);
	
	// next, initialise this RSyntaxTextArea:
	
	// Adding the UndoableEditListener has to come after instantiation of the undo and 
	// redo buttons, since the listener expects these buttons to already exist
	this.getDocument().addUndoableEditListener(this);
	
	/* Fields specific to RSyntaxQuery inherited class */
	setSyntaxEditingStyle(syntax_type);
	setBracketMatchingEnabled(true);
	setAnimateBracketMatching(true);
	setAntiAliasingEnabled(true);
	setAutoIndentEnabled(true);
	setPaintMarkOccurrencesBorder(false);
	
	/* Standard fields to JTextArea */
	setOpaque(false);
	setBackground(Configuration.getColor("coloring.editable_background", false));
	setCaretPosition(0);
	setLineWrap(true);
	setRows(11);
	setWrapStyleWord(false);	
	setToolTipText(tooltip);
    }
    
    public void paintComponent(Graphics g)
    {
	Insets insets = getInsets();
	Rectangle rectangle = g.getClipBounds();
	g.setColor(Color.white);
	g.fillRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
	
	super.paintComponent(g);
	
	if (rectangle.x < insets.left)
	    {
		FontMetrics font_metrics = g.getFontMetrics();
		int font_height = font_metrics.getHeight();
		int y = font_metrics.getAscent() + insets.top;
		int line_number_start_point = ((rectangle.y + insets.top) / font_height) + 1;
		if (y < rectangle.y)
		    {
			y = line_number_start_point * font_height - (font_height - font_metrics.getAscent());
		    }
		int y_axis_end_point = y + rectangle.height + font_height;
		int x_axis_start_point = insets.left;
		x_axis_start_point -= getFontMetrics(getFont()).stringWidth(Math.max(getRows(), getLineCount() + 1) + " ");
		if (!this.getText().trim().equals(""))
		    {
			g.setColor(Color.DARK_GRAY);
		    }
		else
		    {
			g.setColor(Color.white);
		    }
		int length = ("" + Math.max(getRows(), getLineCount() + 1)).length();
		while (y < y_axis_end_point)
		    {
			g.drawString(line_number_start_point + "  ", x_axis_start_point, y);
			y += font_height;
			line_number_start_point++;
		    }
	    }
    }
    
    
    public Insets getInsets()
    {
	Insets insets = super.getInsets(new Insets(0, 0, 0, 0));
	insets.left += getFontMetrics(getFont()).stringWidth(Math.max(getRows(), getLineCount() + 1) + " ");
	return insets;
    }
    
    
    // Overriding, to ensure that even if ctrl-z was pressed to undo something,
    // the undo and redo buttons are in sync with that.
    public void undoLastAction() {
	super.undoLastAction();
	redoButton.setEnabled(true);
	
	if (!this.canUndo()) {
	    undoButton.setEnabled(false);
	} else {
	    undoButton.setEnabled(true);
	}
    }
    
    // Overriding
    public void redoLastAction() {
	super.redoLastAction();
	undoButton.setEnabled(true);
	    
	if (!this.canRedo()) {
	    redoButton.setEnabled(false);
	} else {
	    redoButton.setEnabled(true);
	}
    }

    // Overriding
    public void discardAllEdits() {
	// Buttons have to be disabled before discardAllEdits(). If done in reverse order, buttons get re-enabled
	undoButton.setEnabled(false);
	redoButton.setEnabled(false);
	super.discardAllEdits();
    }

    // The RSyntaxTextarea class already provides undo and redo functionality hooked up to their
    // usual keyboard shortcuts and to rightclick popup menus. The actionListener below merely
    // reuses this behaviour and attaches it to the undoButton and redoButton.
    public void actionPerformed(ActionEvent event)
    {
	// actionPerformed() not defined in any of the superclasses RTextArea, RTextAreaBase, JTextArea
	//super.actionPerformed(event); // compile error
	
	if(event.getSource() == undoButton) {
	    
	    try {
		// calls canUndo() and undoLastAction() defined by RTextArea
		// internally calls its undoManager's canUndo() and undo() to handle compoundEdits
		if (this.canUndo()) {
		    this.undoLastAction();					
		}
		
		if (!this.canUndo()) {
		    undoButton.setEnabled(false);
		} else {
		    undoButton.setEnabled(true);
		}
		
	    } catch (Exception e) {
		System.err.println("Exception trying to undo: " + e.getMessage());
		e.printStackTrace();
	    }
	}
	else if (event.getSource() == redoButton) {
	    try {
		// calls canRedo() and redoLastAction() defined by RTextArea
		// internally calls its undoManager's canRedo() and redo() to handle compoundEdits
		
		if (this.canRedo()) {
		    this.redoLastAction();
		}
		
		if (!this.canRedo()) {
		    redoButton.setEnabled(false);
		} else {
		    redoButton.setEnabled(true);
		}
		
	    } catch (Exception e) {
		System.err.println("Exception trying to redo: " + e.getMessage());
		e.printStackTrace();
	    }
	}
    }

    
    // defined in interface UndoableEditListener
    public void undoableEditHappened(UndoableEditEvent evt)
    {	    
	undoButton.setEnabled(true);
	redoButton.setEnabled(false);	    
    }
}
