package jnpf.util;


import java.util.*;

/**
 * 空判断工具
 *
 * @author tq
 */
public class OptionalUtils {

    /**
     * 或 处理
     * 所有if里，只要有一个满足条件，最后才会执行done方法
     * 每个if满足条件则会执行then方法
     * <pre>
     *     <code>
     * if(){
     * then();
     * }  if(){
     * then();
     * }  if(){
     * then();
     * }
     * ...
     * if(条件N){
     * then();
     * }
     * if(任一满足){
     * done();
     * }
     * 例子:
     *
     *  OptionalUtils.or()
     *                 .ifPresent(req.getProvinceCode()).then(() -> update.setProvinceCode(req.getProvinceCode()))
     *                 .ifPresent(req.getCityCode()).then(() -> update.setCityCode(req.getCityCode()))
     *                 .ifPresent(req.getDistrictCode()).then(() -> update.setDistrictCode(req.getDistrictCode()))
     *                 .ifPresent(req.getCategoryId()).then(() -> update.setCategoryId(req.getCategoryId()))
     *                 .ifPresent(req.getAddress()).then(() -> update.setAddress(req.getAddress()))
     *                 .done(() -> {
     *                     this.updateById(update);
     *                 });
     *     </code>
     * </pre>
     *
     * @param <T>
     * @return
     */
    public static <T> Condition<T> or() {
        return new OrCondition<>();
    }

    /**
     * 并且 处理
     * 所有if里，必须所有满足条件，最后才会执行done方法
     * 每个if满足条件则会执行then方法
     * <pre>
     *     <code>
     * if(条件1){
     * then();
     * }
     * if(条件2){
     * then();
     * }
     * if(条件3){
     * then();
     * }
     * ...
     * if(条件N){
     * then();
     * }
     * if(所有满足){
     * done();
     * }
     *
     * 例子:
     *  OptionalUtils.and()
     *                 .ifPresent(req.getProvinceCode()).then(() -> update.setProvinceCode(req.getProvinceCode()))
     *                 .ifPresent(req.getCityCode()).then(() -> update.setCityCode(req.getCityCode()))
     *                 .ifPresent(req.getDistrictCode()).then(() -> update.setDistrictCode(req.getDistrictCode()))
     *                 .ifPresent(req.getCategoryId()).then(() -> update.setCategoryId(req.getCategoryId()))
     *                 .ifPresent(req.getAddress()).then(() -> update.setAddress(req.getAddress()))
     *                 .done(() -> {
     *                     this.updateById(update);
     *                 });
     *     </code>
     * </pre>
     *
     * @param <T>
     * @return
     */
    public static <T> Condition<T> and() {
        return new AndCondition<>();
    }

    public static <T> void ifPresent(T value, RunBlockNoReturnFunction<T> runBlock) {
        if (!isEmpty(value)) {
            runBlock.run(value);
        }
    }

    public static <T, R> R ifPresent(T value, RunBlockReturnFunction<T, R> runBlock) {
        if (!isEmpty(value)) {
            return runBlock.run(value);
        }
        return null;
    }

    public static <T, R> R ifPresent(T value, R defaultValue, RunBlockReturnFunction<T, R> runBlock) {
        if (!isEmpty(value)) {
            return runBlock.run(value);
        }
        return defaultValue;
    }

    public static <T> void ifNotPresent(T value, String exceptionMsg) {
        if (isEmpty(value)) {
            throw new ServiceException(exceptionMsg);
        }
    }

    public static <T> void ifNotPresent(T value, RunBlockNoParameterNoReturnFunction runBlock) {
        if (isEmpty(value)) {
            runBlock.run();
        }
    }

    public static <T, R> R ifNotPresent(T value, RunBlockNoParameterReturnFunction<R> runBlock, R defaultValue) {
        if (isEmpty(value)) {
            return runBlock.run();
        }
        return defaultValue;
    }

    @FunctionalInterface
    public interface RunBlockNoParameterReturnFunction<R> {
        R run();
    }

    @FunctionalInterface
    public interface RunBlockReturnFunction<T, R> {
        R run(T t);
    }

    @FunctionalInterface
    public interface RunBlockNoReturnFunction<T> {
        void run(T t);
    }

    @FunctionalInterface
    public interface RunBlockNoParameterNoReturnFunction {
        void run();
    }

    public static class Execute<T> {

        private final T value;
        private final Condition<T> condition;

        public Execute(T value, Condition<T> condition) {
            this.value = value;
            this.condition = condition;
        }

        public Condition<T> then(RunBlockNoParameterNoReturnFunction runBlock) {
            runBlock.run();
            return condition;
        }
    }

    public static class NullExecute<T> extends Execute<T> {
        private final Condition<T> condition;

        public NullExecute(Condition<T> condition) {
            super(null, condition);
            this.condition = condition;
        }

        @Override
        public Condition<T> then(RunBlockNoParameterNoReturnFunction runBlock) {
            return condition;
        }
    }

    public abstract static class Condition<T> {

        protected final List<Boolean> stepNullOptional = new ArrayList<>();

        public Condition() {
        }

        public Execute<T> ifPresent(T value) {
            if (!isEmpty(value)) {
                stepNullOptional.add(true);
                return new Execute<>(value, this);
            } else {
                stepNullOptional.add(false);
                return new NullExecute<>(this);
            }
        }

        public Execute<T> ifPresent(T value, RunBlockReturnFunction<T, Boolean> runBlock) {
            if (!runBlock.run(value)) {
                stepNullOptional.add(true);
                return new Execute<>(value, this);
            } else {
                stepNullOptional.add(false);
                return new NullExecute<>(this);
            }
        }

        public Execute<T> ifNotPresent(T value) {
            return new NullExecute<>(this);
        }

        public Execute<T> ifNotPresent(T value, RunBlockNoParameterNoReturnFunction runBlock) {
            runBlock.run();
            return new NullExecute<>(this);
        }

        public abstract <E> E done(RunBlockNoParameterReturnFunction<E> runBlock);

        public abstract void done(RunBlockNoParameterNoReturnFunction runBlock);

    }

    public static class AndCondition<T> extends Condition<T> {

        @Override
        public <E> E done(RunBlockNoParameterReturnFunction<E> runBlock) {
            if (!stepNullOptional.contains(false)) {
                return runBlock.run();
            }
            return null;
        }

        @Override
        public void done(RunBlockNoParameterNoReturnFunction runBlock) {
            if (!stepNullOptional.contains(false)) {
                runBlock.run();
            }
        }
    }

    public static class OrCondition<T> extends Condition<T> {

        @Override
        public <E> E done(RunBlockNoParameterReturnFunction<E> runBlock) {
            if (stepNullOptional.contains(true)) {
                return runBlock.run();
            }
            return null;
        }

        @Override
        public void done(RunBlockNoParameterNoReturnFunction runBlock) {
            if (stepNullOptional.contains(true)) {
                runBlock.run();
            }
        }
    }

    private static <T> boolean isEmpty(T value) {
        if (Objects.isNull(value)) {
            return true;
        }
        if (value instanceof String) {
            if ("".equals(value) || "".equals(((String) value).trim())) {
                return true;
            }
        } else if (Collection.class.isAssignableFrom(value.getClass())) {
            return ((Collection<?>) (value)).isEmpty();
        } else if (Map.class.isAssignableFrom(value.getClass())) {
            return ((Map<?, ?>) value).isEmpty();
        }
        return false;
    }

}
