package ru.simplex_software.wicket_springdata.kryo;

import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.springframework.data.domain.Persistable;

import java.io.Serializable;
import java.util.AbstractMap;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Модель отображения.
 *
 * @param <K> тип ключа, сохраняемый в БД.
 * @param <V> тип значения.
 */
@Deprecated
public class KryoMapModel<K extends Persistable<? extends Serializable>, V extends Serializable> implements IModel<Map<K, V>> {

    private Map<IModel<K>, IModel<V>> modelMap;

    /**
     * Загружена ли модель.
     */
    private boolean attached;

    /**
     * Создание новой модели отображеия.
     *
     * @param map исходное отображение.
     */
    public KryoMapModel(Map<K, V> map) {
        this.modelMap = wrap(map);
        attached = true;
    }

    /**
     * Получение значения из модели.
     */
    @Override
    public Map<K, V> getObject() {
        checkLoad();
        return unwrap(modelMap);
    }

    /**
     * Открепление модели.
     */
    @Override
    public void detach() {
        modelMap.keySet().forEach(IModel::detach);
        modelMap.values().forEach(IModel::detach);
        attached = false;
    }

    /**
     * Получение количества элементов.
     */
    public int size() {
        return modelMap.size();
    }

    /**
     * Добавление ключа и значения.
     *
     * @param key   ключ.
     * @param value значение.
     */
    public void put(K key, V value) {
        put(new KryoModel<>(key), Model.of(value));
    }

    /**
     * Добавление моделей ключа и значения.
     *
     * @param key   модель ключа.
     * @param value модель значения.
     */
    public void put(IModel<K> key, IModel<V> value) {
        checkLoad();
        modelMap.put(key, value);
    }

    /**
     * Получение значения по модели ключа.
     *
     * @param keyModel модель ключа.
     * @return значение.
     */
    public V get(IModel<K> keyModel) {
        checkLoad();
        return modelMap.get(keyModel).getObject();
    }

    /**
     * Удаление ключа и значения по модели ключа.
     *
     * @param keyModel модель ключа.
     */
    public void remove(IModel<K> keyModel) {
        modelMap.remove(keyModel);
    }

    /**
     * Получение итератор ключей.
     */
    public Iterator<IModel<K>> getKeysIterator() {
        checkLoad();
        return modelMap.keySet().iterator();
    }

    /**
     * Получение итератора ключей после сортировки.
     *
     * @param comparator компаратор для сортировки.
     * @return итератор ключей.
     */
    public Iterator<IModel<K>> getSortedModelKeysIterator(Comparator<Map.Entry<K, V>> comparator) {
        checkLoad();
        Comparator<Map.Entry<IModel<K>, IModel<V>>> mapComparator = (me1, me2) -> {
            AbstractMap.SimpleEntry<K, V> e1 = new AbstractMap.SimpleEntry<>(me1.getKey().getObject(), me1.getValue().getObject());
            AbstractMap.SimpleEntry<K, V> e2 = new AbstractMap.SimpleEntry<>(me2.getKey().getObject(), me2.getValue().getObject());
            return comparator.compare(e1, e2);
        };
        return modelMap.entrySet().stream()
                .sorted(mapComparator)
                .map(Map.Entry::getKey)
                .iterator();
    }

    /**
     * Проверка загрузки и загрузка при необходимости.
     */
    private void checkLoad() {
        if (!attached) {
            modelMap.keySet().forEach(model -> ((KryoModel<K>) model).load());
        }
        attached = true;
    }

    /**
     * Оборачивание ключей и значений отображения в модели.
     *
     * @param map отображение.
     * @return отображение моделей.
     */
    private Map<IModel<K>, IModel<V>> wrap(Map<K, V> map) {
        Map<IModel<K>, IModel<V>> modelMap = new LinkedHashMap<>();
        map.forEach((key, value) -> modelMap.put(new KryoModel<>(key), Model.of(value)));
        return modelMap;
    }

    /**
     * Получение отображения с ключами и значениями из моделей.
     *
     * @param modelMap отображение моделей.
     * @return отображение ключей и значений.
     */
    private Map<K, V> unwrap(Map<IModel<K>, IModel<V>> modelMap) {
        return modelMap.entrySet().stream()
                .collect(Collectors.toMap(
                        entry -> entry.getKey().getObject(),
                        entry -> entry.getValue().getObject()));
    }
}
