package net.sf.jabref.logic.openoffice;

import java.util.EnumSet;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.sf.jabref.logic.layout.Layout;
import net.sf.jabref.model.database.BibDatabase;
import net.sf.jabref.model.entry.BibEntry;

import com.sun.star.beans.PropertyVetoException;
import com.sun.star.beans.UnknownPropertyException;
import com.sun.star.beans.XPropertySet;
import com.sun.star.lang.IllegalArgumentException;
import com.sun.star.lang.WrappedTargetException;
import com.sun.star.text.ControlCharacter;
import com.sun.star.text.XParagraphCursor;
import com.sun.star.text.XText;
import com.sun.star.text.XTextCursor;
import com.sun.star.uno.UnoRuntime;

/**
 * Utility methods for processing OO Writer documents.
 */
public class OOUtil {

    private static final String CHAR_STRIKEOUT = "CharStrikeout";
    private static final String CHAR_UNDERLINE = "CharUnderline";
    private static final String PARA_STYLE_NAME = "ParaStyleName";
    private static final String CHAR_CASE_MAP = "CharCaseMap";
    private static final String CHAR_POSTURE = "CharPosture";
    private static final String CHAR_WEIGHT = "CharWeight";
    private static final String CHAR_ESCAPEMENT_HEIGHT = "CharEscapementHeight";
    private static final String CHAR_ESCAPEMENT = "CharEscapement";


    public enum Formatting {
        BOLD,
        ITALIC,
        SMALLCAPS,
        SUPERSCRIPT,
        SUBSCRIPT,
        UNDERLINE,
        STRIKEOUT,
        MONOSPACE
    }

    private static final Pattern HTML_TAG = Pattern.compile("</?[a-z]+>");

    private static final String UNIQUEFIER_FIELD = "uniq";


    private OOUtil() {
        // Just to hide the public constructor
    }

    /**
     * Insert a reference, formatted using a Layout, at the position of a given cursor.
     * @param text The text to insert in.
     * @param cursor The cursor giving the insert location.
     * @param layout The Layout to format the reference with.
     * @param parStyle The name of the paragraph style to use.
     * @param entry The entry to insert.
     * @param database The database the entry belongs to.
     * @param uniquefier Uniqiefier letter, if any, to append to the entry's year.
     */
    public static void insertFullReferenceAtCurrentLocation(XText text, XTextCursor cursor,
            Layout layout, String parStyle, BibEntry entry, BibDatabase database, String uniquefier)
                    throws UndefinedParagraphFormatException, UnknownPropertyException, PropertyVetoException,
                    WrappedTargetException, IllegalArgumentException {

        // Backup the value of the uniq field, just in case the entry already has it:
        Optional<String> oldUniqVal = entry.getField(UNIQUEFIER_FIELD);


        // Set the uniq field with the supplied uniquefier:
        if (uniquefier == null) {
            entry.clearField(UNIQUEFIER_FIELD);
        } else {
            entry.setField(UNIQUEFIER_FIELD, uniquefier);
        }

        // Do the layout for this entry:
        String lText = layout.doLayout(entry, database);

        // Afterwards, reset the old value:
        if (oldUniqVal.isPresent()) {
            entry.setField(UNIQUEFIER_FIELD, oldUniqVal.get());
        } else {
            entry.clearField(UNIQUEFIER_FIELD);
        }

        // Insert the formatted text:
        OOUtil.insertOOFormattedTextAtCurrentLocation(text, cursor, lText, parStyle);
    }

    /**
     * Insert a text with formatting indicated by HTML-like tags, into a text at
     * the position given by a cursor.
     * @param text The text to insert in.
     * @param cursor The cursor giving the insert location.
     * @param lText The marked-up text to insert.
     * @param parStyle The name of the paragraph style to use.
     * @throws WrappedTargetException
     * @throws PropertyVetoException
     * @throws UnknownPropertyException
     * @throws IllegalArgumentException
     */
    public static void insertOOFormattedTextAtCurrentLocation(XText text, XTextCursor cursor, String lText,
            String parStyle) throws UndefinedParagraphFormatException, UnknownPropertyException, PropertyVetoException,
                    WrappedTargetException, IllegalArgumentException {

        XParagraphCursor parCursor = UnoRuntime.queryInterface(
                XParagraphCursor.class, cursor);
        XPropertySet props = UnoRuntime.queryInterface(
                XPropertySet.class, parCursor);

        try {
            props.setPropertyValue(PARA_STYLE_NAME, parStyle);
        } catch (IllegalArgumentException ex) {
            throw new UndefinedParagraphFormatException(parStyle);
        }

        Set<Formatting> formatting = EnumSet.noneOf(Formatting.class);
        // We need to extract formatting. Use a simple regexp search iteration:
        int piv = 0;
        Matcher m = OOUtil.HTML_TAG.matcher(lText);
        while (m.find()) {
            String ss = lText.substring(piv, m.start());
            if (!ss.isEmpty()) {
                OOUtil.insertTextAtCurrentLocation(text, cursor, ss, formatting);
            }
            String tag = m.group();
            // Handle tags:
            if ("<b>".equals(tag)) {
                formatting.add(Formatting.BOLD);
            } else if ("</b>".equals(tag)) {
                formatting.remove(Formatting.BOLD);
            } else if ("<i>".equals(tag) || "<em>".equals(tag)) {
                formatting.add(Formatting.ITALIC);
            } else if ("</i>".equals(tag) || "</em>".equals(tag)) {
                formatting.remove(Formatting.ITALIC);
            } else if ("<tt>".equals(tag)) {
                formatting.add(Formatting.MONOSPACE);
            } else if ("</tt>".equals(tag)) {
                formatting.remove(Formatting.MONOSPACE);
            } else if ("<smallcaps>".equals(tag)) {
                formatting.add(Formatting.SMALLCAPS);
            } else if ("</smallcaps>".equals(tag)) {
                formatting.remove(Formatting.SMALLCAPS);
            } else if ("<sup>".equals(tag)) {
                formatting.add(Formatting.SUPERSCRIPT);
            } else if ("</sup>".equals(tag)) {
                formatting.remove(Formatting.SUPERSCRIPT);
            } else if ("<sub>".equals(tag)) {
                formatting.add(Formatting.SUBSCRIPT);
            } else if ("</sub>".equals(tag)) {
                formatting.remove(Formatting.SUBSCRIPT);
            } else if ("<u>".equals(tag)) {
                formatting.add(Formatting.UNDERLINE);
            } else if ("</u>".equals(tag)) {
                formatting.remove(Formatting.UNDERLINE);
            } else if ("<s>".equals(tag)) {
                formatting.add(Formatting.STRIKEOUT);
            } else if ("</s>".equals(tag)) {
                formatting.remove(Formatting.STRIKEOUT);
            }

            piv = m.end();

        }

        if (piv < lText.length()) {
            OOUtil.insertTextAtCurrentLocation(text, cursor, lText.substring(piv), formatting);
        }

        cursor.collapseToEnd();
    }

    public static void insertParagraphBreak(XText text, XTextCursor cursor) throws IllegalArgumentException {
        text.insertControlCharacter(cursor, ControlCharacter.PARAGRAPH_BREAK, true);
        cursor.collapseToEnd();
    }

    public static void insertTextAtCurrentLocation(XText text, XTextCursor cursor, String string,
            Set<Formatting> formatting)
                    throws UnknownPropertyException, PropertyVetoException, WrappedTargetException,
                    IllegalArgumentException {
        text.insertString(cursor, string, true);
        // Access the property set of the cursor, and set the currently selected text
        // (which is the string we just inserted) to be bold
        XPropertySet xCursorProps = UnoRuntime.queryInterface(
                XPropertySet.class, cursor);
        if (formatting.contains(Formatting.BOLD)) {
            xCursorProps.setPropertyValue(CHAR_WEIGHT,
                    com.sun.star.awt.FontWeight.BOLD);
        } else {
            xCursorProps.setPropertyValue(CHAR_WEIGHT,
                    com.sun.star.awt.FontWeight.NORMAL);
        }

        if (formatting.contains(Formatting.ITALIC)) {
            xCursorProps.setPropertyValue(CHAR_POSTURE,
                    com.sun.star.awt.FontSlant.ITALIC);
        } else {
            xCursorProps.setPropertyValue(CHAR_POSTURE,
                    com.sun.star.awt.FontSlant.NONE);
        }

        if (formatting.contains(Formatting.SMALLCAPS)) {
            xCursorProps.setPropertyValue(CHAR_CASE_MAP,
                    com.sun.star.style.CaseMap.SMALLCAPS);
        }        else {
            xCursorProps.setPropertyValue(CHAR_CASE_MAP,
                    com.sun.star.style.CaseMap.NONE);
        }

        // TODO: the <monospace> tag doesn't work
        /*
        if (formatting.contains(Formatting.MONOSPACE)) {
            xCursorProps.setPropertyValue("CharFontPitch",
                            com.sun.star.awt.FontPitch.FIXED);
        }
        else {
            xCursorProps.setPropertyValue("CharFontPitch",
                            com.sun.star.awt.FontPitch.VARIABLE);
        } */
        if (formatting.contains(Formatting.SUBSCRIPT)) {
            xCursorProps.setPropertyValue(CHAR_ESCAPEMENT,
                    (byte) -101);
            xCursorProps.setPropertyValue(CHAR_ESCAPEMENT_HEIGHT,
                    (byte) 58);
        } else if (formatting.contains(Formatting.SUPERSCRIPT)) {
            xCursorProps.setPropertyValue(CHAR_ESCAPEMENT,
                    (byte) 101);
            xCursorProps.setPropertyValue(CHAR_ESCAPEMENT_HEIGHT,
                    (byte) 58);
        } else {
            xCursorProps.setPropertyValue(CHAR_ESCAPEMENT,
                    (byte) 0);
            xCursorProps.setPropertyValue(CHAR_ESCAPEMENT_HEIGHT,
                    (byte) 100);
        }

        if (formatting.contains(Formatting.UNDERLINE)) {
            xCursorProps.setPropertyValue(CHAR_UNDERLINE, com.sun.star.awt.FontUnderline.SINGLE);
        } else {
            xCursorProps.setPropertyValue(CHAR_UNDERLINE, com.sun.star.awt.FontUnderline.NONE);
        }

        if (formatting.contains(Formatting.STRIKEOUT)) {
            xCursorProps.setPropertyValue(CHAR_STRIKEOUT, com.sun.star.awt.FontStrikeout.SINGLE);
        } else {
            xCursorProps.setPropertyValue(CHAR_STRIKEOUT, com.sun.star.awt.FontStrikeout.NONE);
        }
        cursor.collapseToEnd();

    }

    public static void insertTextAtCurrentLocation(XText text, XTextCursor cursor, String string, String parStyle)
            throws WrappedTargetException, PropertyVetoException, UnknownPropertyException,
            UndefinedParagraphFormatException {
        text.insertString(cursor, string, true);
        XParagraphCursor parCursor = UnoRuntime.queryInterface(
                XParagraphCursor.class, cursor);
        // Access the property set of the cursor, and set the currently selected text
        // (which is the string we just inserted) to be bold
        XPropertySet props = UnoRuntime.queryInterface(
                XPropertySet.class, parCursor);
        try {
            props.setPropertyValue(PARA_STYLE_NAME, parStyle);
        } catch (IllegalArgumentException ex) {
            throw new UndefinedParagraphFormatException(parStyle);
        }
        cursor.collapseToEnd();

    }

    public static Object getProperty(Object o, String property)
            throws UnknownPropertyException, WrappedTargetException {
        XPropertySet props = UnoRuntime.queryInterface(
                XPropertySet.class, o);
        return props.getPropertyValue(property);
    }
}
