package org.benf.cfr.reader.entities.attributes;

import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair;
import org.benf.cfr.reader.util.collections.ListFactory;
import org.benf.cfr.reader.util.bytestream.ByteData;

import java.util.List;

public interface TypeAnnotationTargetInfo {

    class TypeAnnotationParameterTarget implements TypeAnnotationTargetInfo {
        private final short type_parameter_index;

        TypeAnnotationParameterTarget(short type_parameter_index) {
            this.type_parameter_index = type_parameter_index;
        }

        static Pair<Long, TypeAnnotationTargetInfo> Read(ByteData raw, long offset) {
            short type_parameter_index = raw.getU1At(offset++);
            return Pair.<Long, TypeAnnotationTargetInfo>make(offset, new TypeAnnotationParameterTarget(type_parameter_index));
        }

        public short getIndex() {
            return type_parameter_index;
        }
    }

    class TypeAnnotationSupertypeTarget implements TypeAnnotationTargetInfo {
        private final int supertype_index;

        private TypeAnnotationSupertypeTarget(int supertype_index) {
            this.supertype_index = supertype_index;
        }

        static Pair<Long, TypeAnnotationTargetInfo> Read(ByteData raw, long offset) {
            int supertype_index = raw.getU2At(offset);
            offset += 2;
            return Pair.<Long, TypeAnnotationTargetInfo>make(offset, new TypeAnnotationSupertypeTarget(supertype_index));
        }

    }

    class TypeAnnotationParameterBoundTarget implements TypeAnnotationTargetInfo {
        private final short type_parameter_index;
        private final short bound_index;

        private TypeAnnotationParameterBoundTarget(short type_parameter_index, short bound_index) {
            this.type_parameter_index = type_parameter_index;
            this.bound_index = bound_index;
        }

        static Pair<Long, TypeAnnotationTargetInfo> Read(ByteData raw, long offset) {
            short type_parameter_index = raw.getU1At(offset++);
            short bound_index = raw.getU1At(offset++);
            return Pair.<Long, TypeAnnotationTargetInfo>make(offset, new TypeAnnotationParameterBoundTarget(type_parameter_index, bound_index));
        }

        public short getIndex() {
            return type_parameter_index;
        }
    }

    class TypeAnnotationEmptyTarget implements TypeAnnotationTargetInfo {
        private static TypeAnnotationTargetInfo INSTANCE = new TypeAnnotationEmptyTarget();
        static Pair<Long, TypeAnnotationTargetInfo> Read(ByteData raw, long offset) {
            return Pair.<Long, TypeAnnotationTargetInfo>make(offset, INSTANCE);
        }

        public static TypeAnnotationTargetInfo getInstance() {
            return INSTANCE;
        }
    }

    class TypeAnnotationFormalParameterTarget implements TypeAnnotationTargetInfo {
        private final short formal_parameter_index;

        private TypeAnnotationFormalParameterTarget(short formal_parameter_index) {
            this.formal_parameter_index = formal_parameter_index;
        }

        static Pair<Long, TypeAnnotationTargetInfo> Read(ByteData raw, long offset) {
            short formal_parameter_index = raw.getU1At(offset++);
            return Pair.<Long, TypeAnnotationTargetInfo>make(offset, new TypeAnnotationFormalParameterTarget(formal_parameter_index));
        }

        public int getIndex() {
            return formal_parameter_index;
        }
    }

    class TypeAnnotationThrowsTarget implements TypeAnnotationTargetInfo {
        private final int throws_type_index;

        private TypeAnnotationThrowsTarget(int throws_type_index) {
            this.throws_type_index = throws_type_index;
        }

        static Pair<Long, TypeAnnotationTargetInfo> Read(ByteData raw, long offset) {
            int throws_type_index = raw.getU2At(offset);
            offset += 2;
            return Pair.<Long, TypeAnnotationTargetInfo>make(offset, new TypeAnnotationThrowsTarget(throws_type_index));
        }

        public int getIndex() {
            return throws_type_index;
        }
    }

    class LocalVarTarget {
        private final int start;
        private final int length;
        private final int index;

        LocalVarTarget(int start, int length, int index) {
            this.start = start;
            this.length = length;
            this.index = index;
        }

        public boolean matches(int offset, int slot, int tolerance) {
            return offset >= start-tolerance && offset < start +length && slot == index;
        }
    }

    class TypeAnnotationLocalVarTarget implements TypeAnnotationTargetInfo {

        private final List<LocalVarTarget> targets;

        TypeAnnotationLocalVarTarget(List<LocalVarTarget> targets) {
            this.targets = targets;
        }

        public boolean matches(int offset, int slot, int tolerance) {
            for (LocalVarTarget tgt : targets) {
                if (tgt.matches(offset, slot, tolerance)) return true;
            }
            return false;
        }

        static Pair<Long, TypeAnnotationTargetInfo> Read(ByteData raw, long offset) {
            int count = raw.getU2At(offset);
            offset += 2;
            List<LocalVarTarget> targetList = ListFactory.newList();
            for (int x=0;x<count;++x) {
                int start = raw.getU2At(offset);
                offset += 2;
                int length = raw.getU2At(offset);
                offset += 2;
                int index = raw.getU2At(offset);
                offset += 2;
                targetList.add(new LocalVarTarget(start, length, index));
            }
            return Pair.<Long, TypeAnnotationTargetInfo>make(offset, new TypeAnnotationLocalVarTarget(targetList));
        }
    }

    class TypeAnnotationCatchTarget implements TypeAnnotationTargetInfo {
        private final int exception_table_index;

        private TypeAnnotationCatchTarget(int exception_table_index) {
            this.exception_table_index = exception_table_index;
        }

        static Pair<Long, TypeAnnotationTargetInfo> Read(ByteData raw, long offset) {
            int exception_table_index = raw.getU2At(offset);
            offset += 2;
            return Pair.<Long, TypeAnnotationTargetInfo>make(offset, new TypeAnnotationCatchTarget(exception_table_index));
        }

    }

    class TypeAnnotationOffsetTarget implements TypeAnnotationTargetInfo {
        private final int offset;

        private TypeAnnotationOffsetTarget(int offset) {
            this.offset = offset;
        }

        static Pair<Long, TypeAnnotationTargetInfo> Read(ByteData raw, long offset) {
            int offset_val = raw.getU2At(offset);
            offset += 2;
            return Pair.<Long, TypeAnnotationTargetInfo>make(offset, new TypeAnnotationOffsetTarget(offset_val));
        }
    }

    class TypeAnnotationTypeArgumentTarget implements TypeAnnotationTargetInfo {
        private final int offset;
        private final short type_argument_index;

        private TypeAnnotationTypeArgumentTarget(int offset, short type_argument_index) {
            this.offset = offset;
            this.type_argument_index = type_argument_index;
        }

        static Pair<Long, TypeAnnotationTargetInfo> Read(ByteData raw, long offset) {
            int offset_val = raw.getU2At(offset);
            offset += 2;
            short type_argument_index = raw.getU1At(offset++);
            return Pair.<Long, TypeAnnotationTargetInfo>make(offset, new TypeAnnotationTypeArgumentTarget(offset_val, type_argument_index));
        }
    }


}