package org.jabref.logic.layout.format;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.jabref.logic.bst.util.BstNameFormatter;
import org.jabref.logic.layout.LayoutFormatter;
import org.jabref.model.entry.AuthorList;

/**
 * This layout formatter uses the Bibtex name.format$ method and provides ultimate flexibility:
 *
 * The formatter needs a parameter to be passed in that follows the following format:
 *
 * <code>&lt;case1>@&lt;range11>@"&lt;format>"@&lt;range12>@"&lt;format>"@&lt;range13>...@@
 *
 * &lt;case2>@&lt;range21>@...</code> and so on.
 *
 * Individual cases are separated by @@ and items in a case by @.
 *
 * Cases are just integers or the character * and will tell the formatter to apply the following formats if there are
 * less or equal authors given to it. The cases must be in strict increasing order with the * in the last position.
 *
 * For instance:
 *
 * case1 = 2
 * case2 = 3
 * case3 = *
 *
 * Ranges are either &lt;integer>..&lt;integer>, &lt;integer> or the character * using a 1 based index for indexing
 * authors from the given authorlist. Integer indexes can be negative to denote them to start from
 * the end of the list where -1 is the last author.
 *
 * For instance with an authorlist of "Joe Doe and Mary Jane and Bruce Bar and Arthur Kay":
 *
 * 1..3 will affect Joe, Mary and Bruce
 *
 * 4..4 will affect Arthur
 *
 * * will affect all of them
 *
 * 2..-1 will affect Mary, Bruce and Arthur
 *
 * The &lt;format> uses the Bibtex formatter format:
 *
 * The four letter v, f, l, j indicate the name parts von, first, last, jr which
 * are used within curly braces. A single letter v, f, l, j indicates that the name should be abbreviated.
 * To put a quote in the format string quote it using \" (mh. this doesn't work yet)
 *
 * I give some examples but would rather point you to the bibtex documentation.
 *
 * "{ll}, {f}." Will turn "Joe Doe" into "Doe, J."
 *
 * Complete example:
 *
 * To turn:
 *
 * "Joe Doe and Mary Jane and Bruce Bar and Arthur Kay"
 *
 * into
 *
 * "Doe, J., Jane, M., Bar, B. and Kay, A."
 *
 * you would use
 *
 * 1@*@{ll}, {f}.@@2@1@{ll}, {f}.@2@ and {ll}, {f}.@@*@1..-3@{ll}, {f}., @-2@{ll}, {f}.@-1@ and {ll}, {f}.
 *
 * Yeah this is trouble-some to write, but should work.
 *
 * For more examples see the test-cases.
 *
 */
public class NameFormatter implements LayoutFormatter {

    public static final String DEFAULT_FORMAT = "1@*@{ff }{vv }{ll}{, jj}@@*@1@{ff }{vv }{ll}{, jj}@*@, {ff }{vv }{ll}{, jj}";

    private String parameter = NameFormatter.DEFAULT_FORMAT;

    private static String format(String toFormat, AuthorList al, String[] formats) {
        StringBuilder sb = new StringBuilder();

        int n = al.getNumberOfAuthors();

        for (int i = 1; i <= al.getNumberOfAuthors(); i++) {
            for (int j = 1; j < formats.length; j += 2) {
                if ("*".equals(formats[j])) {
                    sb.append(BstNameFormatter.formatName(toFormat, i, formats[j + 1]));
                    break;
                } else {
                    String[] range = formats[j].split("\\.\\.");

                    int s;
                    int e;
                    if (range.length == 2) {
                        s = Integer.parseInt(range[0]);
                        e = Integer.parseInt(range[1]);
                    } else {
                        s = e = Integer.parseInt(range[0]);
                    }
                    if (s < 0) {
                        s += n + 1;
                    }
                    if (e < 0) {
                        e += n + 1;
                    }
                    if (e < s) {
                        int temp = e;
                        e = s;
                        s = temp;
                    }

                    if ((s <= i) && (i <= e)) {
                        sb.append(BstNameFormatter.formatName(toFormat, i, formats[j + 1]));
                        break;
                    }
                }
            }
        }
        return sb.toString();
    }

    public String format(String toFormat, String inParameters) {
        AuthorList al = AuthorList.parse(toFormat);
        String parameters;

        if ((inParameters == null) || inParameters.isEmpty()) {
            parameters = "*:*:\"{ff}{vv}{ll}{,jj} \"";
        } else {
            parameters = inParameters;
        }

        String[] cases = parameters.split("@@");
        for (String aCase : cases) {
            String[] formatString = aCase.split("@");

            if (formatString.length < 3) {
                // Error
                return toFormat;
            }

            if ("*".equals(formatString[0])) {
                return format(toFormat, al, formatString);
            } else {
                if (al.getNumberOfAuthors() <= Integer.parseInt(formatString[0])) {
                    return format(toFormat, al, formatString);
                }
            }
        }
        return toFormat;
    }

    @Override
    public String format(String fieldText) {
        return format(fieldText, parameter);
    }

    public void setParameter(String parameter) {
        this.parameter = parameter;
    }

    public static Map<String, String> getNameFormatters(NameFormatterPreferences prefs) {
        Objects.requireNonNull(prefs);

        Map<String, String> result = new HashMap<>();

        List<String> names = prefs.getNameFormatterKey();
        List<String> formats = prefs.getNameFormatterValue();

        for (int i = 0; i < names.size(); i++) {
            if (i < formats.size()) {
                result.put(names.get(i), formats.get(i));
            } else {
                result.put(names.get(i), DEFAULT_FORMAT);
            }
        }

        return result;
    }
}
