/*
 * @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.reflect.Type;
import java.util.*;

import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.generic.Ref;
import net.ranides.assira.reflection.*;
import net.ranides.assira.text.StrBuilder;

/**
 * Abstract base class for all IClass implementations.
 *
 * In general non-final methods try to return "neutral" result or throw exception.
 *
 * For example, if you create non-reflective IClass, then you don't need to implement
 * {@link #reflective()} method: it will throw meaningfull exception.
 *
 * Subclasses which do not case about context could use no-argument constructor.
 * If your subclass can store parameterized types, you should pass context to the
 * constructor, to enable correct generic resolution.
 *
 * @param <T> T
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public abstract class AClass<T> extends AElement implements IClass<T>, Serializable {

    private static final long serialVersionUID = 1L;
    
    private static final IAttributes ATTRS = IAttributes.of(IAttribute.FINAL, IAttribute.NATIVE, IAttribute.PUBLIC, IAttribute.SYMBOLIC).immutable();
    
    private static final Object[] EMPTY_ARGS = new Object[0];
    
    private final IContext context;
    
    protected AClass() {
        this(IContext.getDefault());
    }
    
    protected AClass(IContext context) {
        this.context = context;
    }
    
    @Override
    public IContext context() {
        return context;
    }
    
    @Override
    public List<IClass<?>> params() {
        return Collections.emptyList();
    }

    @Override
    public IFields fields() {
        return IFields.empty();
    }
    
    @Override
    public Ref<IField> field(String name) {
        return fields().require(IAttribute.ANY).require(name).first();
    }

    @Override
    public IConstructors<T> constructors() {
        return IConstructors.empty();
    }

    @Override
    public IMethods methods() {
        return IMethods.empty();
    }

    @Override
    public Ref<IMethod> method(String name) {
        return methods().require(IAttribute.ANY).require(name).first();
    }

    @Override
    public IClass<?> parent() {
        throw new IReflectiveException("IClass<NULL> has no parent");
    }

    @Override
    public IClasses parents() {
        return IClasses.empty();
    }

    @Override
    public IClasses interfaces() {
        return IClasses.empty();
    }

    @Override
    public IClass<?> outer() {
        throw new IReflectiveException("IClass<NULL> has no outer class");
    }

    @Override
    public IClasses inner() {
        return IClasses.empty();
    }

    @Override
    public final T construct() {
        return construct(EMPTY_ARGS);
    }

    @SuppressWarnings("unchecked")
    @Override
    public final T construct(Object... arguments) {
        return (T)constructors()
            .require(IAttribute.DECLARED)
            .accepts(arguments)
            .first()
            .orElseThrow(() -> new IReflectiveException("Constructor for type: " + name() + " not found"))
            .accessible()
            .call(arguments);
    }
    
    @Override
    public IClass<?> component() {
        if(!attributes().contains(IAttribute.ARRAY)) {
            throw new IReflectiveException(name() + " is not array");
        } else {
            return context.typeinfo(raw().getComponentType());
        }
    }

    @Override
    public Type reflective() {
        throw new IReflectiveException("IClass<NULL> is not reflective");
    }

    @Override
    public Class<T> raw() {
       throw new IReflectiveException("IClass<NULL> is not reflective");
    }

    @Override
    public final boolean isSuper(Class<?> type) {
        return isSuper(IClass.typeinfo(type));
    }

    @Override
    public final boolean isSubclass(Class<?> type) {
        return isSubclass(IClass.typeinfo(type));
    }

    @Override
    public final boolean isSuper(IClass<?> type) {
        return RCompareUtils.isSuper(this, type);
    }

    @Override
    public final boolean isSubclass(IClass<?> type) {
        return RCompareUtils.isSubclass(this, type);
    }

    @Override
    public final boolean isEquivalent(Class<?> type) {
        return isEquivalent(IClass.typeinfo(type));
    }
    
    @Override
    public final boolean isEquivalent(IClass<?> type) {
        return RCompareUtils.isEquivalent(this, type);
    }

    @Override
    public boolean isInstance(Object value) {
        return true;
    }

    @SuppressWarnings("unchecked")
    @Override
    public T cast(Object value) {
        if(!isInstance(value)) {
            throw new ClassCastException(value + " is not instance of " + name());
        }
        return (T)value;
    }

    @Override
    public final IClasses collect() {
        return FElements.newClasses(IHints.none(), CQuery.from().value(this));
    }

    @Override
    public String name() {
        return "$NULL";
    }

    @Override
    public String shortName() {
        return name();
    }

    @Override
    public String packageName() {
        String fqn = name();
        // "name" should not contain generic part, but it is safer to ignore it just in case
        int end = fqn.indexOf('<');
        if(end != -1) {
            fqn = fqn.substring(0, end);
        }
        // strip everything after last "." sepearator (it is class name)
        int sep = fqn.lastIndexOf('.');
        if(sep != -1) {
            return fqn.substring(0, sep);
        } else {
            // if there is no separators, then there is no package name
            return "";
        }
    }

    @Override
    public IAttributes attributes() {
        return ATTRS;
    }

    @Override
    public IAnnotations annotations() {
        return IAnnotations.empty();
    }

    @Override
    public final boolean isInterface() {
        return attribute(IAttribute.INTERFACE);
    }

    @Override
    public final boolean isAbstract() {
        return attribute(IAttribute.ABSTRACT);
    }

    @Override
    public final boolean isAnnotation() {
        return attribute(IAttribute.ANNOTATION);
    }

    @Override
    public final boolean isEnum() {
        return attribute(IAttribute.ENUM);
    }

    @Override
    public final boolean isParameterized() {
        return attribute(IAttribute.PARAMETERIZED);
    }

    @Override
    public final boolean isArray() {
        return attribute(IAttribute.ARRAY);
    }

    @Override
    public final boolean isPrimitiveArray() {
        return isArray() && component().isPrimitive();
    }

    @Override
    public final boolean isPrimitive() {
        return attribute(IAttribute.PRIMITIVE);
    }

    @Override
    public final boolean isNumber() {
        return attribute(IAttribute.NUMBER);
    }

    @Override
    public final boolean isInteger() {
        return attribute(IAttribute.INTEGER);
    }

    @Override
    public final boolean isBoxed() {
        return attribute(IAttribute.BOXED);
    }

    @Override
    public final boolean isVoid() {
        return attribute(IAttribute.VOID);
    }

    @Override
    public final boolean isBoolean() {
        return attribute(IAttribute.BOOLEAN);
    }

    @Override
    public final boolean isResolved() {
        return attribute(IAttribute.RESOLVED);
    }

    @Override
    public final int compareTo(IClass<?> object) {
        return RCompareUtils.compare(this, object);
    }

    @Override
    public final boolean equals(Object that) {
        return (that instanceof IClass<?>) && RCompareUtils.equals(this, (IClass<?>)that);
    }

    @Override
    public final int hashCode() {
        return RCompareUtils.hashcode(this);
    }
    
    @Override
    public final String toString() {
        List<IClass<?>> params = params();
        if(params.isEmpty()) {
            return name();
        }
        return new StrBuilder()
            .append(name())
            .open("<",">",", ")
            .append(params)
            .close()
            .toString();
    }

}
