package org.keycloak.models.map.common;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Represents a field in an entity with appropriate accessors.
 * 
 * @author hmlnarik
 * @param <E>
 */
public interface EntityField<E> {

    /**
     * Returns name of this field with no spaces where each word starts with a capital letter.
     * @return
     */
    String getName();
    /*
     * Returns name of this field in camel case with first word starting with lower case letter and
     * further words starting with a capital letter.
     * @return
     */
    String getNameCamelCase();
    /**
     * Returns name of this field in lowercase with words separated by a dash ({@code -}).
     * @return
     */
    String getNameDashed();
    /**
     * Returns the value of this field.
     *
     * @param e Entity
     * @return Value of the field
     */
    Object get(E e);

    /**
     * Sets the value of this field. Does nothing by default. If you want to have a field set, override this method.
     * @param <T>
     * @param e Entity
     * @param value Value of the field
     */
    default <T> void set(E e, T value) {};

    /**
     * Adds an element to the collection stored in this field.
     * @param e Entity
     * @param value Value to be added to the collection
     * @throws ClassCastException If this field is not a collection.
     * @throws UnsupportedOperationException If this collection type is not yet known.
     */
    default <T> void collectionAdd(E e, T value) {
        @SuppressWarnings("unchecked")
        Collection<T> c = (Collection<T>) get(e);
        if (c != null) {
            c.add(value);
        } else {
            if (getFieldClass().equals(List.class)) {
                c = Collections.singletonList(value);
            } else if (getFieldClass().equals(Set.class)) {
                c = Collections.singleton(value);
            } else {
                throw new UnsupportedOperationException("Unsupported collection type."); // in case we add e.g. java.util.Queue in future
            }
            set(e, c);
        }
    }
    /**
     * Removes an element from the collection stored in this field.
     * @param e Entity
     * @param value Value to be added to the collection
     * @return Defined by the underlying field. Preferrably it should return deleted object, but it can return
     *    {@code true / false} indication of removal, or just {@code null}.
     * @throws ClassCastException If this field is not a collection.
     */
    default <T> Object collectionRemove(E e, T value) {
        Collection<?> c = (Collection<?>) get(e);
        return c == null ? null : c.remove(value);
    }

    /**
     * Retrieves a value from the map stored in this field.
     * @param e Entity
     * @param key Requested key
     * @return Object mapped to this key
     * @throws ClassCastException If this field is not a map.
     */
    default <K> Object mapGet(E e, K key) {
        @SuppressWarnings("unchecked")
        Map<K, ?> m = (Map<K, ?>) get(e);
        return m == null ? null : m.get(key);
    }
    /**
     * Adds a mapping to the map stored in this field.
     * @param e Entity
     * @param key Key to map
     * @param value Mapped value
     * @throws ClassCastException If this field is not a map.
     */
    default <K, T> void mapPut(E e, K key, T value) {
        @SuppressWarnings("unchecked")
        Map<K, T> m = (Map<K, T>) get(e);
        if (m != null) {
            m.put(key, value);
        } else {
            set(e, Collections.singletonMap(key, value));
        }
    }
    /**
     * Removes a mapping from the map stored in this field.
     * @param e Entity
     * @param key Key to remove
     * @return Object mapped to this key
     * @throws ClassCastException If this field is not a map.
     */
    default <K> Object mapRemove(E e, K key) {
        @SuppressWarnings("unchecked")
        Map<K, ?> m = (Map<K, ?>) get(e);
        if (m != null) {
            return m.remove(key);
        }
        return null;
    }

    /**
     * @return Returns the most specific type of this field.
     */
    default Class<?> getFieldClass() { return Object.class; }

    /**
     * @return If this field is a collection, returns type of its elements; otherwise returns {@code Void} class.
     */
    default Class<?> getCollectionElementClass() { return Void.class; }

    /**
     * @return If this field is a map, returns type of its keys; otherwise returns {@code Void} class.
     */
    default Class<?> getMapKeyClass() { return Void.class; }

    /**
     * @return If this field is a map, returns type of its values; otherwise returns {@code Void} class.
     */
    default Class<?> getMapValueClass() { return Void.class; }
}
