package net.sf.jabref.gui;

import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.sf.jabref.Globals;
import net.sf.jabref.gui.undo.NamedCompound;
import net.sf.jabref.gui.undo.UndoableFieldChange;
import net.sf.jabref.model.database.BibDatabase;
import net.sf.jabref.model.entry.BibEntry;
import net.sf.jabref.model.entry.FieldName;
import net.sf.jabref.preferences.JabRefPreferences;

public class EntryMarker {

    public static final int MARK_COLOR_LEVELS = 6;
    public static final int MAX_MARKING_LEVEL = MARK_COLOR_LEVELS - 1;
    public static final int IMPORT_MARK_LEVEL = MARK_COLOR_LEVELS;

    private static final Pattern MARK_NUMBER_PATTERN = Pattern.compile(JabRefPreferences.getInstance().MARKING_WITH_NUMBER_PATTERN);


    /**
     * @param increment whether the given increment should be added to the current one. Currently never used in JabRef. Could be used to increase marking color ("Mark in specific color").
     */
    public static void markEntry(BibEntry be, int markIncrement, boolean increment, NamedCompound ce) {
        int prevMarkLevel;
        String newValue = null;
        if (be.hasField(FieldName.MARKED_INTERNAL)) {
            String markerString = be.getField(FieldName.MARKED_INTERNAL).get();
            int index = markerString.indexOf(Globals.prefs.getWrappedUsername());
            if (index >= 0) {
                // Already marked 1 for this user.
                prevMarkLevel = 1;
                newValue = markerString.substring(0, index)
                        + markerString.substring(index + Globals.prefs.getWrappedUsername().length())
                        + Globals.prefs.getWrappedUsername().substring(0,
                                Globals.prefs.getWrappedUsername().length() - 1)
                        + ":" + (increment ? Math.min(MAX_MARKING_LEVEL, prevMarkLevel + markIncrement) : markIncrement)
                        + "]";
            } else {
                Matcher m = MARK_NUMBER_PATTERN.matcher(markerString);
                if (m.find()) {
                    try {
                        prevMarkLevel = Integer.parseInt(m.group(1));
                        newValue = markerString.substring(0, m.start(1)) + (increment ? Math.min(MAX_MARKING_LEVEL, prevMarkLevel + markIncrement) : markIncrement) + markerString.substring(m.end(1));
                    } catch (NumberFormatException ex) {
                        // Do nothing.
                    }
                }
            }
        }
        if (newValue == null) {
            newValue = Globals.prefs.getWrappedUsername().substring(0, Globals.prefs.getWrappedUsername().length() - 1) + ":" + markIncrement + "]";
        }

        ce.addEdit(new UndoableFieldChange(be, FieldName.MARKED_INTERNAL,
                be.getField(FieldName.MARKED_INTERNAL).orElse(null), newValue));
        be.setField(FieldName.MARKED_INTERNAL, newValue);
    }

    /**
     * SIDE EFFECT: Unselects given entry
     */
    public static void unmarkEntry(BibEntry be, boolean onlyMaxLevel, BibDatabase database, NamedCompound ce) {
        if (be.hasField(FieldName.MARKED_INTERNAL)) {
            String markerString = be.getField(FieldName.MARKED_INTERNAL).get();
            if ("0".equals(markerString)) {
                if (!onlyMaxLevel) {
                    unmarkOldStyle(be, database, ce);
                }
                return;
            }
            String newValue = null;
            int index = markerString.indexOf(Globals.prefs.getWrappedUsername());
            if (index >= 0) {
                // Marked 1 for this user.
                if (onlyMaxLevel) {
                    return;
                } else {
                    newValue = markerString.substring(0, index)
                            + markerString.substring(index + Globals.prefs.getWrappedUsername().length());
                }
            } else {
                Matcher m = MARK_NUMBER_PATTERN.matcher(markerString);
                if (m.find()) {
                    try {
                        int prevMarkLevel = Integer.parseInt(m.group(1));
                        if (!onlyMaxLevel || (prevMarkLevel == MARK_COLOR_LEVELS)) {
                            if (prevMarkLevel > 1) {
                                newValue = markerString.substring(0, m.start(1)) + markerString.substring(m.end(1));
                            } else {
                                String toRemove = Globals.prefs.getWrappedUsername().substring(0,
                                        Globals.prefs.getWrappedUsername().length() - 1) + ":1]";
                                index = markerString.indexOf(toRemove);
                                if (index >= 0) {
                                    newValue = markerString.substring(0, index) + markerString.substring(index + toRemove.length());
                                }
                            }
                        } else {
                            return;
                        }
                    } catch (NumberFormatException ex) {
                        // Do nothing.
                    }
                }
            }

            /*int piv = 0, hit;
            StringBuffer sb = new StringBuffer();
            while ((hit = s.indexOf(G047749118118
            1110lobals.prefs.WRAPPED_USERNAME, piv)) >= 0) {
            	if (hit > 0)
            		sb.append(s.substring(piv, hit));
            	piv = hit + Globals.prefs.WRAPPED_USERNAME.length();
            }
            if (piv < s.length() - 1) {
            	sb.append(s.substring(piv));
            }
            String newVal = sb.length() > 0 ? sb.toString() : null;*/
            ce.addEdit(new UndoableFieldChange(be, FieldName.MARKED_INTERNAL,
                    be.getField(FieldName.MARKED_INTERNAL).get(), newValue));
            if (newValue == null) {
                be.clearField(FieldName.MARKED_INTERNAL);
            } else {
                be.setField(FieldName.MARKED_INTERNAL, newValue);
            }
        }
    }

    /**
     * An entry is marked with a "0", not in the new style with user names. We
     * want to unmark it as transparently as possible. Since this shouldn't
     * happen too often, we do it by scanning the "owner" fields of the entire
     * database, collecting all user names. We then mark the entry for all users
     * except the current one. Thus only the user who unmarks will see that it
     * is unmarked, and we get rid of the old-style marking.
     *
     * @param be
     * @param ce
     */
    private static void unmarkOldStyle(BibEntry be, BibDatabase database, NamedCompound ce) {
        Set<Object> owners = new TreeSet<>();
        for (BibEntry entry : database.getEntries()) {
            entry.getField(FieldName.OWNER).ifPresent(owners::add);
        }
        owners.remove(Globals.prefs.get(JabRefPreferences.DEFAULT_OWNER));
        StringBuilder sb = new StringBuilder();
        for (Object owner : owners) {
            sb.append('[');
            sb.append(owner);
            sb.append(']');
        }
        String newVal = sb.toString();
        if (newVal.isEmpty()) {
            ce.addEdit(new UndoableFieldChange(be, FieldName.MARKED_INTERNAL,
                    be.getField(FieldName.MARKED_INTERNAL).orElse(null), null));
            be.clearField(FieldName.MARKED_INTERNAL);
        } else {
            ce.addEdit(new UndoableFieldChange(be, FieldName.MARKED_INTERNAL,
                    be.getField(FieldName.MARKED_INTERNAL).orElse(null), newVal));
            be.setField(FieldName.MARKED_INTERNAL, newVal);
        }
    }

    public static int isMarked(BibEntry be) {
        if (!be.hasField(FieldName.MARKED_INTERNAL)) {
            return 0;
        }
        String s = be.getField(FieldName.MARKED_INTERNAL).get();
        if ("0".equals(s)) {
            return 1;
        }
        int index = s.indexOf(Globals.prefs.getWrappedUsername());
        if (index >= 0) {
            return 1;
        }

        Matcher m = MARK_NUMBER_PATTERN.matcher(s);
        if (m.find()) {
            try {
                return Integer.parseInt(m.group(1));
            } catch (NumberFormatException ex) {
                return 1;
            }
        } else {
            return 0;
        }

    }

    public static boolean shouldMarkEntries() {
        return Globals.prefs.getBoolean(JabRefPreferences.MARK_IMPORTED_ENTRIES);
    }
}
