package me.chyxion.tigon.service.support;

import java.util.List;
import lombok.extern.slf4j.Slf4j;
import java.util.function.Function;
import me.chyxion.tigon.mybatis.Search;
import me.chyxion.tigon.model.BaseModel;
import me.chyxion.tigon.model.ViewModel;
import me.chyxion.tigon.model.ListResult;
import me.chyxion.tigon.mybatis.BaseMapper;
import me.chyxion.tigon.service.BaseQueryService;
import org.springframework.beans.factory.annotation.Value;

/**
 * @version 0.0.1
 * @since 0.0.1
 * @author Shaun Chyxion <br>
 * chyxion@163.com <br>
 * Nov 7, 2016 4:26:03 PM
 */
@Slf4j
public class BaseQueryServiceSupport
    <PrimaryKey, Model extends BaseModel<PrimaryKey>,
        Mapper extends BaseMapper<PrimaryKey, Model>>
    extends BaseServiceSupport<PrimaryKey, Model, Mapper>
    implements BaseQueryService<PrimaryKey, Model> {

    @Value("${tigon.query.batch.size:32}")
    private int scanBatchSize;

    /**
     * {@inheritDoc}
     */
    @Override
    public Model find(PrimaryKey primaryKey) {
        log.debug("Find model by primary key [{}].", primaryKey);
        return mapper.find(primaryKey);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Model find(Search search) {
        log.debug("Find model by search [{}].", search);
        return mapper.find(search);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Model> list(Search search) {
        log.debug("List models by search [{}].", search);
        return mapper.list(search);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ListResult<Model> listPage(Search search) {
        log.debug("List models page by search [{}].", search);
        return new ListResult<>(list(search), count(search));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ViewModel<Model> findViewModel(PrimaryKey primaryKey) {
        Model model = find(primaryKey);
        return model != null ? toViewModel(model) : null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ViewModel<Model> findViewModel(Search search) {
        log.debug("Find View Model By Search [{}].", search);
        Model model = find(search);
        return model != null ? toViewModel(model) : null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int count(Search search) {
        log.debug("Count model by search [{}].", search);
        return mapper.count(search);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<ViewModel<Model>> listViewModels(Search search) {
        log.debug("List view models by search [{}].", search);
        return toViewModel(list(search));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ListResult<ViewModel<Model>> listViewModelsPage(
            Search search) {
        log.debug("List view models page by search [{}].", search);
        return new ListResult<>(
            listViewModels(search), 
            count(search));
    }

    /**
     * {@inheritDoc} 
     */
    @Override
    public Model scan(Function<Model, Boolean> scanner) {
        return scan(null, scanner);
    }

    /**
     * {@inheritDoc} 
     */
    @Override
    public Model scan(Search search, Function<Model, Boolean> scanner) {
        return scan(scanBatchSize(), search, scanner);
    }

    /**
     * {@inheritDoc} 
     */
    @Override
    public Model scan(int batchSize, Search search, Function<Model, Boolean> scanner) {
        return scan(batchSize, search, this::list, this::count, scanner);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <Model> Model scan(final int batchSize,
        Search search,
        final Function<Search, List<Model>>  dataQuerier,
        final Function<Search, Integer> countQuerier,
        final Function<Model, Boolean> scanner) {
        log.info("Scan model by search [{}].", search);
        int total = countQuerier.apply(search);
        if (total > 0) {
            if (search == null) {
                log.debug("Scan search is null, use default.");
                search = new Search();
            }
            for (int start = 0; start < total; start += batchSize) {
                for (Model model : dataQuerier.apply(
                    search.offset(start)
                        .limit(Math.min(batchSize, total - start)))) {
                    if (scanner.apply(model)) {
                        log.info("Model [{}] found by scanner, stop scan.", model);
                        return model;
                    }
                }
            }
            log.info("Scan completed, no matched model found by scanner.");
        }
        else {
            log.info("No model to scan by search [{}].", search);
        }
        return null;
    }

    /**
     * @return the mapper
     */
    @Override
    public Mapper getMapper() {
        return mapper;
    }

    /**
     * @param mapper the mapper to set
     */
    @Override
    public void setMapper(Mapper mapper) {
        this.mapper = mapper;
    }

    protected int scanBatchSize() {
        return scanBatchSize;
    }
}
