/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.reflection.impl;

import java.io.Serializable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;

import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.generic.LazySupplier;
import net.ranides.assira.generic.LazySuppliers;
import net.ranides.assira.generic.SerializationUtils;
import net.ranides.assira.trace.ExceptionUtils;
import net.ranides.assira.reflection.*;
import net.ranides.assira.reflection.util.ReflectUtils;

/**
 * Reflective implementation of IField.
 *
 * Information about type parameters defined by outer/parent classes are stored inside context.
 *
 * Please note, that the same field can be represented by 2 instances:
 * one if it is retrieved as "declared" from class, second one if it is retrieved as "inherited".
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public class RField extends AField implements IField, Serializable {
    
    private final boolean declared;
    private final IClass<?> parent;
    private final Field field;
    private final LazySupplier<MethodHandle> getter;
    private final LazySupplier<MethodHandle> setter;
    
    RField(boolean declared, IContext context, Field field) {
        super(context);
        this.parent = context.typeinfo(field.getDeclaringClass());
        this.field = field;
        this.getter = LazySuppliers.concurrent(() -> {
            try {
                ReflectUtils.access(field);
                return MethodHandles.lookup().unreflectGetter(field);
            } catch (IllegalAccessException cause) {
                throw new IReflectiveException.AbnormalException(cause);
            }
        });
        this.setter = LazySuppliers.concurrent(() -> {
            try {
                ReflectUtils.access(field);
                return MethodHandles.lookup().unreflectSetter(field);
            } catch (IllegalAccessException cause) {
                throw new IReflectiveException.AbnormalException(cause);
            }
        });
        this.declared = declared;
    }

    @Override
    public IClass<?> type() {
        IContext c = context();
        return FClass.newClass(c, field.getGenericType(), field.getAnnotatedType());
    }

    @Override
    public Object get(Object that) {
        try {
            return field.get(that);
        } catch (ReflectiveOperationException cause) {
            throw new IReflectiveException(cause);
        } catch (IllegalArgumentException cause) {
            throw ExceptionUtils.rethrow(cause);
        }
    }

    @Override
    public void set(Object that, Object value) {
        try {
            field.set(that, value);
        } catch (ReflectiveOperationException cause) {
            throw new IReflectiveException(cause);
        } catch (IllegalArgumentException cause) {
            throw ExceptionUtils.rethrow(cause);
        }
    }

    @Override
    public Field reflective() {
        return field;
    }

    @Override
    public IField accessible() {
        ReflectUtils.access(field);
        return this;
    }

    @Override
    public boolean isAccessible() {
        return ReflectUtils.isAccessible(field);
    }

    @Override
    public IField rewritable() {
        ReflectUtils.rewritable(field);
        return this;
    }

    @Override
    public MethodHandle getter() {
        return getter.compute();
    }

    @Override
    public MethodHandle setter() {
        return setter.compute();
    }

    @Override
    public String name() {
        return field.getName();
    }

    @Override
    public IClass<?> parent() {
        return parent;
    }

    @Override
    public IAttributes attributes() {
        IAttributes attr = IAttributes.of(field);
        if(declared) {
            attr.add(IAttribute.DECLARED);
        }
        return attr;
    }

    @Override
    public IAnnotations annotations() {
        return FElements.newAnnotations(IHints.none(), CQuery.from().array(field.getAnnotations()));
    }

    @Override
    public int hashCode() {
        return field.hashCode();
    }

    protected Object writeReplace() {
        final IContext cContext = super.context();
        final boolean cDeclared = this.declared;
        final Field cField = this.field;
        return SerializationUtils.proxy(() -> FField.newField(cDeclared, cContext, cField));
    }

}
