/*
****************************************************************************
* This version of this file is part of DictionaryForMIDs-Creator
* (C) 2012 Karim Mahamane Karimou
*
* This version is a modified version. It was modified to make it compatible
* with DictionaryForMIDs-Creator. It was modified by me. See below for
* information about the original copyright holder.
*
* DictionaryForMIDs-Creator (DfM-Creator) is a GUI wrapper around various
* DictionaryForMIDs tools, among others we have DictdToDictionaryForMIDs,
* DictionaryGeneration, JarCreator and BitmapFontGenerator.
*  
* GPL applies, see file COPYING for more license information.
* 
*****************************************************************************/


/*
DictionaryForMIDs - a free multi-language dictionary for mobile devices.
Copyright (C) 2005 Gert Nuber (Gert.Nuber@gmx.net)
GPL applies - see file COPYING for copyright statement.


This is the dictionary convertion tool for DictionaryForMIDs.
It takes a dictd file, using jDictd, and converts it to a CSV-file.
*/

package de.kugihan.dictionaryformids.dictdtodictionaryformids;

import de.kugihan.DfMCreator.DfMCreatorMain;
import de.kugihan.DfMCreator.SumWinDictdToDfM;
import edu.hws.eck.mdb.I18n;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import javax.swing.JOptionPane;

    public class DictdToDfM {

	public static String versionNumber = "2.5.0";
        
        // Flag that will tell us wether the DictdToDfM
        // is being called from DfM-Creator or from the
        // command line. Set it to true if called from
        // DfM-Creator and to false otherwise.
        public static boolean flag = false;
        
	private static String dbName;
	private static String dbFolderName;
	private static String outputCSVFile;
	private static String outputEncodingCharset;
	private static char separatorCharacter;
	private static boolean switchLanguages;
	private static boolean keepTabAndNewLineChars;
	private static boolean removeSquareBrackets;
        
        // We will use the following variable and its getter
        // method to retrieve the 00-header of dictd files.
        private static String header;
        
        /*
         * Getter methods for DictdToDfM class.
         */

        public static String getDBName(){
            return dbName;
        }
        
        public static String getDBFolderName(){
            return dbFolderName;
        }
        
        public static String getOutputCSVFile(){
            return outputCSVFile;
        }
        
        public static String getOutputEncodingCharset(){
            return outputEncodingCharset;
        }
        
        public static char getSeparatorCharacter(){
            return separatorCharacter;
        }
        
        public static boolean getSwitchLanguages(){
            return switchLanguages;
        }
        
        public static boolean getKeepTabAndNewLineChars(){
            return keepTabAndNewLineChars;
        }
        
        public static boolean getRemoveSquareBrackets(){
            return removeSquareBrackets;
        }
        
        // Getter method for the header variable.
        public static String getHeader(){
            return header;
        }
        
        /*
         * Setter methods.
         */
        
        public static void setDBName(String newDBName){
            dbName = newDBName;
        }
        
        public static void setDBFolderName(String newDBFolderName){
            dbFolderName = newDBFolderName;
        }
        
        public static void setOutputCSVFile(String newOutputCSVFile){
            outputCSVFile = newOutputCSVFile;
        }
        
        public static void setOutputEncodingCharset(String newOutputEncodingCharset){
            outputEncodingCharset = newOutputEncodingCharset;
        }
        
        public static void setSeparatorCharacter(char newSeparatorCharacter){
            separatorCharacter = newSeparatorCharacter;
        }
        
        public static void setSwitchLanguages(boolean newSwitchLanguages){
            switchLanguages = newSwitchLanguages;
        }
        
        public static void setKeepTabAndNewLineChars(boolean newKeepTabAndNewLineChars){
            keepTabAndNewLineChars = newKeepTabAndNewLineChars;
        }
        
        public static void setRemoveSquareBrackets(boolean newRemoveSquareBrackets){
            removeSquareBrackets = newRemoveSquareBrackets;
        }
        
        
    // subroutine to be called to retrieve the summary
    // of the conversion preferences before the actual
    // conversion.
    public static void printDictdConvSummary() {
        SumWinDictdToDfM summary = SumWinDictdToDfM.getInstance();
        summary.setVisible(true);
    }
        
	public static void main(String[] args) {
            
            // Starting the interaction with the user.
            printDBSetUpInfo();
            dbName = TextIO.getlnWord();

            printDBFolderNamePrompt();
            dbFolderName = TextIO.getlnWord();

            printOutputCSVfileNamePrompt();
            outputCSVFile = TextIO.getlnWord();

            printSwitchLanguagesPrompt();
            switchLanguages = TextIO.getlnBoolean();

            printKeepTabAndNewlineCharsPrompt();
            keepTabAndNewLineChars = TextIO.getlnBoolean();

            printRemoveSquareBracketsPrompt();
            removeSquareBrackets = TextIO.getlnBoolean();

            printOutputEncodingCharsetPrompt();
            outputEncodingCharset = TextIO.getlnWord();

            printSeparatorCharacterPrompt();
            stripCharFromInput();

            // Call the conversion subroutine
            convert();
        }   
	 
	/*
	 * The convertion subroutine: reads all the entries from the DICT-database
	 * and writes them line by line to the output file.
	 */
	 
	 public static void convert() {

            try {
               try (OutputStreamWriter outputWriter = new OutputStreamWriter(new FileOutputStream(outputCSVFile),
                    outputEncodingCharset)) {
                        org.dict.kernel.IDictEngine e =
                        org.dict.server.DatabaseFactory.getEngine(dbFolderName + "/" + dbName + ".ini");

                        String definition;
                        /* In a loop all entries of the datebase are retrieved */
                        for (int pos = 0; pos < e.getDatabases()[0].getSize(); ++ pos) {
                        //for (int pos = 0; pos < 1000; ++ pos) {  // this line is for tests only
                                // generate the string to query the entry at pos
                                String posString = "db=" + dbName + "&pos=" + pos;
                                // retrieve the definition at pos
                                org.dict.kernel.IRequest req =
                                                new org.dict.kernel.SimpleRequest("", posString);
                                org.dict.kernel.IAnswer[] arr = e.lookup(req);
                                // parse the result and write to the output file
                                for (int i = 0; i < arr.length; ++i) {
                                        definition = arr[i].getDefinition();
                                        if (! definition.startsWith("00")) {  // skip the entries starting with "00" - these contain
                                                                                // general information about the dictionary
                                                if (removeSquareBrackets)
                                                        definition = removeSquareBracketContent(definition);
                                                // get rid of the separatorCharacter in the definition
                                                definition = replaceCharacterInString(definition, separatorCharacter, ' ');
                                                String definitionConverted = definition;
                                                // search where the keyword ends and the translation starts
                                                int posTranslationSeparator = definitionConverted.indexOf('\n');
                                                int posEndKeyWord = posTranslationSeparator - 1;
                                                int posStartTranslation = posTranslationSeparator + 1;
                                                // separate keyword and translation
                                                String keyWord = definitionConverted.substring(0, posEndKeyWord + 1).trim();
                                                String translation = definitionConverted.substring(posStartTranslation);
                                                translation = removeNewlinesAtBeginAndEnd(translation); // useful e.g. for freedict English-Turkish Dictionary
                                                translation = replaceFieldAndLineSeparationChars(translation);  // note: separatorCharacter was already removed before
                                                // if required, perform adaptation to output character encoding
                                                translation = adaptToCharset(translation);
                                                keyWord = adaptToCharset(keyWord);
                                                // if there should be any repeated whitespaces in the translation, trim the superflous whitespaces
                                                translation = filterSuperflousWhitespaces(translation);
                                                // write line to the output file
                                                String outputLine = new String();
                                                if (switchLanguages) {
                                                        outputLine = translation + separatorCharacter + keyWord + "\n";
                                                }
                                                else {
                                                        outputLine = keyWord + separatorCharacter + translation + "\n";
                                                }
                                                outputWriter.write(outputLine);
                                        }
                                        else {
                                                // print to stdout the "00" entries (for information)

                                                System.out.println(definition);
                                                header = "\n\n" + definition + "\n\n";

                                        }
                                }
                        }
                    }
			System.out.println(I18n.tr("done"));
		} catch (UnsupportedEncodingException e){
                    // If DictdToDictionaryForMIDs is being called from DfM-Creator make
                    // it possible to display graphical error messages if exceptions occur.
                    if (!flag) {
                        SumWinDictdToDfM.done = true;
                        DfMCreatorMain.printAnyMsg(I18n.tr("encErrMsg", new Object[] {e.getLocalizedMessage()}),
                                                   I18n.tr("encErrWinTitle"), JOptionPane.ERROR_MESSAGE);
                        // We'll print the exception in command line anyway.
                        System.out.println("\n" + e.getMessage() + "\n");
                        
                    // If DictdToDictionaryForMIDs is being called from CLI prevent the app
                    // from displaying graphical error messages when exceptions occur.                        
                    } else {
                        System.out.println("\nThe selected Character Encoding is not supported.");                        
                        System.out.println("Here is the value that you entered: " + e.getMessage());
                        System.out.println("Please choose another one; UTF-8 works fine.");
                    }
                } catch (ArrayIndexOutOfBoundsException e) {
                        System.out.println("\nAn unexpected error occurred! Probably one or more of the required");
                        System.out.println("arguments was/were not provided. Technical ause of the error: " + e);
                } catch (StringIndexOutOfBoundsException e) {
                        System.out.println("\nAn internal error occurred! Please make sure that the dictionary and");
                        System.out.println("arguments you provided are valid. Technical cause of the error: " + e);
                } catch (Throwable t){
                    if (!flag) {
                        DfMCreatorMain.printAnyMsg(I18n.tr("criticalErrMsg", new Object[]
                                {t, t.getLocalizedMessage()}), I18n.tr("error"), JOptionPane.ERROR_MESSAGE);
                        // Print exception in command line anyway
                        System.out.println("\n" + t.getMessage() + "\n");
                    } else {
                        System.out.println("\nA critical error occured; Operation aborted!");
                        System.out.println("If you are familiar with technical details.");
                        System.out.println("check the message below:");
                        System.out.println("Error: " + t.getMessage());
                        System.out.println("Description/Cause: " + t.getCause());
                    }
                } 
	}
        
	
	static public void printCopyrightNotice() {
            System.out.print(
                "\n\nDictionaryForMIDs - DictdToDictionaryForMIDs Version " + versionNumber + "\n" +
                "Copyright (C) 2005 Gert Nuber, 2012 Karim Mahamane Karimou\n\n" +

                "DictdToDictionaryForMIDs is free software; you can redistribute it and/or modify\n" +
                "it under the terms of the GNU General Public License as published by\n" +
                "the Free Software Foundation; either version 2 of the License, or\n" +
                "(at your option) any later version.\n\n" +

                "DictdToDictionaryForMIDs is distributed in the hope that it will be useful,\n" +
                "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +
                "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n" +
                "GNU General Public License for more details.\n\n" +

                "You should have received a copy of the GNU General Public License\n" +
                "along with DictdToDictionaryForMIDs; if not, write to the Free Software Foundation,\n" +
                "Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA\n" +
                "For documentation and source code, see http://dictionarymid.sourceforge.net\n\n");
	}

	/*
	 * Interactive messages subroutines
         */
	
	
	static public void printDBSetUpInfo() {
            System.out.print("\n__________________________________________________________________________________\n"
                    + "DATABASE_NAME\n"
                    + "Enter the name of the database:\n"
                    + "The name of the database defines the DICT-database to be used. It must be defined for JDictd.\n"
                    + " See the documentation for JDictd to see how to set up a DICT-database for JDictd. The JDictd\n"
                    + "database configuration file must have the name <dbname-value>.ini. e.g. eng-por.ini.\n"
                    + "This ini-file needs to be copied in the folder where the dictionary files are.\n");
	}
	

	static public void printDBFolderNamePrompt() {
            System.out.print("\n__________________________________________________________________________________\n"
                    + "DATABASE_FOLDER_PATH\n"
                    + "Enter the path and the name of the folder where the ini file is located:\n"
                    + "e.g: /home/user/Dicts/Gcide on a Linux/Unix machine or C:/Dicts/Gcide\n"
                    + "on a Windows machine. NOTE: Don't add the last '/' to the folder name this\n"
                    + "is added automatically by me! Instead of C:/Dicts/Gcide/ write C:/Dicts/Gcide\n");
	}



	static public void printOutputCSVfileNamePrompt() {
            System.out.print("\n__________________________________________________________________________________\n"
                    + "OUTPUT_CSV_FILE_PATH\n"
                    + "Enter the path to the output CSV-file. This is where the converted dictionary will be\n"
                    + "written to. This file need not be present even if it were, it will be overwritten anyway.\n"
                    + "Example /Generated_Dict/gcide.txt (Linux); C:/Generated_Dict/gcide.txt (Windows)\n");
	}



	static public void printSwitchLanguagesPrompt() {
            System.out.print("\n__________________________________________________________________________________\n"
                    + "SWITCH_LANGUAGES\n"                    
                    + "This will toggle the SWITCH_LANGUAGES value On/Off. If SWITCH_LANGUAGES is set to true then\n"
                    + "the translation is put in the first column and the keyword in the second. If it is set to\n"
                    + "false, the keyword is put in the first column and the translation in the second\n"
                    + "Enter: true, false, t, f, yes, no, y, n, 0, or 1. You can put upper or lowercase letters.\n");
	}


	static public void printKeepTabAndNewlineCharsPrompt() {
            System.out.print("\n__________________________________________________________________________________\n"
                    + "KEEP_TAB_AND_NEW_LINE_CHARACTERS\n"
                    + "When KEEP_TAB_AND_NEW_LINE_CHARACTERS is set to true then the newline and tab characters\n"
                    + "in the translation text are replaced by '\\n' and '\\t'. DictionaryForMIDs will then insert\n"
                    + "newlines/tabs when displaying the result. If KEEP_TAB_AND_NEW_LINE_CHARACTERS is set to false\n"
                    + "then the newline and tab characters are replaced by blanks.\n"
                    + "Enter: true, false, t, f, yes, no, y, n, 0, or 1. You can put upper or lowercase letters.\n");
	}
	

	static public void printRemoveSquareBracketsPrompt() {
            System.out.print("\n__________________________________________________________________________________\n"
                    + "REMOVE_SQUARE_BRACKETS\n"
                    + "If REMOVE_SQUARE_BRACKETS is set to true, then any text within square brackets is removed\n"
                    + "from the keyword/translation. However, bear in mind that this is a very special (dangerous)\n"
                    + "flag that normally is set to false. It is only set to true when really needed.\n"
                    + "Enter: true, false, t, f, yes, no, y, n, 0, or 1. You can put upper or lowercase letters.\n");
	}
	
	
	static public void printSeparatorCharacterPrompt() {
            System.out.print("\n__________________________________________________________________________________\n"
                    + "SEPARATOR_CHARACTER\n"
                    + "It defines the character between the first and the second column (normally a tab-character).\n"
                    + "The property \"dictionaryGenerationSeparatorCharacter\" for DictionaryGeneration has to have the\n"
                    + "same value as SEPARATOR_CHARACTER. NOTE: if you want to enter one of the following characters\n"
                    + "follow these instructions:\n"
                    + "For a TAB character enter TAB or tab\n"
                    + "For a CARRIAGE-RETURN character enter CR or cr\n"
                    + "For a FORMFEED character enter FF or ff\n"
                    + "For any other printed character enter it as it is. Note that if you enter a string (many characters)\n"
                    + "only the first one will be taken, the other ones will be discarded.\n");
	}
	
	
	static public void printOutputEncodingCharsetPrompt() {
            System.out.print("\n__________________________________________________________________________________\n"
                    + "OUTPUT_ENCODING_CHARACTER_SET\n"
                    + "It defines the character encoding for the generated file. UTF-8 works with all files.\n"
                    + "The property dictionaryGenerationInputCharEncoding for DictionaryGeneration has to have\n"
                    + "the same value as OUTPUT_ENCODING_CHARSET. You can enter for example UTF-8, it works fine.\n");
	}
         

	/*
	 * 
	 * A few utility methods:
	 * 
	 */
	 
	public static String removeSquareBracketContent(String expression) {
		// remove everything in square brackets
		int startBracket = expression.indexOf('[');
		int endBracket = expression.indexOf(']');
		StringBuilder returnExpression = new StringBuilder(expression);
		if ((startBracket >= 0) && (endBracket > startBracket)) {
			returnExpression.delete(startBracket, endBracket+1);
		}
		return returnExpression.toString();
	}

	public static String replaceCharacterInString(String input, char oldCharacter, char newCharacter) {
		String output = new String();
		for (int pos = 0; pos < input.length(); ++pos) {
			if (input.charAt(pos) != oldCharacter) {
                                output = output + input.charAt(pos); 
			}
			else {
                                output = output + newCharacter;
			}
		}
		return output;
	}
	
	public static String adaptToCharset(String input)
                                throws IOException {
		String output;
		if ("ISO_8859-1".equals(outputEncodingCharset)) {
			// Replace characters that do not exist in ISO-8859-1 with blanks.
			String inputCleaned = new String();
			for (int posInput = 0; posInput < input.length(); ++posInput) {
                                char characterFromInput = input.charAt(posInput);
                                int charType = Character.getType(characterFromInput);
                                if ((charType == Character.COMBINING_SPACING_MARK) ||
                                	(charType == Character.DASH_PUNCTUATION ) ||
                                	(charType == Character.FORMAT ) ||
                                	(charType == Character.SPACE_SEPARATOR ) ||
                                	(charType == Character.UNASSIGNED )) {
                                	// replace that character by a blank
                                	inputCleaned = inputCleaned + ' '; 
                                }
                                else {
                                	inputCleaned = inputCleaned + characterFromInput; 
                                }
			}
			output = new String();
			// get rid of characters that are converted into ?
			for (int posInput = 0; posInput < inputCleaned.length(); ++posInput) {
                                String characterFromInput = inputCleaned.substring(posInput, posInput + 1);
                                byte [] bytesForString = characterFromInput.getBytes(outputEncodingCharset); 
                                if ((bytesForString.length == 1) && (bytesForString[0] == (int) '?')) {
                                	// replace that character by a blank
                                	output = output + ' '; 
                                }
                                else {
                                	output = output + characterFromInput; 
                                }
			}
		}
		else {
			// keep input
			output = input;
		}
		return output;
	}

	public static String replaceFieldAndLineSeparationChars(String input) {
		String output;

		if (! keepTabAndNewLineChars) {
			output = replaceCharacterInString(input, '\n', ' ');
			output = replaceCharacterInString(output, separatorCharacter, ' ');
		}
		else {
			output = new String();
			for (int pos = 0; pos < input.length(); ++pos) {
                                char currentCharacter = input.charAt(pos);
                                if (currentCharacter == '\n') {
                                	output = output + "\\n";
                                }
                                else if (currentCharacter == '\t') {
                                	output = output + "\\t";
                                }
                                else if (currentCharacter == '\\') {
                                	output = output + "\\\\";
                                }
                                else {
                                	output = output + currentCharacter;
                                }
			}
		}
		return output;
	}

	// duplicated for convenience from de.kugihan.dictionaryformids.general.Util: 
	public static String filterSuperflousWhitespaces(String expression) {
		String resultExpression = new String();
		boolean lastCharWasWhitespace = false;
		for (int charPos = 0; charPos < expression.length(); ++ charPos) {
			char character = expression.charAt(charPos);
			if ( (character == '\t') || (character == ' ')) {
			    if (resultExpression.length() == 0) {
                                	// ignore whitspaces at the beginning of the expression
                                }
                                else if (charPos == expression.length() -1) {
                                	// ignore whitspaces at the end of the expression
                                }
                                else if (lastCharWasWhitespace)  {
                                	// ignore multiple whitespaces in sequence
                                }

                                else {
                                	resultExpression = resultExpression + character;
                                }
                                lastCharWasWhitespace = true;
			}
			else {
                                resultExpression = resultExpression + character;
                                lastCharWasWhitespace = false;
			}
		}
		return resultExpression;
	}

	public static String removeNewlinesAtBeginAndEnd(String input) {
		String output;
		int pos = 0;
		while ((pos < input.length()) && (input.charAt(pos) == '\n'))
			++pos;
		if (pos < input.length())
			output = input.substring(pos, input.length());
		else 
			output = "";
		pos = output.length() -1;
		while ((pos >= 0) && (output.charAt(pos) == '\n'))
			--pos;
		if (pos >= 0)
			output = output.substring(0, pos + 1);
		else 
			output = "";
		return output;
	}

    // Analyzes the the string input for separatorCharacter
    // and returns a valid separatorCharacter.
    private static void stripCharFromInput() {
        
        String tempSepChar = TextIO.getlnWord();
        
        switch (tempSepChar) {
                case "TAB":
                    separatorCharacter = '\t';
                break;
                
                // lowercase tab
                case "tab":
                    separatorCharacter = '\t';
                break;
                    
                case "CR":
                    separatorCharacter = '\r';
                break;
                   
                // lowercase cr
                case "cr":
                    separatorCharacter = '\r';
                break;
                    
                case "FF":
                    separatorCharacter = '\f';
                break;
                
                // lowercase ff
                case "ff":
                    separatorCharacter = '\f';
                break;
                    
                default:
                    // separator character becomes the first
                    // character in the tempSepChar string.
                    separatorCharacter = tempSepChar.charAt(0);
                    
                    // display the separator character
                    System.out.println("\nSeparator character stripped from your input: " + separatorCharacter);
       }
        
    }
}
