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

import java.io.Serializable;

/**
 * This class defines error handling used by ResolvePattern *
 */
public abstract class ResolveStrategy implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * This strategy throws ResolveException if any error occurs
     */
    public static final ResolveStrategy THROW = new EHThrow();

    /**
     * This strategy
     *  - returns quoted expression on read error
     *  - throws ResolveException on write error
     *  - returns String.class on type deduction error
     *
     * Quoting strategy: "{" + expression + "}"
     */
    public static final ResolveStrategy FORMAT = new EHFormat();

    /**
     * This strategy
     *  - returns quoted expression on read error
     *  - throws ResolveException on write error
     *  - returns String.class on type deduction error
     *
     * Quoting strategy: "{{" + expression + "}}"
     */
    public static final ResolveStrategy HANDLEBARS = new EHHandleBars();

    /**
     * This strategy
     *  - returns null on read error
     *  - does nothing on write error
     *  - returns IObject.NULL on type deduction error
     */
    public static final ResolveStrategy SILENT = new EHSilent();

    /**
     * This strategy:
     *  - returns null on read error
     *  - throws ResolveException on write error
     *  - returns IObject.NULL on type deduction error
     *
     * But additionally:
     *  - throws ResolveException if any intermediate field can't be accessed
     */
    public static final ResolveStrategy NULL = new EHDefault(null);
    
    protected ResolveStrategy() {
        // do nothing
    }

    /**
     * Factory: creates new strategy which returns default value on error.
     *
     * More detailed contract:
     *  - returns "value" on read error
     *  - throws ResolveException on write error
     *  - returns typeof "value" on type deduction error
     *
     * But additionally:
     *  - throws ResolveException if any intermediate field can't be accessed
     *
     * @param value value
     * @return ResolveStrategy
     */
    public static ResolveStrategy withDefault(Object value) {
        return new EHDefault(value);
    }

    /**
     * This is handler executed on read error for last component of expression
     *
     * @param resolver resolver
     * @return Object
     */
    public abstract Object get(ResolvePattern resolver);

    /**
     * This is handler executed on read error for intermediate component of expression
     *
     * @param resolver resolver
     * @param token token
     * @return Object
     */
    public abstract Object get(ResolvePattern resolver, String token);

    /**
     * This is handler executed on replace error for last component of expression
     *
     * @param resolver resolver
     * @return Object
     */
    public abstract Object replace(ResolvePattern resolver);

    /**
     * This is handler executed on write error for last component of expression
     *
     * @param resolver resolver
     */
    public abstract void set(ResolvePattern resolver);

    /**
     * This is handler executed on type deduction error for last component of expression
     *
     * @param resolver resolver
     * @return IClass
     */
    public abstract IClass<?> type(ResolvePattern resolver);

    /**
     * This is handler executed on type deduction error for intermediate component of expression
     *
     * @param resolver resolver
     * @param token token
     * @return IClass
     */
    public abstract IClass<?> type(ResolvePattern resolver, String token);
    
    private static class EHSilent extends ResolveStrategy {
        
        private static final long serialVersionUID = 1L;
        
        @Override
        public Object get(ResolvePattern resolver) {
            return null;
        }
        
        @Override
        public Object get(ResolvePattern resolver, String token) {
            return null;
        }
        
        @Override
        public IClass<?> type(ResolvePattern resolver) {
            return IClass.NULL;
        }
        
        @Override
        public IClass<?> type(ResolvePattern resolver, String token) {
            return IClass.NULL;
        }
        
        @Override
        public Object replace(ResolvePattern resolver) {
            return null;
        }
        
        @Override
        public void set(ResolvePattern resolver) {
            // do nothing
        }
        
    }
    
    private static class EHThrow extends ResolveStrategy {
        
        private static final long serialVersionUID = 1L;
        
        @Override
        public Object get(ResolvePattern resolver) {
            throw new ResolveException("Unknown value of expression: {" + resolver.expression()+"}");
        }
        
        @Override
        public Object get(ResolvePattern resolver, String token) {
            throw new ResolveException("Invalid expression: {" + resolver.expression() + "} at {" + token+"}");
        }
        
        @Override
        public IClass<?> type(ResolvePattern resolver) {
            throw new ResolveException("Unknown type of expression: {" + resolver.expression() + "}");
        }
        
        @Override
        public IClass<?> type(ResolvePattern resolver, String token) {
            throw new ResolveException("Invalid expression: {" + resolver.expression() + "} at {" + token + "}");
        }
        
        @Override
        public Object replace(ResolvePattern resolver) {
            throw new ResolveException("No replaceable expression: {" + resolver.expression() + "}");
        }
        
        @Override
        public void set(ResolvePattern resolver) {
            throw new ResolveException("No writable expression: {" + resolver.expression() + "}");
        }
        
    }

    private static class EHFormat extends EHThrow {
        
        private static final long serialVersionUID = 1L;

        @Override
        public Object get(ResolvePattern resolver) {
            return "{"+resolver.expression()+"}";
        }
        
        @Override
        public Object get(ResolvePattern resolver, String token) {
            return "{"+resolver.expression()+"}";
        }
        
        @Override
        public IClass<?> type(ResolvePattern resolver) {
            return IClass.STRING;
        }
        
        @Override
        public IClass<?> type(ResolvePattern resolver, String token) {
            return IClass.STRING;
        }
        
    }

    private static class EHHandleBars extends EHThrow {

        private static final long serialVersionUID = 1L;

        @Override
        public Object get(ResolvePattern resolver) {
            return "{{"+resolver.expression()+"}}";
        }

        @Override
        public Object get(ResolvePattern resolver, String token) {
            return "{{"+resolver.expression()+"}}";
        }

        @Override
        public IClass<?> type(ResolvePattern resolver) {
            return IClass.STRING;
        }

        @Override
        public IClass<?> type(ResolvePattern resolver, String token) {
            return IClass.STRING;
        }

    }
    
    private static class EHDefault extends EHThrow {
        
        private static final long serialVersionUID = 1L;
        
        final Object value;

        public EHDefault(Object value) {
            this.value = value;
        }

        @Override
        public Object get(ResolvePattern resolver) {
            return value;
        }
        
        @Override
        public IClass<?> type(ResolvePattern resolver) {
            return IClass.typefor(value);
        }
        
    }
    
}
