/*
 *   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.bausystem.features.attributescopy;

import lombok.experimental.UtilityClass;
import org.bukkit.block.data.BlockData;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@UtilityClass
public class AttributeUtils {

    private Map<Method, String> names = new HashMap<>();
    private Map<Class<?>, List<Method>> getters = new HashMap<>();
    private Map<Class<?>, Map<String, Method>> setters = new HashMap<>();

    private void generate(BlockData blockData) {
        Class<? extends BlockData> clazz = blockData.getClass();
        if (getters.containsKey(clazz) && setters.containsKey(clazz)) return;

        Map<String, List<Method>> methods = new HashMap<>();
        for (Method declaredMethod : clazz.getMethods()) {
            String s = declaredMethod.getName();
            if (s.startsWith("get") && declaredMethod.getParameterCount() == 0) {
                methods.computeIfAbsent(s.substring(3), aClass -> new ArrayList<>()).add(declaredMethod);
            } else if (s.startsWith("is") && declaredMethod.getParameterCount() == 0) {
                methods.computeIfAbsent(s.substring(2), aClass -> new ArrayList<>()).add(declaredMethod);
            } else if (s.startsWith("set") && declaredMethod.getParameterCount() == 1) {
                methods.computeIfAbsent(s.substring(3), aClass -> new ArrayList<>()).add(declaredMethod);
            }
        }
        for (Map.Entry<String, List<Method>> entry : methods.entrySet()) {
            if (entry.getValue().size() != 2) continue;
            for (Method method : entry.getValue()) {
                names.put(method, entry.getKey());
                if (method.getName().startsWith("is") || method.getName().startsWith("get")) {
                    getters.computeIfAbsent(clazz, aClass -> new ArrayList<>()).add(method);
                } else {
                    setters.computeIfAbsent(clazz, aClass -> new HashMap<>()).put(entry.getKey(), method);
                }
            }
        }
    }

    public void copy(BlockData blockData, List<String> attributes) {
        generate(blockData);

        getters.getOrDefault(blockData.getClass(), new ArrayList<>()).forEach(method -> {
            try {
                Object invoke = method.invoke(blockData);
                if (invoke != null) {
                    attributes.add("§8-§7 " + names.get(method) + "§8:§7 " + convert(invoke));
                }
            } catch (Exception e) {
                // ignore
            }
        });
    }

    public void paste(BlockData blockData, List<String> attributes) {
        generate(blockData);

        for (String attribute : attributes) {
            String[] split = attribute.split("§8:§7 ");
            if (split.length != 2) continue;
            String name = split[0].substring(6);
            String value = split[1];
            Method method = setters.getOrDefault(blockData.getClass(), new HashMap<>()).get(name);
            if (method == null) continue;
            try {
                method.invoke(blockData, convert(value, method.getParameterTypes()[0]));
            } catch (Exception e) {
                // ignore
            }
        }
    }

    private String convert(Object o) {
        if (o.getClass().isEnum()) {
            return ((Enum<?>) o).name();
        } else {
            return o.toString();
        }
    }

    private Object convert(String s, Class<?> type) {
        if (type.isEnum()) {
            return Enum.valueOf((Class<? extends Enum>) type, s);
        } else if (type == int.class || type == Integer.class) {
            return Integer.parseInt(s);
        } else if (type == double.class || type == Double.class) {
            return Double.parseDouble(s);
        } else if (type == float.class || type == Float.class) {
            return Float.parseFloat(s);
        } else if (type == long.class || type == Long.class) {
            return Long.parseLong(s);
        } else if (type == short.class || type == Short.class) {
            return Short.parseShort(s);
        } else if (type == byte.class || type == Byte.class) {
            return Byte.parseByte(s);
        } else if (type == boolean.class || type == Boolean.class) {
            return Boolean.parseBoolean(s);
        } else {
            return s;
        }
    }
}
