package com.ankaibei.workFlow.common.core.cache;

import cn.hutool.core.collection.CollUtil;
import com.ankaibei.workFlow.common.core.exception.MapCacheAccessException;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;

/**
 * 树形字典数据内存缓存对象。
 *
 * @param <K> 字典表主键类型。
 * @param <V> 字典表对象类型。
 * @author Buzhanyi
 * @date 2022-09-25
 */
@Slf4j
public class MapTreeDictionaryCache<K, V> extends MapDictionaryCache<K, V> {

    /**
     * 树形数据存储对象。
     */
    private final Multimap<K, V> allTreeMap = LinkedHashMultimap.create();
    /**
     * 获取字典父主键数据的函数对象。
     */
    protected final Function<V, K> parentIdGetter;

    /**
     * 当前对象的构造器函数。
     *
     * @param idGetter       获取当前类主键字段值的函数对象。
     * @param parentIdGetter 获取当前类父主键字段值的函数对象。
     * @param <K>            字典主键类型。
     * @param <V>            字典对象类型
     * @return 实例化后的树形字典内存缓存对象。
     */
    public static <K, V> MapTreeDictionaryCache<K, V> create(Function<V, K> idGetter, Function<V, K> parentIdGetter) {
        if (idGetter == null) {
            throw new IllegalArgumentException("IdGetter can't be NULL.");
        }
        if (parentIdGetter == null) {
            throw new IllegalArgumentException("ParentIdGetter can't be NULL.");
        }
        return new MapTreeDictionaryCache<>(idGetter, parentIdGetter);
    }

    /**
     * 构造函数。
     *
     * @param idGetter       获取当前类主键字段值的函数对象。
     * @param parentIdGetter 获取当前类父主键字段值的函数对象。
     */
    public MapTreeDictionaryCache(Function<V, K> idGetter, Function<V, K> parentIdGetter) {
        super(idGetter);
        this.parentIdGetter = parentIdGetter;
    }

    @Override
    public void reload(List<V> dataList, boolean force) {
        if (!force && this.getCount() > 0) {
            return;
        }
        String exceptionMessage;
        try {
            if (lock.writeLock().tryLock(TIMEOUT, TimeUnit.MILLISECONDS)) {
                try {
                    dataMap.clear();
                    allTreeMap.clear();
                    dataList.forEach(data -> {
                        K id = idGetter.apply(data);
                        dataMap.put(id, data);
                        K parentId = parentIdGetter.apply(data);
                        allTreeMap.put(parentId, data);
                    });
                } finally {
                    lock.writeLock().unlock();
                }
            } else {
                throw new TimeoutException();
            }
        } catch (Exception e) {
            exceptionMessage = String.format(
                    "LOCK Operation of [MapDictionaryCache::getInList] encountered EXCEPTION [%s] for DICT.",
                    e.getClass().getSimpleName());
            log.warn(exceptionMessage);
            throw new MapCacheAccessException(exceptionMessage, e);
        }
    }

    @Override
    public List<V> getListByParentId(K parentId) {
        List<V> resultList = new LinkedList<>();
        String exceptionMessage;
        try {
            if (lock.readLock().tryLock(TIMEOUT, TimeUnit.MILLISECONDS)) {
                try {
                    Collection<V> children = allTreeMap.get(parentId);
                    if (CollUtil.isNotEmpty(children)) {
                        resultList.addAll(children);
                    }
                } finally {
                    lock.readLock().unlock();
                }
            } else {
                throw new TimeoutException();
            }
        } catch (Exception e) {
            exceptionMessage = String.format(
                    "LOCK Operation of [MapDictionaryCache::getInList] encountered EXCEPTION [%s] for DICT.",
                    e.getClass().getSimpleName());
            log.warn(exceptionMessage);
            throw new MapCacheAccessException(exceptionMessage, e);
        }
        return resultList;
    }

    @Override
    public void put(K id, V data) {
        String exceptionMessage;
        try {
            if (lock.writeLock().tryLock(TIMEOUT, TimeUnit.MILLISECONDS)) {
                try {
                    dataMap.put(id, data);
                    K parentId = parentIdGetter.apply(data);
                    allTreeMap.remove(parentId, data);
                    allTreeMap.put(parentId, data);
                } finally {
                    lock.writeLock().unlock();
                }
            } else {
                throw new TimeoutException();
            }
        } catch (Exception e) {
            exceptionMessage = String.format(
                    "LOCK Operation of [MapDictionaryCache::getInList] encountered EXCEPTION [%s] for DICT.",
                    e.getClass().getSimpleName());
            log.warn(exceptionMessage);
            throw new MapCacheAccessException(exceptionMessage, e);
        }
    }

    @Override
    public V invalidate(K id) {
        V v;
        String exceptionMessage;
        try {
            if (lock.writeLock().tryLock(TIMEOUT, TimeUnit.MILLISECONDS)) {
                try {
                    v = dataMap.remove(id);
                    if (v != null) {
                        K parentId = parentIdGetter.apply(v);
                        allTreeMap.remove(parentId, v);
                    }
                } finally {
                    lock.writeLock().unlock();
                }
            } else {
                throw new TimeoutException();
            }
        } catch (Exception e) {
            exceptionMessage = String.format(
                    "LOCK Operation of [MapDictionaryCache::getInList] encountered EXCEPTION [%s] for DICT.",
                    e.getClass().getSimpleName());
            log.warn(exceptionMessage);
            throw new MapCacheAccessException(exceptionMessage, e);
        }
        return v;
    }

    @Override
    public void invalidateSet(Set<K> keys) {
        String exceptionMessage;
        try {
            if (lock.writeLock().tryLock(TIMEOUT, TimeUnit.MILLISECONDS)) {
                try {
                    keys.forEach(id -> {
                        if (id != null) {
                            V data = dataMap.remove(id);
                            if (data != null) {
                                K parentId = parentIdGetter.apply(data);
                                allTreeMap.remove(parentId, data);
                            }
                        }
                    });
                } finally {
                    lock.writeLock().unlock();
                }
            } else {
                throw new TimeoutException();
            }
        } catch (Exception e) {
            exceptionMessage = String.format(
                    "LOCK Operation of [MapDictionaryCache::getInList] encountered EXCEPTION [%s] for DICT.",
                    e.getClass().getSimpleName());
            log.warn(exceptionMessage);
            throw new MapCacheAccessException(exceptionMessage, e);
        }
    }

    @Override
    public void invalidateAll() {
        String exceptionMessage;
        try {
            if (lock.writeLock().tryLock(TIMEOUT, TimeUnit.MILLISECONDS)) {
                try {
                    dataMap.clear();
                    allTreeMap.clear();
                } finally {
                    lock.writeLock().unlock();
                }
            } else {
                throw new TimeoutException();
            }
        } catch (Exception e) {
            exceptionMessage = String.format(
                    "LOCK Operation of [MapDictionaryCache::getInList] encountered EXCEPTION [%s] for DICT.",
                    e.getClass().getSimpleName());
            log.warn(exceptionMessage);
            throw new MapCacheAccessException(exceptionMessage, e);
        }
    }
}
