/**
 *#########################################################################
 *
 * 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) 2024 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;

// for RSyntaxTextArea editor's search functionality:
import org.fife.ui.rtextarea.SearchEngine;
import org.fife.ui.rtextarea.SearchContext;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;

import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.Dictionary;
import org.greenstone.gatherer.Gatherer;
import org.greenstone.gatherer.util.StaticStrings;
import org.greenstone.gatherer.util.Utility;
//import org.greenstone.gatherer.util.XMLTools;

//import org.w3c.dom.*;

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

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

/*
SyntaxConstants highlighting stypes that we might use in a collection
  SYNTAX_STYLE_NONE
  SYNTAX_STYLE_CSS
  SYNTAX_STYLE_CSV
  SYNTAX_STYLE_DTD
  SYNTAX_STYLE_JAVASCRIPT
  $YNTAX_STYLE_JSON
  SYNTAX_STYLE_HTML
  SYNTAX_STYLE_PERL
  SYNTAX_STYLE_PROPERTIES_FILE
  SYNTAX_STYLE_XML

 */

public class TextFileEditor extends ModalDialog 
    implements ActionListener, DocumentListener
{
    public final File config_file;
  protected String syntax_type = null;
  
    protected GLIButton cancel_button = null;
    protected GLIButton save_button = null;
    protected NumberedJTextArea editor = null;

    protected JPanel toolbars_panel;
    protected JPanel content_pane;
    // https://github.com/bobbylight/RSyntaxTextArea/wiki/Example:-Using-Find-and-Replace
    final protected JTextField searchField;
    protected JCheckBox regexCB;
    protected JCheckBox matchCaseCB;
    protected JButton nextButton;
    protected JButton prevButton;
    
    protected static final Dimension SIZE = new Dimension(850,550);

  public TextFileEditor(File text_file) {
    this(text_file, null);
  }
  
  public TextFileEditor(File text_file, String syntax_type) {
	
	super(Gatherer.g_man, true);
	setModal(true);
	setSize(SIZE);

        this.config_file = text_file;
        this.syntax_type = syntax_type;
        if (this.syntax_type == null) {
          this.syntax_type = getSyntaxType(config_file);
        }

	// Find functionality - can extend for Find and Replace
	// taken from https://github.com/bobbylight/RSyntaxTextArea/wiki/Example:-Using-Find-and-Replace
	// Create a toolbar with searching options.
	JToolBar toolBar = new JToolBar();
	searchField = new JTextField(30);
	toolBar.add(searchField);
	nextButton = new JButton("Find Next");
	nextButton.setActionCommand("FindNext");
	nextButton.addActionListener(this);
	toolBar.add(nextButton);
	searchField.addActionListener(this);
	prevButton = new JButton("Find Previous");
	prevButton.setActionCommand("FindPrev");
	prevButton.addActionListener(this);
	toolBar.add(prevButton);
	regexCB = new JCheckBox("Regex");
	toolBar.add(regexCB);
	matchCaseCB = new JCheckBox("Match Case");
	toolBar.add(matchCaseCB);
        
	// the all important editor
        // Don't pass in tooltip string for editor:
        // The editor's purpose should be obvious, and
        // the tooltip annoyingly constantly floats over the editor all the time
	editor = new NumberedJTextArea("", "", syntax_type); 
	editor.getDocument().addDocumentListener(this);

        // if Ctrl (or Mac equiv) + F is pressed in the config file editing area,
	// then move the focus to the searchField, so the user can start typing the searchTerm
	editor.addKeyListener(new TextFileEditorKeyListener());
	
	save_button = new GLIButton(Dictionary.get("General.Save"), Dictionary.get("XMLFileEditor.Save_Tooltip"));
	cancel_button = new GLIButton(Dictionary.get("General.Cancel"), Dictionary.get("XMLFileEditor.Cancel_Tooltip")); // tooltip is the same
	cancel_button.addActionListener(this);
	save_button.addActionListener(this);


	JPanel button_panel = new JPanel(new FlowLayout());
	button_panel.setComponentOrientation(Dictionary.getOrientation());
	button_panel.add(editor.undoButton);
	button_panel.add(editor.redoButton);
	button_panel.add(cancel_button);
	button_panel.add(save_button);
	
	// toolbars_panel to contain find-and-replace toolbar and undo/redo/cancel/save button panel
	toolbars_panel = new JPanel(new BorderLayout());
	toolbars_panel.add(toolBar, BorderLayout.NORTH);
	toolbars_panel.add(button_panel, BorderLayout.SOUTH);
	
	content_pane = (JPanel) getContentPane();
	content_pane.setComponentOrientation(Dictionary.getOrientation());
	content_pane.setLayout(new BorderLayout());
	content_pane.add(new JScrollPane(editor), BorderLayout.CENTER); // make editor scrollable
	content_pane.add(toolbars_panel, BorderLayout.SOUTH);

        // set up the content
        setFileTextInEditor();
	    
	// Final dialog setup & positioning.
	this.setDefaultCloseOperation(DISPOSE_ON_CLOSE); // get rid of this dialog when it's closed (on dispose())
	this.addWindowListener(new WindowAdapter() { // called on dispose. Return focus to any curr GLI Pane
		@Override
		public void windowClosed(WindowEvent we) {
		    super.windowClosed(we);		    
		    Gatherer.g_man.doRegainFocus();
		    //Gatherer.g_man.refresh(Gatherer.COLLECTION_OPENED, true);
		    Gatherer.g_man.updateUI();
		}
	    });
	

	
	getRootPane().setDefaultButton(save_button);
	Dimension screen_size = Configuration.screen_size;
	setLocation((screen_size.width - SIZE.width) / 2, (screen_size.height - SIZE.height) / 2);
	editor.setCaretPosition(0); // start at top of config XML

	// Following has no effect: not sure why on dialog show, with or without requesting focus,
	// it takes some time for the editor to get focus and only if user's mouse curser is
	// hovering over XMLFileEditor and moves about a bit there.
	// Give editor focus on dialog show, so that Ctrl-F, to shift focus to searchField, will be
	// responsive too. Not working until mouse moves about over editor, despite the suggestion
	// at https://stackoverflow.com/questions/17828264/java-swing-jdialog-default-focus
	editor.requestFocusInWindow();
    }

  protected String getSyntaxType(File file) {
    String file_name = file.getName();
    String file_ext = file_name.substring(file_name.lastIndexOf(".")+1);
    if (file_ext.matches("xml|xsl|mds|col")) {
      return SyntaxConstants.SYNTAX_STYLE_XML;
    }
    if (file_ext.equals("js")) {
      return SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT;
    }
    if (file_ext.equals("css")) {
      return SyntaxConstants.SYNTAX_STYLE_CSS;
    }
    // wasn't recognised
    // if (file_ext.equals("csv")) {
    //   return SyntaxConstants.SYNTAX_STYLE_CSV;
    // }
    if (file_ext.equals("dtd")) {
      return SyntaxConstants.SYNTAX_STYLE_DTD;
    }
    if (file_ext.matches("htm|html")) {
      return SyntaxConstants.SYNTAX_STYLE_HTML;
    }
    // json wasn't recognised - maybe we are using an older version before this functionality was added
//    if (file_ext.equals("json")) {
//      return SyntaxConstants.SYNTAX_STYLE_JSON;
//    }
    if (file_ext.equals("pm") || file_ext.equals("pl")) {
      return SyntaxConstants.SYNTAX_STYLE_PERL;
    }
    if (file_ext.equals("properties")) {
      return SyntaxConstants.SYNTAX_STYLE_PROPERTIES_FILE;
    }
    return SyntaxConstants.SYNTAX_STYLE_NONE;
  }
  
  protected void setFileTextInEditor() {

    String contents = Utility.readUTF8File(config_file);
    editor.setText(contents);
    
  }

  protected void saveFile() {
    String contents = editor.getText();
    Utility.writeUTF8File(config_file, contents);
  }


    public void actionPerformed(ActionEvent e) {
	
	if(e.getSource() == cancel_button) {
	    this.dispose(); // get rid of this dialog, we're done
	} 
	else if(e.getSource() == save_button) {
          saveFile();          
          this.dispose(); // get rid of this dialog, we're done
          customActionOnSave();
	    
	}
	
	// Find functionality, can extend for Find and Replace
	// taken from https://github.com/bobbylight/RSyntaxTextArea/wiki/Example:-Using-Find-and-Replace
	else if(e.getSource() == searchField) {
	    nextButton.doClick(0);
	}
	else if(e.getSource() == nextButton || e.getSource() == prevButton) {
	    // "FindNext" => search forward, "FindPrev" => search backward
	    String command = e.getActionCommand();
	    boolean forward = "FindNext".equals(command);
	    
	    // Create an object defining our search parameters.
	    SearchContext context = new SearchContext();
	    String text = searchField.getText();
	    if (text.length() == 0) {
		return;
	    }
	    context.setSearchFor(text);
	    context.setMatchCase(matchCaseCB.isSelected());
	    context.setRegularExpression(regexCB.isSelected());
	    context.setSearchForward(forward);
	    context.setWholeWord(false);

	    //boolean found = SearchEngine.find(this.editor, context).wasFound();
	    boolean found = false;
	    try {
		found = SearchEngine.find(this.editor, context);
		// if the current search string is a correction to a previous invalid regex,
		// then return background colour back to normal
		searchField.setBackground(Color.white);
		if (!found) {
		    JOptionPane.showMessageDialog(this, "Text not found");
		}
	    } catch(java.util.regex.PatternSyntaxException regex_exception) {
		searchField.setBackground(Color.red);
		JOptionPane.showMessageDialog(this, "Invalid regex");
	    }
	    
	}
    }

  // sub class to implement if needed - eg close and open collection
  protected void customActionOnSave() {

  }
    // This method returns a proper processing instruction (starts with <?xml and ends with ?>)
    // else the empty string is returned
    public String getProcessingInstruction(String xmlStr) {
	String processingInstruction = "";

	xmlStr = xmlStr.trim();
	if(xmlStr.startsWith("<?xml")) {
	    int endIndex = xmlStr.indexOf("?>"); // end of processing instruction
	    
	    if(endIndex != -1) {
		endIndex += 2;
		processingInstruction = xmlStr.substring(0, endIndex);
	    }
	}	
	return processingInstruction;
    }


    // THE FOLLOWING FUNCTIONS ARE LARGELY FROM Format4gs3Manager.java.EditorListener
    public void changedUpdate(DocumentEvent e)
    {
	update();
    }
    
    public void insertUpdate(DocumentEvent e)
    {
	update();
	updateUndo("insert");
	
    }
    
    public void removeUpdate(DocumentEvent e)
    {
	update();
	updateUndo("remove");
	
    }
    
    protected void updateUndo(String from)
    {
	
	editor.undoButton.setEnabled(true);
    }
    
    public void update()
    {
    }
	
    // if Ctrl (or Mac equiv) + F is pressed in the config file editing area,
    // then move the focus to the searchField, so the user can start typing the searchTerm
    protected class TextFileEditorKeyListener
	extends KeyAdapter
    {
	/** Gives notification of key events on the text field */
	public void keyPressed(KeyEvent key_event)
	{
	    // **** getModifiers() in InputEvent has been deprecated  
	    //   Oracle directs you to use getModifiersEx() which is an extended version that
	    //   has been more carefully constructed.
	    // 

	    //  int onmask = SHIFT_DOWN_MASK | BUTTON1_DOWN_MASK;
	    //  int offmask = CTRL_DOWN_MASK;
	    //  if ((event.getModifiersEx() & (onmask | offmask)) == onmask) {
	    //	  // ...
	    //  }

	    // The above code excerpt is from:
	    //   https://docs.oracle.com/javase/10/docs/api/java/awt/event/InputEvent.html
	    //
	    // In working through changes to avoid using this deprecated call, as similar
	    // change from using getMenuShortcutKeyMask() to getMenuShortcutKeyMaskEx()
	    // will be needed.
	    
	    
	    // Don't hardcode check for Ctrl key "(e.getModifiers() & KeyEvent.CTRL_MASK) != 0)"
	    // because Mac uses another modifier key, Command-F instead of Ctrl-F.
	    // https://stackoverflow.com/questions/5970765/java-detect-ctrlx-key-combination-on-a-jtree
	    if((key_event.getModifiers() == Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())
	       && key_event.getKeyCode() == KeyEvent.VK_F) {

		// Instead of requestFocus() or the more forceful sounding grabFocus(),
		// use requestFocusInWindow(), see
		// https://stackoverflow.com/questions/1425392/how-do-you-set-a-focus-on-textfield-in-swing
		TextFileEditor.this.searchField.requestFocusInWindow();
		//JOptionPane.showMessageDialog(XMLFileEditor.this, "Got a key", "Got key " + key_event.getKeyCode(), JOptionPane.INFORMATION_MESSAGE);
	    }
	}
    }
}
