/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2025 SteamWar.de-Serverteam
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   GNU Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package de.steamwar.sql;

import org.yaml.snakeyaml.Yaml;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

final class YMLWrapper<M, W> {

    final Function<String, M> materialMapper;
    final Function<String, W> winconditionMapper;

    private final boolean canLoad;
    private final Map<String, Object> document;

    YMLWrapper(File file, Function<String, M> materialMapper, Function<String, W> winconditionMapper) {
        this.materialMapper = materialMapper;
        this.winconditionMapper = winconditionMapper;

        Yaml yaml = new Yaml();
        Map<String, Object> document = Collections.emptyMap();
        boolean canLoad = false;
        if (file != null && file.exists() && file.isFile()) {
            try {
                document = yaml.load(new FileReader(file));
                canLoad = true;
            } catch (IOException e) {
                // Ignore
            }
        }
        this.document = document;
        this.canLoad = canLoad;
    }

    private YMLWrapper(boolean canLoad, Map<String, Object> document, Function<String, M> materialMapper, Function<String, W> winconditionMapper) {
        this.materialMapper = materialMapper;
        this.winconditionMapper = winconditionMapper;
        this.canLoad = canLoad;
        this.document = document;
    }

    public boolean canLoad() {
        return canLoad;
    }

    public YMLWrapper<M, W> with(String path) {
        if (document.containsKey(path)) {
            Object value = document.get(path);
            if (value instanceof Map) {
                return new YMLWrapper<>(true, (Map) value, materialMapper, winconditionMapper);
            }
        }
        return new YMLWrapper<>(false, Collections.emptyMap(), materialMapper, winconditionMapper);
    }

    public <T> T get(String path, T defaultValue, Function<Object, T> mapper) {
        Object value = this.document.get(path);
        if (value == null) return defaultValue;
        try {
            T mapped = mapper.apply(value);
            if (mapped != null) return mapped;
        } catch (ClassCastException e) {
            // Ignore exception
        }
        return defaultValue;
    }

    public String getString(String path, String defaultValue) {
        return get(path, defaultValue, Objects::toString);
    }

    public int getInt(String path, int defaultValue) {
        return get(path, defaultValue, Integer.class::cast);
    }

    public double getDouble(String path, double defaultValue) {
        return get(path, defaultValue, o -> {
            if (o instanceof Integer) {
                return (Double) (double) (Integer) o;
            } else if (o instanceof Double) {
                return (Double) o;
            } else {
                throw new ClassCastException(o + " is not a double or integer");
            }
        });
    }

    public boolean getBoolean(String path, boolean defaultValue) {
        return get(path, defaultValue, Boolean.class::cast);
    }

    public SchematicType getSchematicType(String path, String defaultValue) {
        String schematicType = getString(path, defaultValue);
        return SchematicType.fromDB(schematicType);
    }

    public M getMaterial(String path, String defaultValue) {
        return materialMapper.apply(getString(path, defaultValue).toUpperCase());
    }

    public <T> List<T> get(String path, Function<Object, List<T>> mapper) {
        Object value = this.document.get(path);
        if (value == null) return Collections.emptyList();
        try {
            return Collections.unmodifiableList(mapper.apply(value));
        } catch (ClassCastException e) {
            return Collections.emptyList();
        }
    }

    public List<String> getStringList(String path) {
        return get(path, o -> (List<String>) o);
    }

    public List<Integer> getIntList(String path) {
        return get(path, o -> (List<Integer>) o);
    }

    public List<SchematicType> getSchematicTypeList(String path) {
        List<String> list = getStringList(path);
        if (list.isEmpty()) {
            return Collections.emptyList();
        } else {
            return Collections.unmodifiableList(list.stream().map(SchematicType::fromDB).filter(Objects::nonNull).collect(Collectors.toList()));
        }
    }

    public List<M> getMaterialList(String path) {
        List<String> list = getStringList(path);
        if (list.isEmpty()) {
            return Collections.emptyList();
        } else {
            return Collections.unmodifiableList(list.stream().map(String::toUpperCase).map(materialMapper).collect(Collectors.toList()));
        }
    }

    public <K, V> Map<K, V> getMap(String path, Function<String, K> keyFunction, Function<String, V> valueFunction) {
        Object data = this.document.get(path);
        if (data instanceof Map) {
            Map<K, V> result = new HashMap<>();
            ((Map<String, String>) data).forEach((keyString, valueString) -> {
                K key = keyFunction.apply(keyString.toUpperCase());
                V value = valueFunction.apply(valueString.toUpperCase());
                if (key == null || value == null) return;
                result.put(key, value);
            });
            return Collections.unmodifiableMap(result);
        } else {
            return Collections.emptyMap();
        }
    }

    public List<Map<?, ?>> getMapList(String path) {
        Object value = this.document.get(path);
        if (value instanceof List) {
            return Collections.unmodifiableList((List<Map<?, ?>>) value);
        } else {
            return Collections.emptyList();
        }
    }
}
