/*
 *  Draco Econometrics
 *  Copyright (C) 2006-2009 Approximatrix, LLC
 *  http://www.approximatrix.com
 *  
 *  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/>.
 */

package com.approximatrix.draco.editor.ui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.EventListenerList;

import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rtextarea.RTextScrollPane;
import org.fife.ui.rtextarea.SearchEngine;

import com.approximatrix.draco.application.ApplicationManager;
import com.approximatrix.draco.application.icons.VectorIcon;
import com.approximatrix.draco.application.preferences.PreferencesManager;
import com.approximatrix.draco.editor.events.EditorFileStatusEvent;
import com.approximatrix.draco.editor.events.EditorFileStatusEventSource;
import com.approximatrix.draco.editor.events.EditorFileStatusListener;
import com.approximatrix.draco.editor.syntax.comments.CommentEngine;
import com.approximatrix.draco.editor.syntax.comments.NoCommentEngine;
import com.approximatrix.draco.editor.syntax.comments.RubyCommentEngine;
import com.approximatrix.draco.io.PlainTextUtilities;
import com.approximatrix.draco.io.filters.PlainTextFilter;
import com.approximatrix.draco.io.filters.RubyFileFilter;

public class RubyEditorPanel extends JPanel implements EditorFileStatusEventSource, ItemListener, CaretListener, DocumentListener, ActionListener {

	private static final String NO_SYNTAX = "No Syntax";
	private static final String RUBY_SYNTAX = "Ruby Syntax";
	
	protected static String DEFAULT_FILENAME = "Untitled"; 
	
	private static final String CLOSE_ICON = "FILECLOSE";
	private static VectorIcon icon = null;
	
	private File file = null;
	private String simpleName = null;
	private RSyntaxTextArea editor = null; 
	
	private JComboBox syntaxCombo = null;
	private JLabel positionLabel = null;
	private JCheckBox saveCheckBox = null;
	private JPanel statusPanel = null;
	
	private JPanel findPanel = null;
	private JTextField searchField = null;
	private JCheckBox regexCheck = null;
	private JButton findFwdBarButton = null;
	private JButton findBkdBarButton = null;
	private JCheckBox caseCheck = null;
	private JButton closeFindButton = null;
	
	private boolean modified = true;
	
	private boolean antialiasing = true;
	
	private CommentEngine commentEngine = null;
	
	/** The listener list. */
    protected EventListenerList listeners = null;
	
	public RubyEditorPanel() {
		file = null;
		initialize();
		listeners = new EventListenerList();
		setNoSyntax();
	}
	
	public RubyEditorPanel(String contents) {
		this();
		this.setContents(contents);
	}
	
	public RubyEditorPanel(String contents, boolean isRuby) {
		this();
		this.setContents(contents);
		if(isRuby)
			setRubySyntax();
		else
			setNoSyntax();
		
		updateSyntaxCombo();
		
		this.editor.setCaretPosition(0);
	}
	
	public RubyEditorPanel(String contents, String name, String mimeType) {
		this(contents);
		this.setVisibleName(name);
		this.setMimeType(mimeType);
		updateSyntaxCombo();
	}
	
	public RubyEditorPanel(File file) {
		this();
		try {
			String contents = PlainTextUtilities.LoadText(file);
			this.setContents(contents);
			editor.getDocument().addDocumentListener(this);
		} catch(FileNotFoundException fnfe) {
			this.setContents("");
		}
		
		if(file.getName().endsWith(".rb"))
			setRubySyntax();
		else
			setNoSyntax();
		
		this.modified = false;
		updateSyntaxCombo();
		
		this.editor.setCaretPosition(0);
		this.file = file;
		this.saveCheckBox.setSelected(false);
	}
	
	private void setRubySyntax() {
		try {
			editor.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_RUBY);
		} catch(NullPointerException npe) {
			System.err.println("Null pointer exception while setting the editor syntax to none");
		}
		commentEngine = new RubyCommentEngine();
	}
	
	private void setNoSyntax() {
		try {
			editor.setSyntaxEditingStyle(null);
		} catch(NullPointerException npe) {
			System.err.println("Null pointer exception while setting the editor syntax to none");
		}
		commentEngine = new NoCommentEngine();
	}
	
	private void initialize() {
		this.setLayout(new BorderLayout());
		
		PreferencesManager manager = new PreferencesManager();
		
		this.antialiasing = manager.getEditorPrefs().isAntiAliasing();
		
		try {
			editor = new RSyntaxTextArea() {

				@Override
				protected void paintComponent(Graphics g) {
					try {
						if(antialiasing)
							((Graphics2D) g).addRenderingHints(new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON));
						else
							((Graphics2D) g).addRenderingHints(new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF));
					} catch (Exception e) {
						//continue - not too important
					}

					super.paintComponent(g);
				}

			};

			editor.setFont(new Font(manager.getEditorPrefs().getFontName(), 
					Font.PLAIN, 
					manager.getEditorPrefs().getFontSize()));

			editor.addCaretListener(this);

			//editor.setSyntaxEditingStyle(style);
			RTextScrollPane sp = new RTextScrollPane(500,400, editor, true);

			this.add(sp,BorderLayout.CENTER);

		} catch(HeadlessException he) {
			System.out.println("Running headless - blocked creation of RSyntaxTextArea...");
		}
	    
	    JPanel bottomPanels = new JPanel();
	    bottomPanels.setLayout(new BorderLayout());
	    bottomPanels.add(getStatusPanel(),BorderLayout.SOUTH);
	    bottomPanels.add(getFindPanel(),BorderLayout.NORTH);
	    getFindPanel().setVisible(false);
	    
	    this.add(bottomPanels,BorderLayout.SOUTH);
	}
	
	private JPanel getStatusPanel() {
		if(statusPanel == null) {
			statusPanel = new JPanel();
			statusPanel.setLayout(new BorderLayout());
			
			positionLabel = new JLabel();
			updatePositionLabel();
			statusPanel.add(positionLabel,BorderLayout.EAST);
			
			JPanel subPanel = new JPanel();
			subPanel.setLayout(new BorderLayout());
			
			saveCheckBox = new JCheckBox("Save with Worksheet");
			saveCheckBox.setSelected(true);
			saveCheckBox.addItemListener(this);
			subPanel.add(saveCheckBox,BorderLayout.EAST);
			
			syntaxCombo = new JComboBox();
			syntaxCombo.addItem(NO_SYNTAX);
			syntaxCombo.addItem(RUBY_SYNTAX);
			syntaxCombo.setSelectedItem(NO_SYNTAX);
			syntaxCombo.addItemListener(this);
			subPanel.add(syntaxCombo,BorderLayout.WEST);
			
			statusPanel.add(subPanel,BorderLayout.WEST);
		}
		return statusPanel;
	}
	
	public boolean isSavedWithWorksheet() {
		try {
			return saveCheckBox.isSelected();
		} catch(NullPointerException npe) {
			return true;
		}
	}
	
	private void updateSyntaxCombo() {
		if(editor == null)
			return;
		else if(editor.getSyntaxEditingStyle() == null)
			syntaxCombo.setSelectedItem(NO_SYNTAX);
		else if(editor.getSyntaxEditingStyle().equals(SyntaxConstants.SYNTAX_STYLE_NONE))
			syntaxCombo.setSelectedItem(NO_SYNTAX);
		else if(editor.getSyntaxEditingStyle().equals(SyntaxConstants.SYNTAX_STYLE_RUBY))
			syntaxCombo.setSelectedItem(RUBY_SYNTAX);
	}
	
	private void updatePositionLabel() {
		positionLabel.setText(getPositionText());
	}
	
	private String getPositionText() {
		try {
			return "L "+Integer.toString(editor.getCaretLineNumber()+1)+
					" C "+Integer.toString(editor.getCaretOffsetFromLineStart()+1);
		} catch(NullPointerException npe) {
			System.err.println("Null pointer exception while determining editor cursor position");
			return "";
		}
	}

	public void setFilename(File file) {
		this.file = file;
	}
	
	public void setVisibleName(String name) {
		if(name == null)
			this.simpleName = DEFAULT_FILENAME;
		else
			this.simpleName = name;
	}
	
	@Override
	public String getName() {
		if(this.isSavedWithWorksheet()) {
			if(simpleName == null)
				setVisibleName(null);
			return simpleName;
		} else {
		
			if(file == null)
				return DEFAULT_FILENAME;
			else
				return file.getName();
		}
	}
	
	public void setContents(String contents) {
		if(editor == null) {
			System.err.println("Bad editor panel while setting contents - no editor present");
			return;
		}
		
		if(contents != null)
			editor.setText(contents);
		else
			editor.setText("");
	}
	
	public JPanel getFindPanel() {
		if(findPanel == null) {
			findPanel = new JPanel();
			findPanel.setLayout(new FlowLayout());
			
			findPanel.add(new JLabel("Find:"));
			
			searchField = new JTextField() ;
			Dimension d = searchField.getPreferredSize();
			d.width = 150;
			searchField.setPreferredSize(d);
			
			findPanel.add(searchField);
			
			findFwdBarButton = new JButton("Forward");
			findFwdBarButton.addActionListener(this);
			findPanel.add(findFwdBarButton);
			
			findBkdBarButton = new JButton("Backward");
			findBkdBarButton.addActionListener(this);
			findPanel.add(findBkdBarButton);
			
			caseCheck = new JCheckBox("Match Case");
			findPanel.add(caseCheck);
			
			regexCheck = new JCheckBox("Regex");
			findPanel.add(regexCheck);
			
			findPanel.add(Box.createHorizontalGlue());
			
			closeFindButton = new JButton();
			if(icon == null) {
				try {
					icon = ApplicationManager.GetIcon(CLOSE_ICON);
					icon.setDimension(new Dimension(searchField.getPreferredSize().height/2,searchField.getPreferredSize().height/2));
					icon.setFixedSize(true);
				} catch(NullPointerException npe) {
					
				}
			}
			closeFindButton.setIcon(icon);
			closeFindButton.addActionListener(this);
			findPanel.add(closeFindButton);
		}
		return findPanel;
	}
	
	public void addEditorFileStatusListener(EditorFileStatusListener listener) {
		listeners.add(EditorFileStatusListener.class, listener);
	}

	public void fireEditorFileStatusEvent(int type) {
		EditorFileStatusEvent e = new EditorFileStatusEvent(this,type); 
		Object[] ls = listeners.getListenerList();
        for (int i = (ls.length - 2); i >= 0; i-=2) {
            if (ls[i] == EditorFileStatusListener.class) {
                ((EditorFileStatusListener)ls[i + 1]).editorFileStatusChanged(e);
            }
        }
	}

	public void removeEditorFileStatusListener(EditorFileStatusListener listener) {
		listeners.remove(EditorFileStatusListener.class, listener);
	}
	
	/** Returns whether the file in the editor has been modified
	 * 
	 * @return true if not saved, false otherwise
	 */
	public boolean isModified() {
		if(file == null)
			return true;
		return modified;
	}
	
	/** Passes cut command to the editor */
	public void cut() {
		this.editor.cut();
	}
	
	/** Passes copy command to the editor */
	public void copy() {
		this.editor.copy();
	}
	
	/** Passes paste command to the editor */
	public void paste() {
		this.editor.paste();
	}
	
	private void performFind(boolean forward) {
		SearchEngine.find(editor, searchField.getText(), forward, 
				caseCheck.isSelected(), false, regexCheck.isSelected());
	}
	
	public void save() {
		try {
    		PlainTextUtilities.SaveText(file, this.editor.getText());
    		this.modified = false;
    		this.fireEditorFileStatusEvent(EditorFileStatusEvent.FILE_SAVED);
    		editor.getDocument().addDocumentListener(this);
    	} catch(IOException ioe) {
    		JOptionPane.showMessageDialog(this, "There was a problem saving the file: \n"+file.getName(),"Error", JOptionPane.ERROR_MESSAGE);
    		this.fireEditorFileStatusEvent(EditorFileStatusEvent.FILE_MODIFIED);
    	}
	}
	
	public void saveAs() {
		JFileChooser chooser = new JFileChooser();
		chooser.setDialogTitle("Save Text As...");
		chooser.setDialogType(JFileChooser.SAVE_DIALOG);
		chooser.setFileFilter(new RubyFileFilter());
		chooser.setFileFilter(new PlainTextFilter());
	    if (chooser.showOpenDialog(chooser) == JFileChooser.APPROVE_OPTION) {
	    	this.setFilename(chooser.getSelectedFile());
	    	save();
	    }
	}

	public void itemStateChanged(ItemEvent e) {
		if(e.getSource() == syntaxCombo) {
			if(NO_SYNTAX.equals(syntaxCombo.getSelectedItem()))
				editor.setSyntaxEditingStyle(null);
			else if(RUBY_SYNTAX.equals(syntaxCombo.getSelectedItem()))
				this.setRubySyntax();
		} else if(e.getSource() == this.saveCheckBox) {
			this.fireEditorFileStatusEvent(EditorFileStatusEvent.FILE_IN_WORKSHEET_CHANGE);
		}
	}
	
	public void toggleCommentSelected() {
		try {
			String replacement = commentEngine.toggleComment(editor.getSelectedText());
			editor.replaceSelection(replacement);
		} catch(NullPointerException npe) {
			return;
		}
	}
	
	public void undo() {
		editor.undoLastAction();
	}

	public void redo() {
		editor.redoLastAction();
	}
	
	public void caretUpdate(CaretEvent arg0) {
		updatePositionLabel();
	}

	public String getText() {
		try {
			return editor.getText();
		} catch(NullPointerException npe) {
			if(java.awt.GraphicsEnvironment.isHeadless()) {
				System.out.println("Headless caught - passing fake editor contents...");
				return "I am headless...";
			} else
				throw npe;
		}
	}
	
	public void find() {
		getFindPanel().setVisible(true);
		this.doLayout();
	}
	
	public String getMimeType() {
		try {
			if(editor.getSyntaxEditingStyle() == SyntaxConstants.SYNTAX_STYLE_RUBY)	
				return "text/x-ruby";
			else
				return "text/plain";
		} catch(NullPointerException npe) {
			if(java.awt.GraphicsEnvironment.isHeadless()) {
				System.out.println("Headless caught - passing fake editor mime type...");
				return "text/plain";
			} else
				throw npe;
		}
	}
	
	public void setMimeType(String value) {
		if(editor == null) {
			System.err.println("Error setting mime type - no editor present");
			return;
		}
		
		if(value == null)
			editor.setSyntaxEditingStyle(null);
		else {
			if(value.equals("text/x-ruby"))
				this.setRubySyntax();
			else
				editor.setSyntaxEditingStyle(null);
		}
	}
	
	public void changedUpdate(DocumentEvent arg0) {
		this.modified = true;
		this.editor.getDocument().removeDocumentListener(this);
		this.fireEditorFileStatusEvent(EditorFileStatusEvent.FILE_MODIFIED);
	}

	public void insertUpdate(DocumentEvent arg0) {}

	public void removeUpdate(DocumentEvent arg0) {}

	public void actionPerformed(ActionEvent e) {
		if(e.getSource() == closeFindButton) {
			getFindPanel().setVisible(false);
			this.doLayout();
		} else if(e.getSource() == findFwdBarButton && searchField.getText() != null && searchField.getText().length() > 0)
			performFind(true);
		else if(e.getSource() == findBkdBarButton && searchField.getText() != null && searchField.getText().length() > 0)
			performFind(false);
	}
	
}
