class HBaseScanConfigBuilder {

    @SuppressWarnings("unchecked")
    public <T> HBaseScanConfig<T> getScanConfig(Criteria<T> criteria) {
        Map<List<String>, IndexMapper<?>> indexMap = mappers.get(criteria.clazz());
        HBaseScanConfig<T> hBaseScanConfig = null;
        if (indexMap != null) {
            hBaseScanConfig = new HBaseScanConfig<>();
            IndexMapper<T> indexMapper = null;
            List<Criteria.Field> nonIndexedFields = new ArrayList<>();
            List<Criteria.Field> indexedFields = new ArrayList<>();
            List<Criteria.Field> fieldRestrictions = criteria.fieldRestrictions();
            if (!fieldRestrictions.isEmpty()) {
                // construct query field name -> Field map
                Map<String, Criteria.Field> queryFieldMap = new HashMap<>();
                for (Criteria.Field field : fieldRestrictions) {
                    queryFieldMap.put(field.getName(), field);
                }
                // find the best index mapper to use
                Map.Entry<List<String>, IndexMapper<?>> bestEntry = bestMatch(indexMap, queryFieldMap.keySet());
                if (bestEntry != null) {
                    LOG.debug("Found bestEntry {} for fieldRestrictions {}", bestEntry, fieldRestrictions);
                    indexMapper = (IndexMapper<T>) bestEntry.getValue();
                    hBaseScanConfig.setMapper(indexMapper);
                    // add the fields available in the query to indexedFields
                    for (String indexedFieldName : bestEntry.getKey()) {
                        Criteria.Field field;
                        if ((field = queryFieldMap.remove(indexedFieldName)) != null) {
                            indexedFields.add(field);
                        } else {
                            break; // no more fields can be used as prefix so stop.
                        }
                    }
                    hBaseScanConfig.setIndexedFieldValue(fieldsValue(indexedFields));
                    nonIndexedFields.addAll(queryFieldMap.values()); // remaining fields
                }
            }

            // we haven't found an index mapper, use the default index table if available
            if (indexMapper == null) {
                if ((indexMapper = (IndexMapper<T>) indexMap.get(DEFAULT_INDEX_FIELD_NAMES)) == null) {
                    return null; // no default index table, we can't proceed with the scan
                }
                hBaseScanConfig.setMapper(indexMapper);
                nonIndexedFields.addAll(fieldRestrictions);
            }

            LOG.debug("nonIndexedFields {}", nonIndexedFields);
            // add filters for non-indexed fields
            for (Criteria.Field field : nonIndexedFields) {
                List<byte[]> CfCqCv = indexMapper.mapMemberValue(field.getName(), field.getValue());
                if (CfCqCv != null) {
                    SingleColumnValueFilter filter = new SingleColumnValueFilter(CfCqCv.get(0),
                                                                                 CfCqCv.get(1),
                                                                                 CompareFilter.CompareOp.EQUAL,
                                                                                 CfCqCv.get(2));
                    filter.setFilterIfMissing(true);
                    hBaseScanConfig.addFilter(filter);
                } else {
                    return null; // field not found
                }
            }
            hBaseScanConfig.setStartTs(criteria.startTs());
            hBaseScanConfig.setEndTs(criteria.endTs());
            hBaseScanConfig.setNumRows(criteria.numRows());
        }

        return hBaseScanConfig;
    }

}