package net.ranides.assira.reflection.walker;

import net.ranides.assira.collection.prototype.PrototypeMap;
import net.ranides.assira.reflection.ResolveContext;
import net.ranides.assira.reflection.walker.WalkerContexts.ObjectContext;

import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * This visitor is interface used by ObjectWalker.
 *
 * ObjectWalker executes different "visit" objects for different field types.
 * Every visit method can inform walker if it should continue inspection recursively.
 * If method returns false, then recursive inspection is not continued for this field.
 *
 * ObjectVisitor receives information about visited field in the form of ObjectContext.
 * Specialized methods (for example arrays, collections, lists) receive a bit more detailed subclasses
 * with additional context information (for example collection size)
 *
 * ObjectVisitor stores custom properties accessible from every context: it is prototype for every scope.
 * Especially, those custom properties can be used by custom visitors executed from "visit..." methods.
 */
public interface ObjectVisitor {

    /**
     * Returns ResolveContext which is used as parent scope by every ObjectContext.
     * In other words: returns custom global properties shared during inspection time.
     *
     * @return ResolveContext
     */
    ResolveContext scope();

    /**
     * Method executed for "singular" fields, that means: not collections, arrays, or maps.
     * Some implementations of ObjectVisitor call this method in other, more specific cases too, as default handler.
     *
     * @param ctx ctx
     * @return true if should be visited recursively
     */
    boolean visitObject(ObjectContext<?> ctx);

    /**
     * Method executed for fields which are array.
     *
     * @param ctx ctx
     * @return true if should be visited recursively
     */
    boolean visitArray(WalkerContexts.ArrayContext<?> ctx);

    /**
     * Method executed for fields which are collection.
     *
     * @param ctx ctx
     * @return true if should be visited recursively
     */
    boolean visitCollection(WalkerContexts.CollectionContext<?> ctx);

    /**
     * Method executed for fields which are list.
     *
     * @param ctx ctx
     * @return true if should be visited recursively
     */
    boolean visitList(WalkerContexts.ListContext<?> ctx);

    /**
     * Method executed for fields which are maps.
     *
     * @param ctx ctx
     * @return true if should be visited recursively
     */
    boolean visitMap(WalkerContexts.MapContext<?> ctx);

    /**
     * Creates SimpleVisitor which calls predicate for every field.
     *
     * @param predicate predicate
     * @return ObjectVisitor
     */
    static ObjectVisitor of(Predicate<ObjectContext<?>> predicate) {
        return new SimpleVisitor() {
            @Override
            public boolean visitObject(ObjectContext<?> ctx) {
                return predicate.test(ctx);
            }
        };
    }

    /**
     * Basic ObjectVisitor implementation which delegates all work to "visitObject".
     *
     * It does not support custom properties, it returns just empty, immutable ResolveContext.
     */
    abstract class SimpleVisitor implements ObjectVisitor {

        @Override
        public ResolveContext scope() {
            return ResolveContext.EMPTY;
        }

        @Override
        public abstract boolean visitObject(ObjectContext<?> ctx);

        @Override
        public boolean visitArray(WalkerContexts.ArrayContext<?> ctx) {
            return true;
        }

        @Override
        public boolean visitCollection(WalkerContexts.CollectionContext<?> ctx) {
            return true;
        }

        @Override
        public boolean visitList(WalkerContexts.ListContext<?> ctx) {
            return true;
        }

        @Override
        public boolean visitMap(WalkerContexts.MapContext<?> ctx) {
            return true;
        }

    }

    /**
     * Basic ObjectVisitor implementation which delegates all work to "visitObject".
     *
     * It supports custom properties, it stores them inside provided PrototypeMap.
     */
    abstract class AbstractVisitor implements ObjectVisitor {
        private final PrototypeMap scope;

        protected AbstractVisitor(Map<Object, ?> map) {
            this.scope = new PrototypeMap(map);
        }

        @Override
        public final ResolveContext scope() {
            return () -> scope;
        }

        @Override
        public abstract boolean visitObject(ObjectContext<?> context);

        @Override
        public final boolean visitArray(WalkerContexts.ArrayContext<?> ctx) {
            return visitObject(ctx);
        }

        @Override
        public final boolean visitCollection(WalkerContexts.CollectionContext<?> ctx) {
            return visitObject(ctx);
        }

        @Override
        public final boolean visitList(WalkerContexts.ListContext<?> ctx) {
            return visitObject(ctx);
        }

        @Override
        public final boolean visitMap(WalkerContexts.MapContext<?> ctx) {
            return visitObject(ctx);
        }
    }

    /**
     * Basic ObjectVisitor implementation which delegates all work to provided "visitObject".
     *
     * It supports custom properties, it stores them inside provided PrototypeMap.
     *
     * It stores additionally provided "consumer".
     * It is responsibility of implementor to call this "consumer" and/or decide if scan field recursively.
     */
    abstract class ConsumeVisitor<T> extends AbstractVisitor {
        private final Consumer<T> target;

        protected ConsumeVisitor(Consumer<T> target, Map<Object, ?> map) {
            super(map);
            this.target = target;
        }

        /**
         * This method should be called by "visitObject" implementation.
         * Provided field value will be processed by consumer defined at construction time.
         *
         * @param value value
         */
        public final void accept(T value) {
            target.accept(value);
        }

    }

}
