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

import net.ranides.assira.annotations.Meta;
import net.ranides.assira.generic.Ref;
import net.ranides.assira.reflection.impl.FClass;
import net.ranides.assira.reflection.impl.NClass;
import net.ranides.assira.reflection.impl.SClass;
import net.ranides.assira.reflection.util.ClassUtils;

import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;

/**
 * This interface is extension of standard "Class" interface.
 * It allows inspection in a bit more fluent way and it supports generics.
 *
 * It can represent:
 *  - ordinary raw "Class"
 *  - generic class with resolved parameters
 *  - TypeToken
 *  - wildcard type
 *  - symbolic class
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @param <T> T
 */
@SuppressWarnings("PMD.TooManyDifferentMethods")
public interface IClass<T> extends IElement, Comparable<IClass<?>> {

    /**
     * Constant representing "empty value"
     */
    @SuppressWarnings("rawtypes")
    IClass NULL = NClass.getInstance();

    /**
     * Constant representing Object.class
     */
    IClass<Object> OBJECT = typeinfo(Object.class);

    /**
     * Constant representing primitive void.class
     */
    IClass<Void> VOID = typeinfo(void.class);

    /**
     * Constant representing primitive byte.class
     */
    IClass<Byte> BYTE = typeinfo(byte.class);

    /**
     * Constant representing primitive short.class
     */
    IClass<Short> SHORT = typeinfo(short.class);

    /**
     * Constant representing primitive int.class
     */
    IClass<Integer> INT = typeinfo(int.class);

    /**
     * Constant representing primitive long.class
     */
    IClass<Long> LONG = typeinfo(long.class);

    /**
     * Constant representing primitive float.class
     */
    IClass<Float> FLOAT = typeinfo(float.class);

    /**
     * Constant representing primitive double.class
     */
    IClass<Double> DOUBLE = typeinfo(double.class);

    /**
     * Constant representing primitive char.class
     */
    IClass<Character> CHAR = typeinfo(char.class);

    /**
     * Constant representing primitive boolean.class
     */
    IClass<Boolean> BOOL = typeinfo(boolean.class);

    /**
     * Constant representing String.class
     */
    IClass<String> STRING = typeinfo(String.class);

    /**
     * Constant representing Number.class
     */
    IClass<Number> NUMBER = typeinfo(Number.class);

    /**
     * Factory: creates IClass from java.reflection type
     *
     * @param type type
     * @return IClass
     */
    static IClass<?> typeinfo(Type type) {
        return IContext.getDefault().typeinfo(type);
    }

    /**
     * Factory: creates IClass from java.reflection class
     *
     * @param type type
     * @param <R> R
     * @return IClass
     */
    static <R> IClass<R> typeinfo(Class<R> type) {
        return IContext.getDefault().typeinfo(type);
    }

    /**
     * Factory: creates IClass for class stored in provided StackTraceElement
     *
     * @param frame frame
     * @return IClass
     */
    static IClass<?> typeinfo(StackTraceElement frame) {
        return typeinfo(frame.getClassName());
    }

    /**
     * Factory: creates IClass for class with provided fully qualified name
     *
     * @param type type
     * @return IClass
     */
    static IClass<?> typeinfo(String type) {
        return FClass.newClass(IContext.getDefault(), type);
    }

    /**
     * Factory: creates IClass for provided object.
     *
     * Please remember about type-erasure, this method has almost no way to deduce generic parameters.
     *
     * @param object object
     * @param <R> R
     * @return IClass
     */
    @Meta.Alias("typefor")
    static <R> IClass<R> typeof(R object) {
        return IContext.getDefault().typefor(object);
    }

    /**
     * Factory: creates IClass for provided object.
     *
     * Please remember about type-erasure, this method has almost no way to deduce generic parameters.
     *
     * @param object object
     * @param <R> R
     * @return IClass
     */
    @Meta.Alias("typeof")
    static <R> IClass<R> typefor(R object) {
        return IContext.getDefault().typefor(object);
    }

    /**
     * Factory: creates generic IClass from provided types and parameters
     *
     * @param type type
     * @param params params
     * @param <R> R
     * @return IClass
     */
    static <R> IClass<R> generic(Class<R> type, IClass<?>... params) {
        return FClass.newClass(type, params);
    }

    /**
     * Factory: creates generic IClass from provided types and parameters
     *
     * @param type type
     * @param params params
     * @param <R> R
     * @return IClass
     */
    static <R> IClass<R> generic(Class<R> type, Class<?>... params) {
        return FClass.newClass(type, Arrays.stream(params).map(IClass::typeinfo).toArray(IClass<?>[]::new));
    }

    /**
     * Factory: creates symbolic IClass representing type with provided name
     *
     * Symbolic types could be used for example, in bytecode generators,
     * where you don't need more that fully qualified name.
     *
     * It does not give information about inheritance, but tries to resolve information
     * about outer classes by parsing name (JDK uses $ character inside inner class names)
     *
     * It throws exception if you try to get some materialized reflective data.
     *
     * @param name name
     * @return IClass
     */
    static IClass<?> symbolic(String name) {
        return new SClass(name);
    }
    
    @Override
    IClasses collect();

    /**
     * Returns list of generic parameters.
     *
     * If it is impossible to resolve parameters, returned list contains wildcard types
     * with at least type bounds defined as in generic class declaration.
     *
     * @return List
     */
    List<IClass<?>> params();

    /**
     * Returns all fields inside class
     *
     * Strategy for members scan:
     *   - by default returns all public fields (including inherited)
     *   - if you use non-public hints, inherited members are still included
     *   - if you add DECLARED hint, inherited members will be discarded.
     *
     * @return IFields
     */
    IFields fields();

    /**
     * Returns first field with specified name.
     *
     * It searches for private and inherited fields too.
     *
     * @param name name
     * @return optional
     */
    Ref<IField> field(String name);

    /**
     * Returns all constructors
     *
     * By default returns only public constructors.
     *
     * @return IConstructors
     */
    IConstructors<T> constructors();

    /**
     * Returns all methods of class.
     *
     * Strategy for members scan:
     *   - by default returns all public methods (including inherited)
     *   - if you use non-public hints, inherited members are still included
     *   - if you add DECLARED hint, inherited members will be discarded.
     *
     * @return IMethods
     */
    IMethods methods();

    /**
     * Returns first method with specified name.
     *
     * It searches for private and inherited methods too.
     *
     * @param name name
     * @return optional
     */
    Ref<IMethod> method(String name);
    
    /**
     * Returns base class
     * If there is not base class, returns {@link #NULL} constant.
     *
     * For wildcard types deduces base class from upper bound.
     * For symbolic types returns {@link #OBJECT}
     *
     * @return IClass
     */
    @Override
    IClass<?> parent();
    
    /**
     * Returns all classes from inheritance chain
     *
     * @return IClasses
     */
    IClasses parents();

    /**
     * Returns all implemented interfaces
     *
     * Strategy for members scan:
     *   - by default returns all implemented interfaces
     *   - if you use DECLARED hint, inherited interfaces will be discarded
     *
     * @return IClasses
     */
    IClasses interfaces();

    /**
     * Returns outer class, if this class is inner class.
     * Returns {@link #NULL} otherwise.
     *
     * @return IClass
     */
    IClass<?> outer();

    /**
     * Returns all inner classes inside this class.
     *
     * Strategy for members scan:
     *   - by default returns all declared, public and non-public classes
     *   - inherited inner classes are not returned
     *
     * @return IClasses
     */
    IClasses inner();

    /**
     * Returns raw class (type erasured generic).
     *
     * For wildcard types it returns {@code Object.class}
     * If you want to examine it, you can try to use {@link #isSubclass} or {@link #parent}
     *
     * It throws exception for non-reflective types (e.g. symbolic).
     *
     * @return Class
     */
    Class<T> raw();

    /**
     * Returns short name, which means class name without package name or outer class
     *
     * @return String
     */
    String shortName();

    /**
     * Returns package name for this class
     *
     * @return String
     */
    String packageName();

    /**
     * Returns component type if type is an array.
     * Throws IReflectiveException otherwise
     *
     * @return IClass
     */
    IClass<?> component();

    /**
     * Returns reflective type assocciated with this type.
     * Some types are not reflective and will throw IReflectiveException
     *
     * For wildcard types it returns parameterized type.
     * If you want to examine it, you can try to use {@link #isSubclass} or {@link #parent}
     *
     * @return Type
     */
    Type reflective();

    /**
     * Creates new object of this type.
     *
     * It uses constructor even if it is non-public.
     *
     * @return object
     */
    T construct();

    /**
     * Creates new object of this type.
     *
     * It searches for constructor matching arguments.
     * It uses matched constructor even if it is non-public.
     *
     * @param arguments arguments
     * @return object
     */
    T construct(Object... arguments);

    /**
     * Checks if {@code this} is superclass of {@code type} (or equals)
     *
     * @param type type
     * @return bool
     */
    boolean isSuper(Class<?> type);

	/**
	 * Checks if {@code this} is superclass of {@code type} (or equals)
     *
	 * @param type type
     * @return bool
	 */
	boolean isSuper(IClass<?> type);

    /**
     * Checks if {@code this} is a subclass of {@code type} (or equals)
     *
     * @param type type
     * @return bool
     */
    boolean isSubclass(Class<?> type);

	/**
	 * Checks if {@code this} is a subclass of {@code type} (or equals)
     *
	 * @param type type
	 * @return bool
	 */
	boolean isSubclass(IClass<?> type);

    /**
     * Returns true if (this is instance of type) and (type is instance of this)
     *
     * It uses autoboxing
     *
     * @param type type
     * @return boolean
     */
    boolean isEquivalent(Class<?> type);

    /**
     * Returns true if (this is instance of type) and (type is instance of this)
     *
     * It uses autoboxing
     * It uses generic parameters.
     *
     * @param type type
     * @return boolean
     */
    boolean isEquivalent(IClass<?> type);

    /**
     * Returns true if value is instance of this type.
     *
     * @param value value
     * @return boolean
     */
    boolean isInstance(Object value);

    /**
     * Casts provided value to this type.
     * Throws ClassCastException if impossible
     *
     * If you prefer using optional instead of try/catch, lets look at
     * {@link ClassUtils#cast(IClass, Object)}.
     *
     * @param value value
     * @return value
     */
    T cast(Object value);

    /**
     * Alias for {@code attribute(IAttribute.INTERFACE)}.
     *
     * @return boolean
     */
    @Meta.Alias("attribute(INTERFACE)")
    boolean isInterface();

    /**
     * Alias for {@code attribute(IAttribute.ABSTRACT)}.
     *
     * @return boolean
     */
    @Meta.Alias("attribute(ABSTRACT)")
    boolean isAbstract();

    /**
     * Alias for {@code attribute(IAttribute.ANNOTATION)}.
     *
     * @return boolean
     */
    @Meta.Alias("attribute(ANNOTATION)")
    boolean isAnnotation();

    /**
     * Alias for {@code attribute(IAttribute.ENUM)}.
     *
     * @return boolean
     */
    @Meta.Alias("attribute(ENUM)")
    boolean isEnum();

    /**
     * Alias for {@code attribute(IAttribute.PARAMETERIZED)}.
     *
     * @return boolean
     */
    @Meta.Alias("attribute(PARAMETERIZED)")
    boolean isParameterized();

    /**
     * Alias for {@code attribute(IAttribute.ARRAY)}.
     *
     * @return boolean
     */
    @Meta.Alias("attribute(ARRAY)")
    boolean isArray();

    /**
     * Alias for {@code isArray() && component().isPrimitive()}.
     *
     * @return boolean
     */
    @Meta.Alias("isArray() && component().isPrimitive()")
    boolean isPrimitiveArray();

    /**
     * Alias for {@code attribute(IAttribute.PRIMITIVE)}.
     *
     * @return boolean
     */
    @Meta.Alias("attribute(PRIMITIVE)")
    boolean isPrimitive();

    /**
     * Alias for {@code attribute(IAttribute.NUMBER)}.
     *
     * @return boolean
     */
    @Meta.Alias("attribute(NUMBER)")
    boolean isNumber();

    /**
     * Alias for {@code attribute(IAttribute.INTEGER)}.
     *
     * @return boolean
     */
    @Meta.Alias("attribute(INTEGER)")
    boolean isInteger();

    /**
     * Alias for {@code attribute(IAttribute.BOXED)}.
     *
     * @return boolean
     */
    @Meta.Alias("attribute(BOXED)")
    boolean isBoxed();

    /**
     * Alias for {@code attribute(IAttribute.VOID)}.
     *
     * @return boolean
     */
    @Meta.Alias("attribute(VOID)")
    boolean isVoid();

    /**
     * Alias for {@code attribute(IAttribute.BOOLEAN)}.
     *
     * @return boolean
     */
    @Meta.Alias("attribute(BOOLEAN)")
    boolean isBoolean();

    /**
     * Alias for {@code attribute(IAttribute.RESOLVED)}.
     *
     * @return boolean
     */
    @Meta.Alias("attribute(RESOLVED)")
    boolean isResolved();

}
