/**
 *#########################################################################
 *
 * 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.
 *
 * Author: John Thompson, Greenstone Digital Library, University of Waikato
 *
 * 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.file;

import java.io.*;
import java.util.regex.*;
import javax.swing.table.*;
import org.greenstone.gatherer.Gatherer;
import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.DebugStream;
import org.greenstone.gatherer.Dictionary;
import org.greenstone.gatherer.gui.FileAssociationDialog;
import org.greenstone.gatherer.gui.PreviewCommandDialog;
import org.greenstone.gatherer.gui.TestingPreparation;
import org.greenstone.gatherer.util.SafeProcess;
import org.greenstone.gatherer.util.StaticStrings;
import org.greenstone.gatherer.util.Utility;
import org.greenstone.gatherer.util.XMLTools;
import org.w3c.dom.*;

public class FileAssociationManager 
    extends AbstractTableModel {
    static final public String FILENAME_ARG = "%1";
    static final private String ESCAPE = "\\\\"; // '\'
    static final private String ESCAPED_ESCAPE = "\\\\\\\\"; // '\\'
    static final private String SPACE = " ";
    static final private String ESCAPED_SPACE = "\\\\ ";
    private Element associations_element;
    private File data_file;   

    public FileAssociationManager() {
	// Retrieve the associations_element from the config
	associations_element = Configuration.getFileAssociations();
	// Initialize the associations. This involves looking through all current associations searching for those with a command of "".
	if(associations_element != null) {
	    NodeList entries = associations_element.getElementsByTagName(StaticStrings.ENTRY_ELEMENT);
	    for(int i = 0; i < entries.getLength(); i++) {
		Element entry = (Element) entries.item(i);
		String command = XMLTools.getValue(entry);
		// If we encounter a command of ""...
		if(command.length() == 0) {
		    // if we are on windows, we default to the start command
		    if(Utility.isWindows()) {
			if (Utility.isWindows9x()) {
			    XMLTools.setValue(entry, StaticStrings.WIN_9X_OPEN_COMMAND);
			} else {
			    XMLTools.setValue(entry, StaticStrings.WIN_OPEN_COMMAND);
			}
		    }
		    // and if we are on mac, we default to the open program
		    else if(Utility.isMac()) {
			XMLTools.setValue(entry, StaticStrings.MAC_OPEN_COMMAND);
		    }
		    else { // On Linux, we need to discover which of a range of possible solutions
			// works on this machine. If any works, we remember and use that choice
			// for all file extensions for which no file association has been set yet.

			// If we worked out the default linux file open command in a previous
			// round of the FileAssociationManager loop, use it for the current file
			// extension also

			if(StaticStrings.LINUX_OPEN_COMMAND != null &&
			   !StaticStrings.LINUX_OPEN_COMMAND.equals("")) {

			    XMLTools.setValue(entry, StaticStrings.LINUX_OPEN_COMMAND);
			} else if(StaticStrings.LINUX_OPEN_COMMAND == null) {
			    // StaticStrings.LINUX_OPEN_COMMAND not set yet: if we never tried to
			    // work out the default linux file launch command yet, then we'll now
			    // test whether any known linux open command is available on this linux.

			    // xdg-open comes pre-installed on Ubuntu linux, but not on all linux
			    // https://www.cyberciti.biz/faq/unix-linux-command-to-view-file/
			    // Listing xdg-open 1st as it works on Ubuntu, reducing GLI load time
			    final String[] linux_open_cmds = {"xdg-open", "kde-open", "gnome-open"};
			    
			    for(String open_cmd : linux_open_cmds) {
				Gatherer.ProgramInstalledTest installTest = new Gatherer.ProgramInstalledTest(open_cmd);
				if(installTest.found()) {
				    System.err.println("*********** Linux file open command " + installTest + " was found to be installed.");
				    StaticStrings.LINUX_OPEN_COMMAND = open_cmd + " %1";
				    XMLTools.setValue(entry, StaticStrings.LINUX_OPEN_COMMAND);
				    break;
				} else {
				    System.err.println("*********** Linux file open command " + installTest + " was not installed.");
				}
			    }

			    // either we have a default linux open command or not
			    // If we don't, set it to empty, so we don't try working out
			    // the linux open command value for each file extension (for
			    // each iteration of the FileAssociationManager loop) hereafter.
			    if(StaticStrings.LINUX_OPEN_COMMAND == null) {
				StaticStrings.LINUX_OPEN_COMMAND = "";
			    }
			}
			// else StaticStrings.LINUX_OPEN_COMMAND = ""; which means at some point
			// we'd tried but failed to work out a feasible file open command for this linux
		    }
		}
		command = null;
		entry = null;
	    }
	    entries = null;
	}
	else {
	    DebugStream.println("Didn't parse anything. About to crash.");
	}
    }
    
    public void edit() {
	FileAssociationDialog dialog = new FileAssociationDialog(this);
	TestingPreparation.setNamesRecursively(dialog);
	dialog.display(null);
	dialog = null;		  
    }

    public String getBrowserCommand(String url) {
	DebugStream.println("Get browser command: " + url);
	// First off we try to retrieve one from the configuration
	String command = Configuration.getPreviewCommand();
	// If that worked, substitute in the url and return
	if(command != null && command.length() > 0) {
	    command = command.replaceAll("%1", url);
	    DebugStream.println("Result = " + command);
	    return command;
	}
	command = null;
	// Failing that we have a guess at a sensible default 
	if(Utility.isWindows()) {
	    // we use cmd and start
	    if (Utility.isWindows9x()) {
		command = StaticStrings.WIN_9X_OPEN_COMMAND;//"command.com /c start \""+url+"\"";
	    } else {
		command = StaticStrings.WIN_OPEN_COMMAND;//"cmd.exe /c start \"\" \""+url+"\"";
	    }
	} else if (Utility.isMac()) {
	    command = StaticStrings.MAC_OPEN_COMMAND; // "open %1"
	} else {
		// we try to look for a browser
	    String [] browsers = new String [] {"mozilla", "netscape", "firefox"};
	    for (int i=0; i<browsers.length; i++) {
		if (SafeProcess.isAvailable(browsers[i])) {
		    command = browsers[i]+ " %1";
		    break;
		}
	    }
		//if (command == null) { command = StaticStrings.LINUX_OPEN_COMMAND; } // "xdg-open %1"
	}

	// if we still haven't found something, prompt the user
	if (command == null) {
	    PreviewCommandDialog dialog = new PreviewCommandDialog();
	    command = dialog.display();
	    dialog.dispose();
	    dialog = null;
	}

	// Store the result if any
	if(command != null && !command.equals("")) {
	    Configuration.setPreviewCommand(command);
	    command = command.replaceAll(FILENAME_ARG, url);
	    DebugStream.println("Result = " + command);
	    return command;
	}
	// if we haven't got a command by now, we'll never get one
	DebugStream.println("Result = null");
	return null;
	
    }

    public int getColumnCount() {
	return 2;
    }

    public String getColumnName(int column) {
	String name;
	switch(column) {
	case 0:
	    name = Dictionary.get("FileAssociationDialog.Table.Extension");
	    break;
	default:
	    name = Dictionary.get("FileAssociationDialog.Table.Command");
	}
	return name;
    }
   
    public String [] getCommand(File file) {	
	String command = null;
	String [] commands = null;
	if(file.isFile()) {
	    // Determine extension
	    String filename = file.getAbsolutePath();
	    String extension = filename.substring(filename.lastIndexOf(".") + 1);
	    // Try to retrieve a value from cache
	    Element entry = getCommand(extension);
	    if(entry != null) {
		///ystem.err.println("Retrieved Value From Cache");
		command = XMLTools.getValue(entry); 
	    }
	    if(command == null || command.length() == 0) {
		///ystem.err.println("No Existing Command");
		// If command is null, and we are on windows try searching the registry.
		if(Utility.isWindows()) {
		    //try the start command
		    if (Utility.isWindows9x()) {
			command = StaticStrings.WIN_9X_OPEN_COMMAND;
		    } else {
			command = StaticStrings.WIN_OPEN_COMMAND;
		    }
		    
		}
		
		// If we are on a mac, default to using the open program
		else if(Utility.isMac()) {
		    command = StaticStrings.MAC_OPEN_COMMAND;
		}
		
		//else { command = StaticStrings.LINUX_OPEN_COMMAND; } // If linux, default to using xdg-open
		
		// Otherwise display the dialog and ask the user to enter launching command.
		if(command == null || command.length() == 0) {
		    ///ystem.err.println("Show Dialog");
		    // Show the dialog which forces a user to select the launch command for a certain file.
		    FileAssociationDialog dialog = new FileAssociationDialog(this);
		    command = dialog.display(extension);
		    dialog = null;
		}

		// Hopefully by now we have a command, or else we're never going to get one. Add the association.
		if (command != null && !command.equals("")) {
		    // If no previous entry existed create one.
		    if(entry == null) {
			entry = associations_element.getOwnerDocument().createElement(StaticStrings.ENTRY_ELEMENT);
			entry.setAttribute(StaticStrings.EXTENSION_ATTRIBUTE, extension);
			associations_element.appendChild(entry);
		    }
		    // Replace the text in this node. Remember to replace the dummy filename with %1 - I dont think the filename will ever be in the comand now
		    //XMLTools.setValue(entry, command.replaceAll(filename, FILENAME_ARG));
		    XMLTools.setValue(entry, command);
		}
	    }

	    if (command != null && !command.equals("")) {		
		
		// Make the command into a string []		
		commands = command.split(" ");

		// Now substitute any occurrences of %1 with its filename
		// Note this is done after the split on spaces to avoid
		// any conflict with filenames with spaces in them.

		// We have to fix filename under windows to escape the backslashes
		filename = filename.replaceAll(ESCAPE, ESCAPED_ESCAPE);
		
		// dealing with spaces in filepath when using start command
		if(Utility.isWindows() && filename.indexOf(" ") != -1 && command.indexOf("start") != -1) { 
			// On Windows, start command used spaces in filepath. In this case:
			// start and its arguments all together need to go into one element of the commands array
			// otherwise <cmd /c start "window title" "%1"> does not work if there are spaces in the
			// file path %1, when running the Process with a command array.
			
			// Need <"start \"window\" \"%1\""> to be an element in the command array:
			String[] tmp = commands;
			int index = 0;
			
			for(int i = 0; i < commands.length; i++) {
				if(commands[i].indexOf("start") != -1) {
					index = i;
				}
			}
			
			commands = new String[index+1];
			for(int i = 0; i < index; i++) {
				commands[i] = tmp[i];
			}
			
			commands[index] = tmp[index];
			for(int i = index+1; i < tmp.length; i++) {
				commands[index] = commands[index] + " " + tmp[i];
			}
			
		}

		for (int i=0; i<commands.length; i++) {
		    // Replace %1 with the appropriate filename
		    commands[i] = commands[i].replaceAll(FILENAME_ARG, filename);
		}
	    }
	    
	    entry = null;
	    extension = null;
	    filename = null;
	}
	return commands;
    }

    public Element getCommand(String target_extension) {
	NodeList entries = associations_element.getElementsByTagName(StaticStrings.ENTRY_ELEMENT);
	for(int i = 0; i < entries.getLength(); i++) { 
	    Element entry = (Element) entries.item(i);
	    String extension = entry.getAttribute(StaticStrings.EXTENSION_ATTRIBUTE);
	    if(extension.equalsIgnoreCase(target_extension)) {
		entries = null;
		extension = null;
		return entry;
	    }
	}
	entries = null;
	return null;
    }

    public String getCommandString(String target_extension) {
	Element entry = getCommand(target_extension);
	if(entry != null) {
	    return XMLTools.getValue(entry);
	}
	else {
	    return "";
	}
    }

    public String getExtension(int index) {
	NodeList entries = associations_element.getElementsByTagName(StaticStrings.ENTRY_ELEMENT);
	if(0 <= index && index < entries.getLength()) {
	    Element entry = (Element) entries.item(index);
	    return entry.getAttribute(StaticStrings.EXTENSION_ATTRIBUTE);
	}
	return "";
    }

    public int getRowCount() {
	return size();
    }

    public Object getValueAt(int row, int column) {
	String extension = getExtension(row);
	switch(column) {
	case 0:
	    return extension;
	default:
	    return getCommandString(extension);
	}
    }

    public void save() {
    }

    public void setCommand(String extension, String command) {
	DebugStream.println("Set Launch: " + extension + " with " + command);
	// Retrieve any existing entry for this extension
	Element entry = getCommand(extension);
	// If no previous entry existed create one.
	if(entry == null && command != null) {
	    entry = associations_element.getOwnerDocument().createElement(StaticStrings.ENTRY_ELEMENT);
	    entry.setAttribute(StaticStrings.EXTENSION_ATTRIBUTE, extension);
	    associations_element.appendChild(entry);
	}

	if(command != null) {
	    // Replace the text in this node. If the user has used filename instead of %1 then too bad.
	    XMLTools.setValue(entry, command);
	}
	else {
	    // Remove the entry
	    associations_element.removeChild(entry);
	}
	entry = null;
	fireTableDataChanged(); // Can't be anymore efficient as DOM does not gareuntee ordering of new child nodes is consistant
    }

    public int size() {
	NodeList entries = associations_element.getElementsByTagName(StaticStrings.ENTRY_ELEMENT);
	return entries.getLength();
    }

}
