package de.dhbwstuttgart.typeinference.unify.model;

import de.dhbwstuttgart.server.packet.dataContainers.ISerializableData;
import de.dhbwstuttgart.server.packet.dataContainers.KeyStorage;
import de.dhbwstuttgart.server.packet.dataContainers.serialized.SerialList;
import de.dhbwstuttgart.server.packet.dataContainers.serialized.SerialMap;
import de.dhbwstuttgart.server.packet.dataContainers.serialized.SerialValue;

import de.dhbwstuttgart.typeinference.unify.UnifyContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class FunInterfaceType extends ReferenceType implements ISerializableData {
    final List<UnifyType> intfArgTypes;
    final UnifyType intfReturnType;
    final List<String> generics;

    public FunInterfaceType(String name, TypeParams params, List<UnifyType> intfArgTypes, UnifyType intfReturnType, List<String> generics) {
        super(name, params);
        this.intfArgTypes = intfArgTypes;
        this.intfReturnType = intfReturnType;
        this.generics = generics;
    }

    public List<UnifyType> getFunctionalInterfaceTypeArguments(UnifyType t) {
        if (t instanceof FunNType) {
            var ret = new ArrayList<UnifyType>();
            if (t.getTypeParams().size() > 0) {
                ret.add(t.getTypeParams().get(t.getTypeParams().size() - 1));
                ret.addAll(Arrays.asList(t.getTypeParams().get()).subList(0, t.getTypeParams().size() - 1));
            }
            return ret;
        }

        var args = new ArrayList<UnifyType>();
        args.add(intfReturnType);
        args.addAll(intfArgTypes);

        // TODO There might be a better way of dealing with this
        var i = 0;
        for (var generic : generics) {
            for (var j = 0; j < args.size(); j++) {
                if (args.get(j).getName().equals(generic))
                    args.set(j, t.getTypeParams().get(i));
            }
            i += 1;
        }

        return args;
    }

    @Override
    public SerialMap toSerial(KeyStorage keyStorage) {
        var serializedWrapper = super.toSerial(keyStorage);
        SerialMap serialized = serializedWrapper.getMap("object");

        serialized.put("intfArgTypes", SerialList.fromMapped(intfArgTypes, u -> u.toSerial(keyStorage)));
        serialized.put("intfReturnType", intfReturnType.toSerial(keyStorage));
        serialized.put("generics", SerialList.fromMapped(generics, SerialValue::new));

        return serializedWrapper;
    }

    public static FunInterfaceType fromSerial(SerialMap data, UnifyContext context) {
        var name = data.getValue("name").getOf(String.class);
        var params = data.getList("params").assertListOfMaps().stream().map(
          paramData -> UnifyType.fromSerial(paramData, context)).toList();
        var intfArgTypes = data.getList("intfArgTypes").assertListOfMaps().stream().map(
          argTypeData -> UnifyType.fromSerial(argTypeData, context)).toList();
        var intfReturnType = UnifyType.fromSerial(data.getMap("intfReturnType"), context);
        var generics = data.getList("generics").assertListOfValues().stream().map(
          generic -> generic.getOf(String.class)).toList();

        return new FunInterfaceType(name, new TypeParams(params), intfArgTypes, intfReturnType, generics);
    }
}
