/*
 * Zettelkasten - nach Luhmann
 * Copyright (C) 2001-2015 by Daniel Lüdecke (http://www.danielluedecke.de)
 * 
 * Homepage: http://zettelkasten.danielluedecke.de
 * 
 * 
 * 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/>.
 * 
 * 
 * Dieses Programm ist freie Software. Sie können es unter den Bedingungen der GNU
 * General Public License, wie von der Free Software Foundation veröffentlicht, weitergeben
 * und/oder modifizieren, entweder gemäß Version 3 der Lizenz oder (wenn Sie möchten)
 * jeder späteren Version.
 * 
 * Die Veröffentlichung dieses Programms erfolgt in der Hoffnung, daß es Ihnen von Nutzen sein 
 * wird, aber OHNE IRGENDEINE GARANTIE, sogar ohne die implizite Garantie der MARKTREIFE oder 
 * der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Details finden Sie in der 
 * GNU General Public License.
 * 
 * Sie sollten ein Exemplar der GNU General Public License zusammen mit diesem Programm 
 * erhalten haben. Falls nicht, siehe <http://www.gnu.org/licenses/>.
 */

package de.danielluedecke.zettelkasten.database;

import de.danielluedecke.zettelkasten.util.Constants;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.IllegalAddException;
import org.jdom2.IllegalDataException;

/**
 *
 * @author danielludecke
 */
public class AutoKorrektur {

    /**
     * XML document that holds the data for auto-correction. Each element "entry"
     * has an attribute named "id" that contains the wrong, mispelled word. the
     * entry's text is the correct writing of the word.
     */
    private Document autokorrektur;
    /**
     * XML document that is used as backup
     */
    private Document backupdoc = null;
    
    
    /**
     * This class stores the information about the automatic correction (spelling) when
     * the user makes input for new entries.
     */
    public AutoKorrektur() {
        clear();
    }


    /**
     * Clears the XML document and creates a dummy-backup of the document, in case the original
     * XML-document contains data.
     */
    public final void clear() {
        // check whether backup document exists, whether autokorrektur-document exists and whether
        // the autokorrektur-document has any data. only in this case we create a backup
        if (autokorrektur!=null && autokorrektur.getRootElement().getContentSize()>0) {
            // create new backup doc
            backupdoc = new Document(new Element("backup_autokorrektur"));
            // copy content
            backupdoc.getRootElement().addContent(autokorrektur.getRootElement().cloneContent());
        }
        autokorrektur = new Document(new Element("autokorrektur"));
    }
    /**
     * This method checks whether the XML document is ok or corrupted. in case there have been
     * jdom-errors when adding new elements, the XML document {@link #autokorrektur} might
     * be empty, while the backup-document {@link #backupdoc} has data. In this case,
     * {@code false} is returned. If the XML-document is ok, {@code true} is returned.
     * @return {@code true} if the main XML-document is ok, {@code false}if it might be corrupted.
     * <br><br>
     * You can use {@link #restoreDocument() restoreDocument()} to restore a corrupted document.
     */
    public boolean isDocumentOK() {
        // check whether we have any XML-document at all. proceed only, if we have no document
        // of if the XML-document does not contain data
        if ((null==autokorrektur) || (autokorrektur.getRootElement().getContentSize()<1)) {
            // now check whether we have a backup of the XML document, which has content
            if ((backupdoc!=null) && (backupdoc.getRootElement().getContentSize()>0)) {
                // if so, the backup contains data that the main document does not has
                // so, we assume the document is *not* ok
                return false;
            }
        }
        // else everything is fine
        return true;
    }
    /**
     * In case we have a corrupted XML document with a backup document that has data
     * (see {@link #isDocumentOK() isDocumentOK()}), we can restore the backupped data
     * with this method.<br><br>
     * So, this method copies back the content of the {@link #backupdoc} to the
     * original XML document {@link #autokorrektur}.
     */
    public void restoreDocument() {
        // check whether we have a backup document that also contains data
        if ((backupdoc!=null) && (backupdoc.getRootElement().getContentSize()>0)) {
            // if we have it, create new main XML document
            autokorrektur = new Document(new Element("autokorrektur"));
            // and copy the content of the backup document to it
            autokorrektur.getRootElement().addContent(backupdoc.getRootElement().cloneContent());
        }
    }

    
    /**
     * Sets the document, e.g. after loading the settings
     * @param d (the document with the auto-correction-data)
     */
    public void setDocument(Document d) {
        autokorrektur = d;
    }
    /**
     * Gets the xml-document that contains the auto-correction-data
     * @return the xml-document with the auto-correction-data
     */
    public Document getDocument() {
        return autokorrektur;
    }
    
    
    /**
     * Returns the amount of elements
     * @return Returns the amount of elements as integer value
     */
    public int getCount() {
        return autokorrektur.getRootElement().getContentSize();
    }


    /**
     * Checks whether the value passed by the parameter "e" already exists in the
     * data. Therefore, each element's id-attribute is compared to the parameter "e".
     * @param e the string we want to look for if it exists
     * @return {@code true} if we found it, false if it doesn't exist
     */
    public boolean exists(String e) {
        // get all elements
        List<Element> all = autokorrektur.getRootElement().getChildren();
        // create an iterator
        Iterator<Element> i = all.iterator();
        // go through list
        while (i.hasNext()) {
            // get element
            Element el = i.next();
            // get attribute
            String att = el.getAttributeValue("id");
            // if the element's id equals the requestes string "e", return true
            // i.e. the string e already exists as element
            if (att!=null && att.equalsIgnoreCase(e)) {
                return true;
            }
        }
        // nothing found...
        return false;
        
    }


    /**
     * Adds a new pair of false/correct words to the document
     * 
     * @param falsch the wrong or mispelled word
     * @param richtig the correct writing
     * @return {@code true} if element was successfully addes, {@code false} if "falsch" already existed or
     * one of the parameters were invalid
     */
    public boolean addElement(String falsch, String richtig) {
        // check for minimum length
        if (null==falsch || falsch.length()<2 || null==richtig || richtig.length()<2) {
            return false;
        }
        // check for existence
        if (exists(falsch)) {
            return false;
        }
        // if it doesn't already exist, create new element
        Element e = new Element(Daten.ELEMENT_ENTRY);
        try {
            // set id-attribute
            e.setAttribute("id", falsch);
            // set content
            e.setText(richtig);
            // and add it to the document
            autokorrektur.getRootElement().addContent(e);
        }
        catch (IllegalAddException ex) {
            Constants.zknlogger.log(Level.SEVERE,ex.getLocalizedMessage());
            return false;
        }
        catch (IllegalDataException ex) {
            Constants.zknlogger.log(Level.SEVERE,ex.getLocalizedMessage());
            return false;
        }
        // return success
        return true;
    }
    
    
    /**
     * Gets a String-pair of auto-correct data, i.e. a string array with 2 fields. first
     * field contains the wrong/mispelled writing, the second field holds the correct
     * version of the word
     * @param nr the position of the element we want to retrieve
     * @return a string array containing the wrong and right spelling
     */
    public String[] getElement(int nr) {
        // get all elements
        List<Element> all = autokorrektur.getRootElement().getChildren();
        // get the requested element
        Element el = all.get(nr);
        // new string array
        String[] retval = null;
        // if we found an element, return its content
        if (el!=null) {
            retval = new String[2];
            retval[0] = el.getAttributeValue("id");
            retval[1] = el.getText();
        }
        
        return retval;
    }
    

    /**
     *
     * @param wrong
     * @return the correct spelling of the word {@code wrong} or {@code null}, if no spellcorrection was found
     */
    public String getCorrectSpelling(String wrong) {
        // get all elements
        List<Element> all = autokorrektur.getRootElement().getChildren();
        // create an iterator
        Iterator<Element> i = all.iterator();
        // go through list
        while (i.hasNext()) {
            // get element
            Element el = i.next();
            // get attribute value
            String att = el.getAttributeValue("id");
            // check for existing attribute
            if (null==att) {
                return null;
            }
            // get spell-check-word
            String correct = att.toLowerCase();
            // get lower-case word of mistyped wrong word...
            String retval = wrong.toLowerCase();
            // if the element's id equals the requestes string "e", return true
            // i.e. the string e already exists as element
            if (correct.equalsIgnoreCase(wrong)) {
                // now that we found the correct word, we want to see whether
                // the word starts with an upper case letter - and if so, convert
                // the first letter of the return value to upper case as well
                String firstLetter = wrong.substring(0, 1);
                String firstBigLetter = wrong.substring(0, 1).toUpperCase();
                // get return value
                retval = el.getText();
                // if both matches, we have upper case initial letter
                // convert first letter to uppercase
                if (firstLetter.equals(firstBigLetter)) {
                    retval = retval.substring(0,1).toUpperCase()+retval.substring(1);
                }
                return retval;
            }
            // when the misspelled phrase starts with an asterisk, we know that we should check the
            // end of or in between the typed word "wrong".
            if (correct.startsWith("*")) {
                // first we remove the asterisk
                correct = correct.substring(1);
                // if the misspelled phrase also ends with an asterisk, we have to check
                // for the phrase in between - that means, "wrong" is not allowed to end or start
                // with "correct"
                if (correct.endsWith(("*"))) {
                    // remove trailing asterisk
                    correct = correct.substring(0, correct.length()-1);
                    // if the mistyped word "wrong" does not start and end with "correct", we know
                    // that we have a correction in between
                    if (retval.contains(correct)) {
                        // return correct word for wrong spelling
                        return correctWithCase(retval,correct,el.getText(),wrong);
                    }
                }
                // if the mistyped word "wrong" does not end with "correct", we know
                // that
                else if (retval.endsWith(correct) && retval.contains(correct)) {
                    // return correct word for wrong spelling
                    return correctWithCase(retval,correct,el.getText(),wrong);
                }
            }
            else if (correct.endsWith("*")) {
                // get lower-case word of mistyped wrong word...
                retval = wrong.toLowerCase();
                // if the mistyped word "wrong" does not end with "correct", we know
                // that we have the correction at thr word beginning
                if (retval.startsWith(correct) && retval.contains(correct)) {
                    // return correct word for wrong spelling
                    return correctWithCase(retval,correct,el.getText(),wrong);
                }
            }
        }
        // return null, no correct word found
        return null;
    }


    private String correctWithCase(String retval, String correct, String newtext, String wrong) {
        retval = retval.replace(correct, newtext);
        // now that we found the correct word, we want to see whether
        // the word starts with an upper case letter - and if so, convert
        // the first letter of the return value to upper case as well
        String firstLetter = wrong.substring(0, 1);
        String firstBigLetter = wrong.substring(0, 1).toUpperCase();
        // if both matches, we have upper case initial letter
        // convert first letter to uppercase
        if (firstLetter.equals(firstBigLetter)) {
            retval = retval.substring(0,1).toUpperCase()+retval.substring(1);
        }
        return retval;
    }
}