package org.codehaus.jackson.map.introspect;

import org.codehaus.jackson.annotate.JsonAutoDetect;
import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
import org.codehaus.jackson.annotate.JsonMethod;

import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;

/**
 * Interface for object used for determine which property elements
 * (methods, fields, constructors) can be auto-detected, with respect
 * to their visibility modifiers.
 *<p>
 * Note on type declaration: funky recursive type is necessary to
 * support builder/fluent pattern.
 *<p>
 * Note on compatibility: 1.9 introduced overloaded "with" method
 * (which takes {@link Visibility} as value to assign); which could
 * be potential issue, but assumption here is that all custom implementations
 * are based on "Std" base class
 * 
 * @author tatu
 * @since 1.5
 */
public interface VisibilityChecker<T extends VisibilityChecker<T>>
{
    // // Builder methods

    /**
     * Builder method that will return an instance that has same
     * settings as this instance has, except for values that
     * given annotation overrides.
     */
    public T with(JsonAutoDetect ann);

    /**
     * Builder method that will create and return an instance that has specified
     * {@link Visibility} value to use for all property elements.
     * Typical usage would be something like:
     *<pre>
     *  mapper.setVisibilityChecker(
     *     mapper.getVisibilityChecker().with(Visibility.NONE));
     *</pre>
     * (which would basically disable all auto-detection)
     *
     * @since 1.9
     */
    public T with(Visibility v);

    /**
     * Builder method that will create and return an instance that has specified
     * {@link Visibility} value to use for specified property.
     * Typical usage would be:
     *<pre>
     *  mapper.setVisibilityChecker(
     *     mapper.getVisibilityChecker().withVisibility(JsonMethod.FIELD, Visibility.ANY));
     *</pre>
     * (which would basically enable auto-detection for all member fields)
     *
     * @since 1.9
     */
    public T withVisibility(JsonMethod method, Visibility v);
    
    /**
     * Builder method that will return a checker instance that has
     * specified minimum visibility level for regular ("getXxx") getters.
     */
    public T withGetterVisibility(Visibility v);

    /**
     * Builder method that will return a checker instance that has
     * specified minimum visibility level for "is-getters" ("isXxx").
     */
    public T withIsGetterVisibility(Visibility v);
    
    /**
     * Builder method that will return a checker instance that has
     * specified minimum visibility level for setters.
     */
    public T withSetterVisibility(Visibility v);

    /**
     * Builder method that will return a checker instance that has
     * specified minimum visibility level for creator methods
     * (constructors, factory methods)
     */
    public T withCreatorVisibility(Visibility v);

    /**
     * Builder method that will return a checker instance that has
     * specified minimum visibility level for fields.
     */
    public T withFieldVisibility(Visibility v);
	
    // // Accessors
	
    /**
     * Method for checking whether given method is auto-detectable
     * as regular getter, with respect to its visibility (not considering
     * method signature or name, just visibility)
     */
    public boolean isGetterVisible(Method m);
    public boolean isGetterVisible(AnnotatedMethod m);

    /**
     * Method for checking whether given method is auto-detectable
     * as is-getter, with respect to its visibility (not considering
     * method signature or name, just visibility)
     */
    public boolean isIsGetterVisible(Method m);
    public boolean isIsGetterVisible(AnnotatedMethod m);
    
    /**
     * Method for checking whether given method is auto-detectable
     * as setter, with respect to its visibility (not considering
     * method signature or name, just visibility)
     */
    public boolean isSetterVisible(Method m);
    public boolean isSetterVisible(AnnotatedMethod m);

    /**
     * Method for checking whether given method is auto-detectable
     * as Creator, with respect to its visibility (not considering
     * method signature or name, just visibility)
     */
    public boolean isCreatorVisible(Member m);
    public boolean isCreatorVisible(AnnotatedMember m);

    /**
     * Method for checking whether given field is auto-detectable
     * as property, with respect to its visibility (not considering
     * method signature or name, just visibility)
     */
    public boolean isFieldVisible(Field f);
    public boolean isFieldVisible(AnnotatedField f);

    /*
    /********************************************************
    /* Standard implementation suitable for basic use
    /********************************************************
    */

   /**
    * Default standard implementation is purely based on visibility
    * modifier of given class members, and its configured minimum
    * levels.
    * Implemented using "builder" (aka "Fluid") pattern, whereas instances
    * are immutable, and configuration is achieved by chainable factory
    * methods. As a result, type is declared is funky recursive generic
    * type, to allow for sub-classing of build methods with property type
    * co-variance.
    *<p>
    * Note on <code>JsonAutoDetect</code> annotation: it is used to
    * access default minimum visibility access definitions.
    */
    @JsonAutoDetect(
        getterVisibility = Visibility.PUBLIC_ONLY,
        isGetterVisibility = Visibility.PUBLIC_ONLY,
        setterVisibility = Visibility.ANY,
        /**
         * By default, all matching single-arg constructed are found,
         * regardless of visibility. Does not apply to factory methods,
         * they can not be auto-detected; ditto for multiple-argument
         * constructors.
         */
        creatorVisibility = Visibility.ANY,
        fieldVisibility = Visibility.PUBLIC_ONLY
    )
    public static class Std
        implements VisibilityChecker<Std>
    {
        /**
         * This is the canonical base instance, configured with default
         * visibility values
         */
        protected final static Std DEFAULT = new Std(Std.class.getAnnotation(JsonAutoDetect.class));
        
        protected final Visibility _getterMinLevel;
        protected final Visibility _isGetterMinLevel;
        protected final Visibility _setterMinLevel;
        protected final Visibility _creatorMinLevel;
        protected final Visibility _fieldMinLevel;
		
        public static Std defaultInstance() { return DEFAULT; }
        
        /**
         * Constructor used for building instance that has minumum visibility
         * levels as indicated by given annotation instance
         * 
         * @param ann Annotations to use for determining minimum visibility levels
         */
	public Std(JsonAutoDetect ann)
	{
	    JsonMethod[] incl = ann.value();
	    // let's combine checks for enabled/disabled, with minimimum level checks:
	    _getterMinLevel = hasMethod(incl, JsonMethod.GETTER) ? ann.getterVisibility() : Visibility.NONE;
            _isGetterMinLevel = hasMethod(incl, JsonMethod.IS_GETTER) ? ann.isGetterVisibility() : Visibility.NONE;
            _setterMinLevel = hasMethod(incl, JsonMethod.SETTER) ? ann.setterVisibility() : Visibility.NONE;
            _creatorMinLevel = hasMethod(incl, JsonMethod.CREATOR) ? ann.creatorVisibility() : Visibility.NONE;
            _fieldMinLevel = hasMethod(incl, JsonMethod.FIELD) ? ann.fieldVisibility() : Visibility.NONE;
	}

	/**
	 * Constructor that allows directly specifying minimum visibility levels to use
	 */
	public Std(Visibility getter, Visibility isGetter, Visibility setter, Visibility creator, Visibility field)
        {
	    _getterMinLevel = getter;
	    _isGetterMinLevel = isGetter;
	    _setterMinLevel = setter;
	    _creatorMinLevel = creator;
	    _fieldMinLevel = field;
        }
		
	/**
	 * Costructor that will assign given visibility value for all
	 * properties.
	 * 
	 * @param v level to use for all property types
	 * 
	 * @since 1.9
	 */
        public Std(Visibility v)
        {
            // typically we shouldn't get this value; but let's handle it if we do:
            if (v == Visibility.DEFAULT) {
                _getterMinLevel = DEFAULT._getterMinLevel;
                _isGetterMinLevel = DEFAULT._isGetterMinLevel;
                _setterMinLevel = DEFAULT._setterMinLevel;
                _creatorMinLevel = DEFAULT._creatorMinLevel;
                _fieldMinLevel = DEFAULT._fieldMinLevel;
            } else {
                _getterMinLevel = v;
                _isGetterMinLevel = v;
                _setterMinLevel = v;
                _creatorMinLevel = v;
                _fieldMinLevel = v;
            }
        }

	/*
        /********************************************************
	/* Builder/fluent methods for instantiating configured
	/* instances
	/********************************************************
	 */

        @Override
	public Std with(JsonAutoDetect ann)
	{
	    if (ann == null) return this;
	    Std curr = this;

	    JsonMethod[] incl = ann.value();
	    Visibility v;

	    v = hasMethod(incl, JsonMethod.GETTER) ? ann.getterVisibility() : Visibility.NONE;
	    curr = curr.withGetterVisibility(v);
	    v = hasMethod(incl, JsonMethod.IS_GETTER) ? ann.isGetterVisibility() : Visibility.NONE;
	    curr = curr.withIsGetterVisibility(v);
	    v = hasMethod(incl, JsonMethod.SETTER) ? ann.setterVisibility() : Visibility.NONE;
            curr  = curr.withSetterVisibility(v);
            v = hasMethod(incl, JsonMethod.CREATOR) ? ann.creatorVisibility() : Visibility.NONE;
            curr = curr.withCreatorVisibility(v);
            v = hasMethod(incl, JsonMethod.FIELD) ? ann.fieldVisibility() : Visibility.NONE;
            curr = curr.withFieldVisibility(v);
	    return curr;
	}

        @Override
	public Std with(Visibility v)
	{
	    if (v == Visibility.DEFAULT) {
	        return DEFAULT;
	    }
	    return new Std(v);
	}

        @Override
	public Std withVisibility(JsonMethod method, Visibility v)
	{
	    switch (method) {
	    case GETTER:
	        return withGetterVisibility(v);
	    case SETTER:
	        return withSetterVisibility(v);
	    case CREATOR:
	        return withCreatorVisibility(v);
	    case FIELD:
	        return withFieldVisibility(v);
	    case IS_GETTER:
	        return withIsGetterVisibility(v);
            case ALL:
                return with(v);
            //case NONE:
            // break;
	    }
            return this;
	}
	
        @Override
	public Std withGetterVisibility(Visibility v) {
	    if (v == Visibility.DEFAULT)  v = DEFAULT._getterMinLevel;
            if (_getterMinLevel == v) return this;
	    return new Std(v, _isGetterMinLevel, _setterMinLevel, _creatorMinLevel, _fieldMinLevel);
	}

        @Override
        public Std withIsGetterVisibility(Visibility v) {
            if (v == Visibility.DEFAULT)  v = DEFAULT._isGetterMinLevel;
            if (_isGetterMinLevel == v) return this;
            return new Std(_getterMinLevel, v, _setterMinLevel, _creatorMinLevel, _fieldMinLevel);
        }
		
        @Override
	public Std withSetterVisibility(Visibility v) {
            if (v == Visibility.DEFAULT)  v = DEFAULT._setterMinLevel;
            if (_setterMinLevel == v) return this;
            return new Std(_getterMinLevel, _isGetterMinLevel, v, _creatorMinLevel, _fieldMinLevel);
	}

	@Override
	public Std withCreatorVisibility(Visibility v) {
            if (v == Visibility.DEFAULT)  v = DEFAULT._creatorMinLevel;
            if (_creatorMinLevel == v) return this;
	    return new Std(_getterMinLevel, _isGetterMinLevel, _setterMinLevel, v, _fieldMinLevel);
	}

        @Override
	public Std withFieldVisibility(Visibility v) {
            if (v == Visibility.DEFAULT)  v = DEFAULT._fieldMinLevel;
            if (_fieldMinLevel == v) return this;
	    return new Std(_getterMinLevel, _isGetterMinLevel, _setterMinLevel, _creatorMinLevel, v);
	}
		
        /*
        /********************************************************
        /* Public API impl
        /********************************************************
         */

	@Override
	public boolean isCreatorVisible(Member m) {
	    return _creatorMinLevel.isVisible(m);
    	}
    
        @Override
    	public boolean isCreatorVisible(AnnotatedMember m) {
    	    return isCreatorVisible(m.getMember());
    	}
    
        @Override
    	public boolean isFieldVisible(Field f) {
    	    return _fieldMinLevel.isVisible(f);
    	}
    
        @Override
    	public boolean isFieldVisible(AnnotatedField f) {
    	    return isFieldVisible(f.getAnnotated());
    	}
    
        @Override
    	public boolean isGetterVisible(Method m) {
    	    return _getterMinLevel.isVisible(m);
    	}    

        @Override
        public boolean isGetterVisible(AnnotatedMethod m) {
    	    return isGetterVisible(m.getAnnotated());
    	}

        @Override
        public boolean isIsGetterVisible(Method m) {
            return _isGetterMinLevel.isVisible(m);
        }    

        @Override
        public boolean isIsGetterVisible(AnnotatedMethod m) {
            return isIsGetterVisible(m.getAnnotated());
        }

        @Override
        public boolean isSetterVisible(Method m) {
            return _setterMinLevel.isVisible(m);
        }
    
        @Override
        public boolean isSetterVisible(AnnotatedMethod m) {
            return isSetterVisible(m.getAnnotated());
        }

        /*
        /********************************************************
        /* Helper methods
        /********************************************************
         */
    
        private static boolean hasMethod(JsonMethod[] methods, JsonMethod method)
        {
            for (JsonMethod curr : methods) {
                if (curr == method || curr == JsonMethod.ALL) return true;
            }
            return false;
        }

        /*
        /********************************************************
        /* Standard methods
        /********************************************************
         */
        @Override
        public String toString() {
            return new StringBuilder("[Visibility:")
                .append(" getter: ").append(_getterMinLevel)
                .append(", isGetter: ").append(_isGetterMinLevel)
                .append(", setter: ").append(_setterMinLevel)
                .append(", creator: ").append(_creatorMinLevel)
                .append(", field: ").append(_fieldMinLevel)
                .append("]").toString();
        }
    }
}
