/*
 * Copyright (C) 2020 The zfoo Authors
 *
 * 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
 *
 * http://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 com.zfoo.storage.model.vo;

import com.zfoo.protocol.collection.CollectionUtils;
import com.zfoo.protocol.util.AssertionUtils;
import com.zfoo.protocol.util.IOUtils;
import com.zfoo.protocol.util.ReflectionUtils;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.storage.interpreter.ResourceInterpreter;
import org.springframework.lang.Nullable;

import java.io.InputStream;
import java.util.*;

/**
 * @author godotg
 * @version 4.0
 */
public class Storage<K, V> {

    private Class<V> clazz;

    // 当前配置表是否在当前项目中使用，没有被使用的会清楚data数据，以达到节省内存的目的
    private boolean recycle = true;

    private Map<K, V> dataMap = new HashMap<>();
    // 非唯一索引
    private Map<String, Map<Object, List<V>>> indexMap = new HashMap<>();
    // 唯一索引
    private Map<String, Map<Object, V>> uniqueIndexMap = new HashMap<>();

    private IdDef idDef;
    private Map<String, IndexDef> indexDefMap;

    public void init(InputStream inputStream, Class<?> resourceClazz, String suffix) {
        try {
            this.clazz = (Class<V>) resourceClazz;
            idDef = IdDef.valueOf(resourceClazz);
            indexDefMap = IndexDef.createResourceIndexes(resourceClazz);

            var list = ResourceInterpreter.read(inputStream, resourceClazz, suffix);

            dataMap.clear();
            indexMap.clear();
            uniqueIndexMap.clear();

            for (var object : list) {
                put((V) object);
            }
        } catch (Throwable e) {
            throw new RuntimeException(e.getMessage(), e);
        } finally {
            IOUtils.closeIO(inputStream);
        }
    }

    public void recycleStorage() {
        recycle = true;
        dataMap = null;
        indexMap = null;
        uniqueIndexMap = null;
        idDef = null;
        indexDefMap = null;
    }

    public boolean isRecycle() {
        return recycle;
    }

    public void setRecycle(boolean recycle) {
        this.recycle = recycle;
    }

    public Collection<V> getAll() {
        return Collections.unmodifiableCollection(dataMap.values());
    }

    public Map<K, V> getData() {
        return Collections.unmodifiableMap(dataMap);
    }

    public IdDef getIdDef() {
        return idDef;
    }

    public boolean contain(K key) {
        return dataMap.containsKey(key);
    }

    public V get(K id) {
        V result = dataMap.get(id);
        AssertionUtils.notNull(result, "The static resource represented as [id:{}] in the static resource [resource:{}] does not exist", id, clazz.getSimpleName());
        return result;
    }

    public List<V> getIndex(String indexName, Object key) {
        var indexValues = indexMap.get(indexName);
        AssertionUtils.notNull(indexValues, "The index of [indexName:{}] does not exist in the static resource [resource:{}]", indexName, clazz.getSimpleName());
        var values = indexValues.get(key);
        if (CollectionUtils.isEmpty(values)) {
            return Collections.emptyList();
        }
        return values;
    }

    @Nullable
    public V getUniqueIndex(String uniqueIndexName, Object key) {
        var indexValueMap = uniqueIndexMap.get(uniqueIndexName);
        AssertionUtils.notNull(indexValueMap, "There is no a unique index for [uniqueIndexName:{}] in the static resource [resource:{}]", uniqueIndexName, clazz.getSimpleName());
        var value = indexValueMap.get(key);
        return value;
    }


    private V put(V value) {
        var key = (K) ReflectionUtils.getField(idDef.getField(), value);

        if (key == null) {
            throw new RuntimeException("There is an item with an unconfigured id in the static resource");
        }

        if (dataMap.containsKey(key)) {
            throw new RuntimeException(StringUtils.format("Duplicate [id:{}] of static resource [resource:{}]", key, clazz.getSimpleName()));
        }

        // 添加资源
        var result = dataMap.put(key, value);

        // 添加索引
        for (var def : indexDefMap.values()) {
            // 使用field的名称作为索引的名称
            var indexKey = def.getField().getName();
            var indexValue = ReflectionUtils.getField(def.getField(), value);
            if (def.isUnique()) {// 唯一索引
                var index = uniqueIndexMap.computeIfAbsent(indexKey, k -> new HashMap<>());
                if (index.put(indexValue, value) != null) {
                    throw new RuntimeException(StringUtils.format("Duplicate unique index [index:{}][value:{}] of static resource [class:{}]", indexKey, indexValue, clazz.getName()));
                }
            } else {// 不是唯一索引
                var index = indexMap.computeIfAbsent(indexKey, k -> new HashMap<>());
                var list = index.computeIfAbsent(indexValue, k -> new ArrayList<V>());
                list.add(value);
            }
        }
        return result;
    }

    public int size() {
        return dataMap.size();
    }

}
