/*
 * Copyright (C) 2018-2021 Niels Basjes
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.basjes.collections.prefixmap;

import nl.basjes.collections.PrefixMap;

import java.io.Serializable;
import java.util.*;

/**
 * The StringPrefixMap is an implementation of PrefixMap where the assumption is that the
 * stored prefixes can be any character in a String.
 *
 * @param <V> The type of the value that is to be stored.
 */
public class StringPrefixMap<V> implements PrefixMap<V>, Serializable {
    private final Boolean caseSensitive;
    private final PrefixTrie<V> prefixTrie;
    private final TreeMap<String, V> allPrefixes;

    PrefixTrie<V> createTrie(boolean newCaseSensitive) {
        return new StringPrefixTrie<>(newCaseSensitive);
    }

    public StringPrefixMap(boolean caseSensitive) {
        this.caseSensitive = caseSensitive; // Only needed for serialization.
        prefixTrie = createTrie(caseSensitive);
        allPrefixes = new TreeMap<>();
    }

    @Override
    public boolean containsPrefix(String prefix) {
        return prefixTrie.containsPrefix(prefix);
    }

    @Override
    public boolean containsPrefix(char[] prefix) {
        return prefixTrie.containsPrefix(prefix);
    }

    private String storedKey(String key) {
        if (prefixTrie.caseSensitive()) {
            return key;
        } else {
            return key.toLowerCase();
        }
    }

    @Override
    public V put(String prefix, V value) {
        if (prefix == null) {
            throw new NullPointerException("The prefix may not be null");
        }
        if (value == null) {
            throw new NullPointerException("The value may not be null");
        }

        V previousValue = prefixTrie.add(prefix, value);
        if (previousValue == null) {
            allPrefixes.put(storedKey(prefix), value);
        }
        return previousValue;
    }

    @Override
    public int size() {
        return allPrefixes.size();
    }

    @Override
    public void clear() {
        prefixTrie.clear();
        allPrefixes.clear();
    }

    @Override
    public V remove(String prefix) {
        if (prefix == null) {
            throw new NullPointerException("The prefix may not be null");
        }
        V oldValue = prefixTrie.remove(prefix);
        if (oldValue != null) {
            allPrefixes.remove(storedKey(prefix));
        }
        return oldValue;
    }

    @Override
    public V get(String prefix) {
        return prefixTrie.get(prefix);
    }

    @Override
    public V getShortestMatch(String input) {
        return prefixTrie.getShortestMatch(input);
    }

    @Override
    public V getLongestMatch(String input) {
        return prefixTrie.getLongestMatch(input);
    }

    @Override
    public Iterator<V> getAllMatches(String input) {
        return prefixTrie.getAllMatches(input);
    }

    @Override
    public V get(char[] prefix) {
        return prefixTrie.get(prefix);
    }

    @Override
    public V getShortestMatch(char[] input) {
        return prefixTrie.getShortestMatch(input);
    }

    @Override
    public V getLongestMatch(char[] input) {
        return prefixTrie.getLongestMatch(input);
    }

    @Override
    public Iterator<V> getAllMatches(char[] input) {
        return prefixTrie.getAllMatches(input);
    }

    @Override
    public Set<Map.Entry<String, V>> entrySet() {
        return allPrefixes.entrySet();
    }

    @Override
    public boolean containsKey(Object key) {
        return allPrefixes.containsKey(storedKey((String) key));
    }

    @Override
    public boolean containsValue(Object value) {
        return allPrefixes.containsValue(value);
    }

    @Override
    public Set<String> keySet() {
        return allPrefixes.keySet();
    }

    @Override
    public Collection<V> values() {
        return allPrefixes.values();
    }
}
