package net.sf.jabref.model.bibtexkeypattern;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.stream.Collectors;

/**
 * A small table, where an entry type is associated with a Bibtex key pattern (an
 * <code>ArrayList</code>). A parent BibtexKeyPattern can be set.
 */
public abstract class AbstractBibtexKeyPattern {

    protected List<String> defaultPattern = new ArrayList<>();

    protected Map<String, List<String>> data = new HashMap<>();

    public void addBibtexKeyPattern(String type, String pattern) {
        data.put(type, AbstractBibtexKeyPattern.split(pattern));
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("AbstractBibtexKeyPattern{");
        sb.append("defaultPattern=").append(defaultPattern);
        sb.append(", data=").append(data);
        sb.append('}');
        return sb.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if ((o == null) || (getClass() != o.getClass())) {
            return false;
        }
        AbstractBibtexKeyPattern that = (AbstractBibtexKeyPattern) o;
        return Objects.equals(defaultPattern, that.defaultPattern) && Objects.equals(data, that.data);
    }

    @Override
    public int hashCode() {
        return Objects.hash(defaultPattern, data);
    }

    /**
     * Remove a Bibtex key pattern from the BibtexKeyPattern.
     *
     * @param type a <code>String</code>
     */
    public void removeBibtexKeyPattern(String type) {
        if (data.containsKey(type)) {
            data.remove(type);
        }
    }

    /**
     * Gets an object for a desired key from this BibtexKeyPattern or one of it's
     * parents (in the case of DatabaseBibtexKeyPattern). This method first tries to obtain the object from this
     * BibtexKeyPattern via the <code>get</code> method of <code>Hashtable</code>.
     * If this fails, we try the default.<br />
     * If that fails, we try the parent.<br />
     * If that fails, we return the DEFAULT_LABELPATTERN<br />
     *
     * @param key a <code>String</code>
     * @return the list of Strings for the given key. First entry: the complete key
     */
    public List<String> getValue(String key) {
        List<String> result = data.get(key);
        //  Test to see if we found anything
        if (result == null) {
            // check default value
            result = getDefaultValue();
            if (result == null || result.isEmpty()) {
                // we are the "last" to ask
                // we don't have anything left
                return getLastLevelBibtexKeyPattern(key);
            }
        }
        return result;

    }

    /**
     * This method takes a string of the form [field1]spacer[field2]spacer[field3]...,
     * where the fields are the (required) fields of a BibTex entry. The string is split
     * into fields and spacers by recognizing the [ and ].
     *
     * @param bibtexKeyPattern a <code>String</code>
     * @return an <code>ArrayList</code> The first item of the list
     * is a string representation of the key pattern (the parameter),
     * the remaining items are the fields
     */
    public static List<String> split(String bibtexKeyPattern) {
        // A holder for fields of the entry to be used for the key
        List<String> fieldList = new ArrayList<>();

        // Before we do anything, we add the parameter to the ArrayLIst
        fieldList.add(bibtexKeyPattern);

        StringTokenizer tok = new StringTokenizer(bibtexKeyPattern, "[]", true);
        while (tok.hasMoreTokens()) {
            fieldList.add(tok.nextToken());
        }
        return fieldList;
    }

    /**
     * Checks whether this pattern is customized or the default value.
     */
    public final boolean isDefaultValue(String key) {
        return data.get(key) == null;
    }

    /**
     * This method is called "...Value" to be in line with the other methods
     *
     * @return null if not available.
     */
    public List<String> getDefaultValue() {
        return this.defaultPattern;
    }

    /**
     * Sets the DEFAULT PATTERN for this key pattern
     * @param bibtexKeyPattern the pattern to store
     */
    public void setDefaultValue(String bibtexKeyPattern) {
        Objects.requireNonNull(bibtexKeyPattern);
        this.defaultPattern = AbstractBibtexKeyPattern.split(bibtexKeyPattern);
    }

    public Set<String> getAllKeys() {
        return data.keySet();
    }

    public Map<String, List<String>> getPatterns() {
        return data.entrySet().stream().collect(
                Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    }

    public abstract List<String> getLastLevelBibtexKeyPattern(String key);
}
