package cn.axzo.msg.center.util;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.BeanUtils;

import java.util.Collections;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * 不要直接使用这个类, 用 {@link IterableMapper}
 *
 * @author yanglin
 */
class RecordIterable {

    static <T, M extends BaseMapper<T>> Iterable<Page<T>> iterateBath(
            M mapper, long pageSize, LambdaQueryWrapper<T> query) {
        return () -> new BatchIterator<>(
                mapper, pageSize, query, PageStrategy.ADVANCE_CURRENT_PAGE);
    }

    static <T, M extends BaseMapper<T>> Iterable<T> iterateElement(
            M mapper, long pageSize, LambdaQueryWrapper<T> query) {
        return () -> new ElementIterator<>(new BatchIterator<>(
                mapper, pageSize, query, PageStrategy.ADVANCE_CURRENT_PAGE));
    }

    static <T, M extends BaseMapper<T>> Iterable<Page<T>> iterateBathStickyPage(
            M mapper, long pageSize, LambdaQueryWrapper<T> query) {
        return () -> new BatchIterator<>(
                mapper, pageSize, query, PageStrategy.STICKY_PAGE);
    }

    static <T, M extends BaseMapper<T>> Iterable<T> iterateElementStickyPage(
            M mapper, long pageSize, LambdaQueryWrapper<T> query) {
        return () -> new ElementIterator<>(new BatchIterator<>(
                mapper, pageSize, query, PageStrategy.STICKY_PAGE));
    }

    private static class ElementIterator<T, M extends BaseMapper<T>> implements Iterator<T> {

        private final BatchIterator<T, M> pageIterator;
        private Iterator<T> elementIterator;

        ElementIterator(BatchIterator<T, M> pageIterator) {
            this.pageIterator = pageIterator;
        }

        @Override
        public boolean hasNext() {
            maybeAdvance();
            return elementIterator.hasNext() || pageIterator.hasNext();
        }

        @Override
        public T next() {
            if (!hasNext())
                throw new NoSuchElementException();
            return elementIterator.next();
        }

        private void maybeAdvance() {
            if (elementIterator == null || !elementIterator.hasNext() && pageIterator.hasNext()) {
                elementIterator = pageIterator.hasNext()
                        ? pageIterator.next().getRecords().iterator()
                        : Collections.emptyIterator();
            }
        }

    }

    private static class BatchIterator<T, M extends BaseMapper<T>> implements Iterator<Page<T>> {

        private final M mapper;
        private final LambdaQueryWrapper<T> query;
        private final PageStrategy pageStrategy;
        private final Page<T> page;
        private boolean pageConsumed = false;

        BatchIterator(M mapper, long pageSize, LambdaQueryWrapper<T> query, PageStrategy pageStrategy) {
            this.mapper = mapper;
            this.query = query;
            this.pageStrategy = pageStrategy;
            this.page = new Page<>();
            this.page.setSize(pageSize);
            this.page.setCurrent(0);
        }

        @Override
        public boolean hasNext() {
            maybeFetchNextPage();
            // 1. 很明显的还有下一页
            // 2. 看当前页的数据以及当前页的消费情况 (第一页和最后一页)
            return page.getCurrent() < page.getPages() || (!pageConsumed && !page.getRecords().isEmpty());
        }

        @Override
        public Page<T> next() {
            if (!hasNext())
                throw new NoSuchElementException();
            pageConsumed = true;
            Page<T> copy = new Page<>();
            // 不能暴露内部状态. 如果外部改动了内部的page, 会影响到下次迭代
            BeanUtils.copyProperties(page, copy);
            return copy;
        }

        private void maybeFetchNextPage() {
            // 还没有获取过数据或者当前页数据已经消费, 就获取新page数据
            if (page.getCurrent() == 0 || (pageConsumed && page.hasNext())) {
                if (pageStrategy == PageStrategy.ADVANCE_CURRENT_PAGE)
                    page.setCurrent(page.getCurrent() + 1);
                else
                    page.setCurrent(1);
                mapper.selectPage(page, query);
                pageConsumed = false;
            }
        }
    }

    private enum PageStrategy {
        ADVANCE_CURRENT_PAGE, STICKY_PAGE
    }
}