/*
 * @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.generic.Ref;
import net.ranides.assira.reflection.impl.bean.FBeanModel;

import java.util.Collection;
import java.util.Map;
import java.util.Set;

/**
 * This interface is designed for reflectin inspection of JavaBeans.
 * Standard {@link java.beans.Introspector} is very old and it does not support more sophisticated cases:
 *  - overloaded methods
 *  - overloaded setters (in fact, we do not support them too)
 *  - fluent getters and setters (without get/set prefixes)
 *  - indexed properties ({@code Type[]} and {@code List<Type>})
 *  - generic types (very important!)
 *
 *
 * It is compatible with IClass interfaces.
 *
 * Overloaded method can't be accessed directly, but are grouped inside BeanMethod object.
 * BeanMethod will select appropriate overloaded version at call time.
 *
 * There is support for "fluent convention" - you can create fluent map, which will allow you to
 * read all JavaBean properties AND use no-argument methods as computed-property. For example,
 * if your class contains method with name "length()" then you can read fluent property "length" despite
 * the fact, that there is no "getLength" accessor.
 *
 * This functionality is accessible only through FluentMap returned by "fluent" method.
 * You can't call this type of method through "property" or "properties".
 *
 * Note about indexed properties:
 * Some properties defined more than ordinary getters and setters for lists or arrays.
 * In such a case, BeanModel does not return value of property directly, but wraps it by specialized
 * virtual list.
 *
 * This list allows you to modify list elements one-by-one without rewriting whole property over and
 * over again. Please note, that you can introduce structural changes into list, but they won't be
 * visible in JavaBean. In such a case you have to invoke appropriate setter method.
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public interface BeanModel {

    /**
     * Verifies if specified class meets bean requirements
     *
     * @param type type
     * @return error report
     */
    static Collection<BeanException> verify(Class<?> type) {
        return FBeanModel.verify(IClass.typeinfo(type));
    }

    /**
     * Verifies if specified class meets bean requirements
     *
     * @param type type
     * @return error report
     */
    static Collection<BeanException> verify(IClass<?> type) {
        return FBeanModel.verify(type);
    }

    /**
     * Creates model for specified type
     *
     * @param type type
     * @return model
     */
    static BeanModel typeinfo(Class<?> type) {
        return typeinfo(IClass.typeinfo(type));
    }

    /**
     * Creates model for specified type
     *
     * @param type type
     * @return model
     */
    static BeanModel typeinfo(IClass<?> type) {
        return FBeanModel.resolve(type);
    }

    /**
     * Creates model for specified object
     *
     * @param object object
     * @return model
     */
    static BeanModel typefor(Object object) {
        return typeinfo(IClass.typefor(object));
    }

    /**
     * Returns type of introspected bean
     *
     * @return type
     */
    IClass<?> type();

    /**
     * Returns method with specified name.
     * If model has overloaded version, returned BeanMethod represents multi-dispatch.
     *
     * @param name name
     * @return empty if not found
     */
    Ref<BeanMethod> method(String name);

    /**
     * Returns all methods inside JavaBean, mapped by name.
     * Overloaded methods are joined and represented as one multi-dispatch BeanMethod
     *
     * @return map
     */
    Map<String, BeanMethod> methods();

    /**
     * Returns property with specified name.
     * Method supports only JavaBean properties (not fluent)
     *
     * @param name name
     * @return empty if not found
     */
    Ref<BeanProperty> property(String name);

    /**
     * Returns map of properties.
     * Method supports only JavaBean properties (not fluent)
     *
     * @return map
     */
    Map<String, BeanProperty> properties();

    /**
     * Returns map which is view of specified JavaBean.
     * Any JavaBean property is visible in map.
     * Modification of map will modify JavaBean.
     *
     * @param that object
     * @return map view
     */
    Map<String, Object> properties(Object that);

    /**
     * Returns all names which can be used as keys inside FluentMap
     * Fluent keys are names of all properties and no-argument methods which return non-void value.
     *
     * @return keys
     */
    Set<String> fluent();

    /**
     * Returns map which is view of specified JavaBean.
     *
     * Map contains keys for all properties and and no-argument methods which return non-void value.
     *
     * Access to map content will cause invocation of getter or method with specified name.
     * Modification of properties is possible.
     * Modification of computed value (fluent method) will cause exception.
     *
     * @param that that
     * @return map
     */
    FluentMap fluent(Object that);

    /**
     * Creates new instance of JavaBean described by this model.
     * Class of constructed object is identical as returned by {@link #type()} method.
     *
     * @return new object
     */
    Object construct();

    /**
     * Creates new instance of JavaBean described by this model.
     * Class of constructed object is identical as returned by {@link #type()} method.
     *
     * Assigns JavaBeans properties to values specified by map passed as argument.
     *
     * @param values property values
     * @return new object
     */
    Object construct(Map<String, Object> values);

    /**
     * Creates new object and copies all properties from source object to created instance
     *
     * @param that source object
     * @return new object
     */
    Object clone(Object that);

    /**
     * This is specialization of map interface representing view of JavaBean
     * Map contains entries for every property and no-argument method (fluent method).
     *
     * Writable properties can be modified by put methods.
     * Read-only properties or computed values can't be modified.
     * Map does not support adding or removing keys.
     *
     * Illegal operation will throw UnsupportedOperationException,
     * with more detailed exception as a cause.
     */
    interface FluentMap extends Map<String, Object> {

        /**
         * Returns JavaBean instance which is represented by this map view.
         *
         * @return object
         */
        Object unwrap();

        /**
         * Returns BeanModel for this specific map.
         *
         * @return model
         */
        BeanModel model();

        /**
         * Returns declared type of property
         * @param key key
         * @return type or null
         */
        IClass<?> getType(String key);

        /**
         * Returns class declaring specified of property
         * @param key key
         * @return type or null
         */
        IClass<?> getParent(String key);

        /**
         * Returns declared type of property
         * @param key key
         * @return binding or null
         */
        BeanPropertyBinding<Object> getBinding(String key);

    }
}
