package me.chyxion.tigon.service.support;

import java.util.List;
import lombok.extern.slf4j.Slf4j;
import me.chyxion.tigon.model.BaseModel;
import me.chyxion.tigon.mybatis.Search;
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("${default.query.batch.size:32}")
    private int scanBatchSize;

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

    /**
     * {@inheritDoc}
     */
    @Override
    public ListResult<Model> listPage(Search search) {
        log.debug("List Models Page By Search [{}], Using Mapper [{}].", search, mapper);
        return new ListResult<Model>(mapper.list(search),
            mapper.count(search));
    }

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

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

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

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

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

    /**
     * {@inheritDoc}
     */
    @Override
    public ListResult<ViewModel<Model>> listViewModelsPage(
            Search search) {
        log.debug("List View Models Page By Search [{}] Using Mapper [{}].", search, mapper);
        return new ListResult<ViewModel<Model>>(
            listViewModels(search), 
            mapper.count(search));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<ViewModel<Model>> listViewModels(Search search) {
        log.debug("List View Models By Search [{}] Using Mapper [{}].", search, mapper);
        return toViewModel(mapper.list(search));
    }

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

    /**
     * {@inheritDoc}
     */
    @Override
    public Model find(PrimaryKey primaryKey) {
        return mapper.find(primaryKey);
    }

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

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

    /**
     * {@inheritDoc} 
     */
    @Override
    public Model scan(int batchSize, Search search, Scanner<Model> scanner) {
        log.info("Scan Model By Search [{}].", search);
        int total = mapper.count(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 : mapper.list(
                        search.offset(start)
                            .limit(Math.min(batchSize, total - start)))) {
                    if (scanner.run(model)) {
                        log.info("Model [{}] Found By Scanner.", model);
                        return model;
                    }
                }
            }
            log.debug("No Matched Model Found By Scanner.");
        }
        else {
            log.info("No Model To Scan By Search [{}].", search);
        }
        return null;
    }

    protected int scanBatchSize() {
        return scanBatchSize;
    }
}
