package com.xxx.amp.magenta.livecodebench;

import org.json.JSONArray;
import org.json.JSONException;

import java.lang.reflect.Array;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

/**
 * Converter for test input/output literal from JSON to Java Objects.
 *
 * @author Alexey Kharlamov <aih1013@xxx.com>
 */
@SuppressWarnings("rawtypes")
public class LiteralParser {

    public String parseString(Object input) {
        if (input instanceof String str) {
            return str;
        }
        throw new LiteralSyntaxException("Invalid string literal `%s`".formatted(input));
    }

    public int parseInt(Object input) {
        if (input instanceof Integer i) {
            return i;
        } else if (input instanceof String s) {
            try {
                return Integer.parseInt(s);
            } catch (NumberFormatException e) {
                // Fall-through to LiteralSyntaxException
            }
        }
        throw new LiteralSyntaxException("Invalid integer literal `%s`".formatted(input));
    }

    public List<Object> parseList(Type elemType, Object input) {
        List<Object> list;
        if (input instanceof JSONArray json) {
            list = json.toList();
        } else if (input instanceof List l) {
            list = l;
        } else {
            throw new LiteralSyntaxException("Invalid list literal `%s`".formatted(input));
        }
        return list
                .stream()
                .map(x -> convertJsonObject(elemType, x))
                .toList();
    }

    private Object parseArray(Class<?> componentType, Object input) throws LiteralSyntaxException {
        if (input instanceof JSONArray json) {
            Object result = Array.newInstance(componentType, json.length());
            int i = 0;
            for (Object o : json) {
                Array.set(result, i++, convertJsonObject(componentType, o));
            }
            return result;
        }
        throw new LiteralSyntaxException("Invalid list literal `%s`".formatted(input));
    }

    public Object parse(Type type, String s) throws LiteralSyntaxException {
        try {
            JSONArray obj = new JSONArray("[" + s + "]");
            return convertJsonObject(type, obj.get(0));
        } catch (JSONException e) {
            throw new LiteralSyntaxException("Failed to eval literal `%s`".formatted(s));
        }
    }

    private boolean parseBoolean(Object obj) throws LiteralSyntaxException {
        if (obj instanceof Boolean b) {
            return b;
        }
        throw new LiteralSyntaxException("Failed to parse %s as boolean literal".formatted(obj));
    }

    private Object convertJsonObject(Type targetType, Object obj) throws LiteralSyntaxException {
        if (targetType == String.class) {
            return parseString(obj);
        } else if (targetType == Integer.class || targetType == int.class) {
            return parseInt(obj);
        } else if (targetType == Long.class || targetType == long.class) {
            return parseLong(obj);
        } else if (targetType == Boolean.class || targetType == boolean.class) {
            return parseBoolean(obj);
        } else if (targetType instanceof ParameterizedType paramType) {
            if (paramType.getRawType() == List.class) {
                return parseList(paramType.getActualTypeArguments()[0], obj);
            }
        } else if (targetType instanceof Class clz && clz.isArray()) {
            return parseArray(clz.getComponentType(), obj);
        }
        throw new RuntimeException("Unsupported argument type %s".formatted(targetType.getTypeName()));
    }

    private long parseLong(Object obj) {
        if (obj instanceof Long l) {
            return l;
        }
        if (obj instanceof Integer i) {
            return i;
        }
        if (obj instanceof String s) {
            try {
                return Long.parseLong(s);
            } catch (NumberFormatException e) {
                // Fall-through to LiteralSyntaxException
            }
        }
        throw new LiteralSyntaxException("Invalid integer literal `%s`".formatted(obj));
    }
}
