package net.ranides.assira.reflection;

import net.ranides.assira.annotations.Meta;
import net.ranides.assira.collection.sets.MaskFactory;
import net.ranides.assira.collection.sets.MaskSet;
import net.ranides.assira.reflection.util.ClassTraits;

import java.lang.reflect.*;
import java.util.Objects;

import static net.ranides.assira.reflection.IAttribute.*;

/**
 * This is container dedicated to store IAttribute values
 *
 * Internaly it uses MaskFactory and MaskSet configured to use IAttribute#mask
 */
public class IAttributes {

    private static final int M_BRIDGE   = 0x0000_0040;

    private static final int M_VARARGS  = 0x0000_0080;

    private static final int ACCESS_ANY = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE;

    private static final MaskFactory<IAttribute> MAP = new MaskFactory<>(IAttribute.values(), a -> a.mask);

    /**
     * Empty and immutable set of attributes
     */
    public static final IAttributes EMPTY = new IAttributes(0,0).immutable();

    private int reflective;

    private final MaskSet<IAttribute> values;

    private IAttributes(int reflective, MaskSet<IAttribute> values) {
        this.reflective = reflective;
        this.values = values;
    }

    private IAttributes(int reflective, int mask) {
        this.reflective = reflective;
        this.values = MAP.collect(mask);
    }

    /**
     * Returns immutable copy of this collection
     * If collection is already immutable, returns "this".
     *
     * @return IAttributes
     */
    public IAttributes immutable() {
        return new CAttributes(this);
    }

    @Override
    public String toString() {
        return values.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof IAttributes)) {
            return false;
        }
        IAttributes that = (IAttributes) o;
        return reflective == that.reflective && values.equals(that.values);
    }

    @Override
    public int hashCode() {
        return Objects.hash(reflective, values);
    }

    /**
     * Returns true if collection contains specified attribute
     *
     * @param attr attr
     * @return boolean
     */
    @Meta.Alias("has")
    public boolean contains(IAttribute attr) {
        return values.contains(attr);
    }

    /**
     * Returns true if collection contains all specified attributes
     *
     * @param attrs attrs
     * @return boolean
     */
    @Meta.Alias("has")
    public boolean containsAll(IAttributes attrs) {
        return values.containsAll(attrs.values);
    }

    /**
     * Returns true if collection contains specified attribute
     *
     * @param attr attr
     * @return boolean
     */
    @Meta.Alias("contains")
    public boolean has(IAttribute attr) {
        return values.contains(attr);
    }

    /**
     * Returns true if collection contains all specified attributes
     *
     * @param attrs attrs
     * @return boolean
     */
    @Meta.Alias("containsAll")
    public boolean has(IAttributes attrs) {
        return values.containsAll(attrs.values);
    }

    /**
     * Inserts attribute to this collection.
     * If collection is immutable, does nothing (does not throw exception).
     *
     * @param attr attr
     * @return this
     */
    public IAttributes add(IAttribute attr) {
        values.add(attr);
        reflective |= attr.reflective;
        return this;
    }

    /**
     * Inserts all attributes to this collection.
     * If collection is immutable, does nothing (does not throw exception).
     *
     * @param attrs attrs
     * @return this
     */
    public IAttributes add(IAttributes attrs) {
        values.addAll(attrs.values);
        reflective |= attrs.reflective;
        return this;
    }

    /**
     * Returns attributes as bitmask compatible with {@link java.lang.reflect.Modifier}
     * Obviously returned mask does not contain non-standard attributes.
     *
     * @return int
     */
    public int reflective() {
        return reflective;
    }

    /**
     * Creates new mutable collection with provided attributes
     *
     * @param attrs attrs
     * @return IAttributes
     */
    public static IAttributes of(IAttribute... attrs) {
        int reflective = 0;
        for(IAttribute a : attrs) {
            reflective |= a.reflective;
        }
        return new IAttributes(reflective, MAP.collect(attrs));
    }

    /**
     * Creates new mutable collection with attributes deduced from field
     *
     * @param field field
     * @return IAttributes
     */
    public static IAttributes of(Field field) {
        int reflective = field.getModifiers();
        int mask = getModifiers(reflective);
        if(!(field.getGenericType() instanceof Class)) {
            mask |= PARAMETERIZED.mask;
        }
        return new IAttributes(reflective, mask);
    }

    /**
     * Creates new mutable collection with attributes deduced from method/constructor
     *
     * @param method method
     * @return IAttributes
     */
    public static IAttributes of(Executable method) {
        int reflective = method.getModifiers();
        int mask = getModifiers(reflective);
        if(method instanceof Method) {
            mask &= ~DECLARED.mask;
        }
        if( 0 != ((M_BRIDGE|M_VARARGS) & mask) ) {
            mask |= (0 != (mask & M_BRIDGE)) ? BRIDGE.mask : 0;
            mask |= (0 != (mask & M_VARARGS)) ? VARARGS.mask : 0;
            mask &= ~(M_BRIDGE|M_VARARGS);
        }
        if( 0 != method.getTypeParameters().length ) {
            mask |= PARAMETERIZED.mask;
        }
        return new IAttributes(reflective, mask);
    }

    /**
     * Creates new mutable collection with attributes deduced from Parameter
     *
     * @param param param
     * @return IAttributes
     */
    public static IAttributes of(Parameter param) {
        int reflective = param.getModifiers();
        int mask = getModifiers(reflective);
        if(!(param.getParameterizedType() instanceof Class)) {
            mask |= PARAMETERIZED.mask;
        }
        return new IAttributes(reflective, mask);
    }

    /**
     * Creates new mutable collection with attributes deduced from Class
     *
     * @param type type
     * @return IAttributes
     */
    public static IAttributes of(Class<?> type) {
        int reflective = type.getModifiers();
        int mask = getModifiers(reflective);
        if( 0 != type.getTypeParameters().length) {
            mask |= PARAMETERIZED.mask;
        } else {
            mask |= RESOLVED.mask;
        }
        if( ClassTraits.isBoxed(type)) {
            mask |= BOXED.mask;
        }
        if( ClassTraits.isPrimitive(type)) {
            mask |= PRIMITIVE.mask;
        }
        if( ClassTraits.isNumber(type)) {
            mask |= NUMBER.mask;
        }
        if( ClassTraits.isInteger(type)) {
            mask |= INTEGER.mask;
        }
        if(type.isArray()) {
            mask |= ARRAY.mask;
        }
        if(ClassTraits.isVoid(type)) {
            mask |= VOID.mask;
        }
        if(ClassTraits.isBool(type)) {
            mask |= BOOLEAN.mask;
        }
        return new IAttributes(reflective, mask);
    }

    private static int getModifiers(int mask) {
        mask |= DECLARED.mask;
        if( 0 == (mask & ACCESS_ANY)) {
            mask |= PACKAGE.mask;
        }
        return mask;
    }


    private static class CAttributes extends IAttributes {

        public CAttributes(IAttributes src) {
            super(src.reflective, MAP.clone(src.values));
        }

        @Override
        public IAttributes add(IAttribute attr) {
            return this;
        }

        @Override
        public IAttributes add(IAttributes attrs) {
            return this;
        }

        public IAttributes immutable() {
            return this;
        }

    }

}
