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

import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import net.ranides.assira.generic.LazyReference;
import net.ranides.assira.generic.Wrapper;
import net.ranides.assira.trace.ExceptionUtils;
import net.ranides.assira.reflection.*;
import net.ranides.assira.reflection.util.ReflectUtils;
import net.ranides.assira.collection.query.CQueryBuilder;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class RField implements IField {
    
    private final boolean declared;
    private final IContext context;
    private final IClass<?> parent;
    private final Field field;
    private final Supplier<MethodHandle> getter;
    private final Supplier<MethodHandle> setter;
    
    RField(boolean declared, IContext context, Field field) {
        ReflectUtils.access(field);
        this.context = context;
        this.parent = context.typeinfo(field.getDeclaringClass());
        this.field = field;
        this.getter = LazyReference.concurrent(() -> {
            try {
                return MethodHandles.lookup().unreflectGetter(field);
            } catch (IllegalAccessException cause) {
                throw ExceptionUtils.rethrow(cause);
            }
        });
        this.setter = LazyReference.concurrent(() -> {
            try {
                return MethodHandles.lookup().unreflectSetter(field);
            } catch (IllegalAccessException cause) {
                throw ExceptionUtils.rethrow(cause);
            }
        });
        this.declared = declared;
    }
    
    @Override
    public IContext context() {
        return context;
    }
    
    @Override
    public IFields collect() {
        return FElements.newFields(AHints.EMPTY, CQueryBuilder.of(this));
    }

    @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 | IllegalArgumentException cause) {
            throw ExceptionUtils.rethrow(cause);
        }
    }

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

    @Override
    public Object replace(Object that, Object value) {
        Object prv = get(that);
        set(that,value);
        return prv;
    }

    @Override
    public Wrapper<Object> bind(Object that) {
        return new FWrapper(that);
    }

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

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

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

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

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

    @Override
    public Set<IAttribute> attributes() {
        Set<IAttribute> attr = IAttribute.resolve(field);
        if(declared) {
            attr.add(IAttribute.DECLARED);
        }
        return attr;
    }

    @Override
    public IAnnotations annotations() {
        return FElements.newAnnotations(AHints.EMPTY, CQueryBuilder.fromArray(field.getAnnotations()));
    }
    
    @Override
    public <A extends Annotation> Optional<A> annotation(Class<A> type) {
        return annotations().first(type);
    }

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

    @Override
    public boolean equals(Object object) {
        if(!(object instanceof IField)) {
            return false;
        }
        IField that = (IField)object;
        return this.name().equals(that.name()) && this.type().equals(that.type()) && this.parent().equals(that.parent());
    }

    @Override
    public String toString() {
        return type() + " " + parent() + "." + name();
    }
    
    
    
    private final class FWrapper implements Wrapper<Object> {
        
        private final MethodHandle getter;
        private final MethodHandle setter;

        public FWrapper(Object that) {
            this.getter = getter().bindTo(that);
            this.setter = setter().bindTo(that);
        }

        @Override
        public Object get() {
            try {
                return getter.invoke();
            } catch (Throwable cause) { //NOPMD rethrow idiom
                throw ExceptionUtils.rethrow(cause);
            }
        }

        @Override
        public void set(Object value) {
            try {
                setter.invoke(value);
            } catch (Throwable cause) { //NOPMD rethrow idiom
                throw ExceptionUtils.rethrow(cause);
            }
        }

        @Override
        public IClass<?> type() {
            return RField.this.type();
        }
        
    }
    
}
