package mindustry.annotations.remote;

import arc.struct.*;
import com.squareup.javapoet.*;
import mindustry.annotations.Annotations.*;
import mindustry.annotations.*;
import mindustry.annotations.util.*;
import mindustry.annotations.util.TypeIOResolver.*;

import javax.annotation.processing.*;
import javax.lang.model.element.*;
import java.util.*;


/** The annotation processor for generating remote method call code. */
@SupportedAnnotationTypes({
"mindustry.annotations.Annotations.Remote",
"mindustry.annotations.Annotations.TypeIOHandler"
})
public class RemoteProcess extends BaseProcessor{
    /** Maximum size of each event packet. */
    public static final int maxPacketSize = 8192;
    /** Warning on top of each autogenerated file. */
    public static final String autogenWarning = "Autogenerated file. Do not modify!\n";

    /** Name of class that handles reading and invoking packets on the server. */
    private static final String readServerName = "RemoteReadServer";
    /** Name of class that handles reading and invoking packets on the client. */
    private static final String readClientName = "RemoteReadClient";
    /** Simple class name of generated class name. */
    private static final String callLocation = "Call";

    //class serializers
    private ClassSerializer serializer;
    //all elements with the Remote annotation
    private Seq<Smethod> elements;
    //map of all classes to generate by name
    private HashMap<String, ClassEntry> classMap;
    //list of all method entries
    private Seq<MethodEntry> methods;
    //list of all method entries
    private Seq<ClassEntry> classes;

    {
        rounds = 2;
    }

    @Override
    public void process(RoundEnvironment roundEnv) throws Exception{
        //round 1: find all annotations, generate *writers*
        if(round == 1){
            //get serializers
            serializer = TypeIOResolver.resolve(this);
            //last method ID used
            int lastMethodID = 0;
            //find all elements with the Remote annotation
            elements = methods(Remote.class);
            //map of all classes to generate by name
            classMap = new HashMap<>();
            //list of all method entries
            methods = new Seq<>();
            //list of all method entries
            classes = new Seq<>();

            Seq<Smethod> orderedElements = elements.copy();
            orderedElements.sortComparing(Object::toString);

            //create methods
            for(Smethod element : orderedElements){
                Remote annotation = element.annotation(Remote.class);

                //check for static
                if(!element.is(Modifier.STATIC) || !element.is(Modifier.PUBLIC)){
                    err("All @Remote methods must be public and static: ", element);
                }

                //can't generate none methods
                if(annotation.targets() == Loc.none){
                    err("A @Remote method's targets() cannot be equal to 'none':", element);
                }

                //get and create class entry if needed
                if(!classMap.containsKey(callLocation)){
                    ClassEntry clas = new ClassEntry(callLocation);
                    classMap.put(callLocation, clas);
                    classes.add(clas);
                }

                ClassEntry entry = classMap.get(callLocation);

                //create and add entry
                MethodEntry method = new MethodEntry(entry.name, BaseProcessor.getMethodName(element.e), annotation.targets(), annotation.variants(),
                annotation.called(), annotation.unreliable(), annotation.forward(), lastMethodID++, element.e, annotation.priority());

                entry.methods.add(method);
                methods.add(method);
            }

            //create read/write generators
            RemoteWriteGenerator writegen = new RemoteWriteGenerator(serializer);

            //generate the methods to invoke (write)
            writegen.generateFor(classes, packageName);
        }else if(round == 2){ //round 2: generate all *readers*
            RemoteReadGenerator readgen = new RemoteReadGenerator(serializer);

            //generate server readers
            readgen.generateFor(methods.select(method -> method.where.isClient), readServerName, packageName, true);
            //generate client readers
            readgen.generateFor(methods.select(method -> method.where.isServer), readClientName, packageName, false);

            //create class for storing unique method hash
            TypeSpec.Builder hashBuilder = TypeSpec.classBuilder("MethodHash").addModifiers(Modifier.PUBLIC);
            hashBuilder.addJavadoc(autogenWarning);
            hashBuilder.addField(FieldSpec.builder(int.class, "HASH", Modifier.STATIC, Modifier.PUBLIC, Modifier.FINAL)
            .initializer("$1L", Arrays.hashCode(methods.map(m -> m.element).toArray())).build());

            //build and write resulting hash class
            TypeSpec spec = hashBuilder.build();
            JavaFile.builder(packageName, spec).build().writeTo(BaseProcessor.filer);
        }
    }
}
