package ru.yandex.wmtools.common.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.yandex.common.framework.pager.Pager;
import ru.yandex.common.util.db.OrderByClause;
import ru.yandex.wmtools.common.data.TopQueryHistoryTypeEnum;
import ru.yandex.wmtools.common.data.info.CustomQueryInfo;
import ru.yandex.wmtools.common.data.info.HostRegionsInfo;
import ru.yandex.wmtools.common.data.info.RegionInfo;
import ru.yandex.wmtools.common.data.info.TopInfo;
import ru.yandex.wmtools.common.data.info.TopInfoItem;
import ru.yandex.wmtools.common.data.info.TopQueryHistoryInfo;
import ru.yandex.wmtools.common.data.info.TotalsInfo;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.UserException;
import ru.yandex.wmtools.common.error.UserProblem;
import ru.yandex.wmtools.common.util.ParameterizedMapRowMapper;
import ru.yandex.wmtools.common.util.SqlUtil;

abstract public class
        TopInfoService<HostInfoType, TI extends TopInfo, TII extends TopInfoItem, TTL extends TotalsInfo> extends AbstractDbService {
    private static final Logger log = LoggerFactory.getLogger(TopInfoService.class);

    private static final int MAX_BYTES_FOR_QUERY = 255;
    public static final int THE_WHOLE_WORLD_KEY_REGION = 0;

    private CustomRegionService<HostInfoType> customRegionService;
    public void setCustomRegionService(CustomRegionService<HostInfoType> customRegionService) {
        this.customRegionService = customRegionService;
    }
    protected CustomRegionService<HostInfoType> getCustomRegionService() {
        return customRegionService;
    }

    private RegionsTreeCacheService regionsTreeCacheService;
    protected RegionsTreeCacheService getRegionsTreeCacheService() {
        return regionsTreeCacheService;
    }
    public void setRegionsTreeCacheService(RegionsTreeCacheService regionsTreeCacheService) {
        this.regionsTreeCacheService = regionsTreeCacheService;
    }

    public Date getLatestInfoDateIfDateIsNull(Integer region, HostInfoType hostInfoType, Date date, boolean includeSubregions) throws InternalException {
        List<Date> dates = getTopInfoDatesForRegionByHost(region, hostInfoType, includeSubregions);
        if (!dates.isEmpty()) {
            if (date != null) {
                return getRightBorderOfTimePeriod(date, dates);
            }
            return dates.get(dates.size() - 1);
        }

        return null;
    }

    public Date getLatestTopOrCustomInfoDateIfDateIsNull(Integer region, HostInfoType hostInfoType, Date date) throws InternalException {
        List<Date> dates = getTopAndCustomInfoDatesForRegionByHost(region, hostInfoType, true);
        if (!dates.isEmpty()) {
            if (date != null) {
                return getRightBorderOfTimePeriod(date, dates);
            }
            return dates.get(dates.size() - 1);
        }

        return null;
    }

    // TODO ��� ������. ��� ���� ����� ����������� � ����������� � WMCTopInfoService?
    public void getDatesAndRegionsForRequest(Collection<Date> validDates, Collection<RegionInfo> regions, Integer region, List<RegionInfo> keyRegions, boolean includeSubregions, Map<Date, List<RegionInfo>> keyRegionsForDates) throws InternalException {
        List<Date> dates = new ArrayList<Date>(keyRegionsForDates.keySet());
        Collections.sort(dates);

        if (!includeSubregions) {
            //Regions excluded from region.
            List<RegionInfo> excluded = regionsTreeCacheService.getRegionsInfo(regionsTreeCacheService.getKeyRegionsFromSubTreeByInfoList(region, keyRegions, true));

            log.debug("Excluded regions are: " + SqlUtil.getCommaSeparatedList(excluded));

            //dates with good keyregions
            for (Date date : dates) {
                //Regions excluded from region in specified date
                List<RegionInfo> excludedAt = regionsTreeCacheService.getRegionsInfo(
                        regionsTreeCacheService.getKeyRegionsFromSubTreeByInfoList(region, keyRegionsForDates.get(date), true));
                log.debug("Excluded at " + date + " : " + SqlUtil.getCommaSeparatedList(excludedAt));
                if (new HashSet<RegionInfo>(excluded).equals(new HashSet<RegionInfo>(excludedAt))) {
                    validDates.add(date);
                    log.debug("So good");
                }
            }
            if (region != null) {
                regions.add(regionsTreeCacheService.getRegionInfo(region));
            }
        } else {
            //All regions from subtree of region which are contained in keyRegions for any date
            Set<RegionInfo> toInclude = new HashSet<RegionInfo>();
            RegionInfo selected = regionsTreeCacheService.getRegionInfo(region);

            for (Date date : dates) {
                List<RegionInfo> keyRegionsForDate = keyRegionsForDates.get(date);

                if (region != 0 && !keyRegionsForDate.contains(selected)) {
                    continue;
                }

                validDates.add(date);
                keyRegionsForDate = regionsTreeCacheService.getRegionsInfo(regionsTreeCacheService.getKeyRegionsFromSubTreeByInfoList(region, keyRegionsForDate, false));

                toInclude.addAll(keyRegionsForDate);
            }

            toInclude.add(selected);
            regions.addAll(toInclude);
        }
    }

    public Date getLatestCustomInfoDateIfDateIsNull(Integer region, HostInfoType hostInfoType, Date date, boolean includeSubregions) throws InternalException {
        List<Date> dates = getCustomInfoDatesForRegionByHost(region, hostInfoType, includeSubregions);
        if (!dates.isEmpty()) {
            if (date != null) {
                return getRightBorderOfTimePeriod(date, dates);
            }
            return dates.get(dates.size() - 1);
        }

        return null;
    }

    public TI getCustomQueriesInfo(Integer region, List<RegionInfo> keyRegions, HostInfoType hostInfoType, Date date,
                                   OrderByClause orderBy, Pager pager, TopTypeEnum type, boolean includeSubregions) throws InternalException {
        if (!existsInKeyRegions(region, keyRegions)) {
            // not a key region for this date
            return getTopInfoStubNotAKeyRegion();
        }

        if (date == null) {
            log.warn("getTopInfo: Date not specified!");
            return getTopInfoStubDateNotSpecified();
        }

        int regionForCustomQueryHistory = THE_WHOLE_WORLD_KEY_REGION;
        if (region != null) {
            regionForCustomQueryHistory = region;
        }

        String whereClause = getWhereClauseForKeyRegions(region, keyRegions, includeSubregions);

        // get all custom queries for this host with all information.
        List<TII> customList = getInfoAboutCustomQueries(hostInfoType, date, type, regionForCustomQueryHistory, whereClause, orderBy, pager, includeSubregions);


        Collections.sort(customList, getTopInfoComparator(orderBy));

        TTL totals = getInfoAboutTotals(hostInfoType, date, whereClause);
        if (totals != null) {
            return createTopInfo(customList, totals);
        }

        return getTopInfoStubEmpty();
    }

    public TI getTopInfo(Integer region, List<RegionInfo> keyRegions, HostInfoType hostInfoType, Date date,
                         OrderByClause orderBy, TopTypeEnum type, boolean includeSubregions) throws InternalException {
        if (!existsInKeyRegions(region, keyRegions)) {
            // not a key region for this date
            return getTopInfoStubNotAKeyRegion();
        }

        if (date == null) {
            log.warn("getTopInfo: Date not specified!");
            return getTopInfoStubDateNotSpecified();
        }

        int regionForCustomQueryHistory = THE_WHOLE_WORLD_KEY_REGION;
        if (region != null) {
            regionForCustomQueryHistory = region;
        }

        String whereClause = getWhereClauseForKeyRegions(region, keyRegions, includeSubregions);

        // get all custom queries for this host with all information.
        List<TII> customList = getInfoAboutTopCustomQueries(hostInfoType, date, type, regionForCustomQueryHistory, whereClause, includeSubregions);

        // get top queries list
        List<TII> topList = getInfoAboutTopQueries(hostInfoType, date, type, whereClause);

        // place custom queries into resulting array on corresponding places.
        TII[] topArray = createTopInfoItem();
        int arraySize = topList.size();
        for (TII item : customList) {
            Integer topRank = item.getTopRank(type);
            if ((topRank == null) || (topRank >= getTopQueriesCount())) {
                topArray[arraySize] = item;
                arraySize++;
            } else {
                topArray[topRank] = item;
            }
        }

        // insert top queries into array, already filled of custom queries
        topList.removeAll(customList);
        int place = 0;
        boolean limitReached = false;
        for (TII item : topList) {
            while (topArray[place] != null) {
                place++;
                if (place >= getTopQueriesCount()) {
                    limitReached = true;
                    break;
                }
            }
            if (limitReached) {
                break;
            }
            topArray[place] = item;
        }

        // WMCON-2264. Workaround to prevent some errors of C++-part.
        int next = 0;
        for (int i = 0; i < arraySize; i++) {
            topArray[next] = topArray[i];
            if (topArray[i] != null) {
                next++;
            }
        }
        arraySize = next;

        // set valid ranks for all cells in array
        for (int i = 0; i < arraySize; i++) {
            topArray[i].setTopRankIfNotCustom(type, i);
        }

        // sort as required
        List<TII> resultList = Arrays.asList(Arrays.copyOf(topArray, arraySize));
        Collections.sort(resultList, getTopInfoComparator(orderBy));

        TTL totals = getInfoAboutTotals(hostInfoType, date, whereClause);
        if (totals != null) {
            return createTopInfo(resultList, totals);
        }

        return getTopInfoStubEmpty();
    }

    abstract protected Comparator<? super TII> getTopInfoComparator(OrderByClause orderBy);

    abstract protected TI createTopInfo(List<TII> resultList, TTL totals);

    abstract protected TI getTopInfoStubEmpty();

    abstract protected TI getTopInfoStubDateNotSpecified();

    abstract protected TI getTopInfoStubNotAKeyRegion();

    abstract protected TII[] createTopInfoItem();

    abstract protected String getWhereClauseForKeyRegions(Integer region, List<RegionInfo> keyRegions, boolean includeSubregions) throws InternalException;

    abstract protected int getTopQueriesCount();

    abstract protected TTL getInfoAboutTotals(HostInfoType hostInfoType, Date date, String whereClause) throws InternalException;

    abstract protected List<TII> getInfoAboutTopQueries(HostInfoType hostInfoType, Date date, TopTypeEnum type, String whereClause) throws InternalException;

    abstract protected List<TII> getInfoAboutCustomQueries(HostInfoType hostInfoType, Date date, TopTypeEnum type, int regionForCustomQueryHistory, String whereClause, OrderByClause order, Pager pager, boolean includeSubregions) throws InternalException;

    abstract protected List<TII> getInfoAboutTopCustomQueries(HostInfoType hostInfoType, Date date, TopTypeEnum type, int regionForCustomQueryHistory, String whereClause, boolean includeSubregions) throws InternalException;

    public List<Date> getTopInfoDatesByHost(final HostInfoType hostInfoType) throws InternalException {
        return getTopInfoDatesForRegionByHost(null, hostInfoType);
    }

    public List<Date> getCustomInfoDatesByHost(final HostInfoType hostInfoType) throws InternalException {
        return getCustomInfoDatesForRegionByHost(null, hostInfoType, true);
    }

    public List<Date> getTopInfoDatesForRegionByHost(final Integer region, final HostInfoType hostInfoType)
            throws InternalException {
        return getTopInfoDatesForRegionByHost(region, hostInfoType, true);
    }

    abstract public List<Date> getTopInfoDatesForRegionByHost(final Integer region, final HostInfoType hostInfoType,
                                                              boolean includeSubregions)
            throws InternalException;

    abstract public List<Date> getCustomInfoDatesForRegionByHost(final Integer region, final HostInfoType hostInfoType,
                                                                 boolean includeSubregions)
            throws InternalException;

    abstract public List<Date> getTopAndCustomInfoDatesForRegionByHost(final Integer region, final HostInfoType hostInfoType, boolean includeSubregions)
            throws InternalException;

    abstract public List<Date> getTopInfoUpdateDatesForRegionByHost(final Integer region, final HostInfoType hostInfoType)
            throws InternalException;

    abstract public NavigableMap<Date, Integer> getQueryShowsHistory(final Integer region,
                                                                     final List<RegionInfo> keyRegions,
                                                                     final HostInfoType hostInfoType,
                                                                     final long requestId,
                                                                     final boolean includeSubregions) throws InternalException;

    abstract public NavigableMap<Date, Integer> getQueryClicksHistory(final Integer region,
                                                                      final List<RegionInfo> keyRegions,
                                                                      final HostInfoType hostInfoType,
                                                                      final long queryId,
                                                                      final boolean includeSubregions) throws InternalException;

    abstract public NavigableMap<Date, Double> getQueryCTRHistory(final Integer region,
                                                                  final List<RegionInfo> keyRegions,
                                                                  final HostInfoType hostInfoType,
                                                                  long queryId,
                                                                  final boolean includeSubregions) throws InternalException;

    abstract public NavigableMap<Date, Integer> getQueryPositionHistory(final Integer region,
                                                                        final List<RegionInfo> keyRegions,
                                                                        final HostInfoType hostInfoType,
                                                                        long queryId,
                                                                        final boolean includeSubregions) throws InternalException;

    abstract public NavigableMap<Date, Double> getQueryPartHistory(final Integer region,
                                                                   final List<RegionInfo> keyRegions,
                                                                   final HostInfoType hostInfoType,
                                                                   long queryId,
                                                                   final boolean includeSubregions) throws InternalException;

    abstract public List<TopQueryHistoryInfo> getQueriesHistoryInfo(Integer region,
                                                                    TopQueryHistoryTypeEnum type,
                                                                    final List<RegionInfo> keyRegions,
                                                                    final HostInfoType hostInfoType,
                                                                    int count,
                                                                    final boolean includeSubregions) throws InternalException;

    abstract public List<Long> getQueriesWithHistoryIds(List<Long> queries,
                                                        HostInfoType hostInfoType,
                                                        String whereClause) throws InternalException;

    abstract public List<TopQueryHistoryInfo> getQueriesHistoryInfo(Integer region,
                                                                    TopQueryHistoryTypeEnum type,
                                                                    final List<RegionInfo> keyRegions,
                                                                    final HostInfoType hostInfoType,
                                                                    long count,
                                                                    final boolean includeSubregions,
                                                                    OrderByClause order,
                                                                    Pager pager,
                                                                    Date day) throws InternalException;

    abstract public List<CustomQueryInfo> getCustomQueriesForHost(HostInfoType hostInfoType) throws InternalException;

    abstract public void removeCustomQueriesForHost(HostInfoType hostInfoType, List<Long> queryIds)
            throws InternalException, UserException;

    public void addCustomQueriesForHost(HostInfoType hostInfoType, List<String> standardizedQueriesList) throws InternalException, UserException {
        int count = getCustomQueriesCountForHost(hostInfoType);
        if (count + standardizedQueriesList.size() > getMaxCustomQueriesForHost()) {
            throw new UserException(UserProblem.TOO_MANY_CUSTOM_QUERIES_FOR_HOST, "There are already "
                    + count + " queries for this host. Can't add " + standardizedQueriesList.size()
                    + " more!");
        }

        addCustomQueriesToDb(hostInfoType, standardizedQueriesList);
        Long[] queryIds = new Long[0];
        queryIds = getCustomQueriesIds(hostInfoType, standardizedQueriesList).toArray(queryIds);
        addQueriesToUserCustomQueriesList(hostInfoType, queryIds);
    }

    public void addCustomQueryForHost(HostInfoType hostInfoType, String standardizedQuery) throws InternalException, UserException {
        // 0. check, that there are not more than 10 custom queries already added
        if (getCustomQueriesCountForHost(hostInfoType) >= getMaxCustomQueriesForHost()) {
            throw new UserException(UserProblem.TOO_MANY_CUSTOM_QUERIES_FOR_HOST, "There are already " + getMaxCustomQueriesForHost() + " queries for this host.");
        }

        // 1. add query to queries list, if needed
        addCustomQueryToDb(hostInfoType, standardizedQuery);

        // 2. get query id
        Long queryId = getCustomQueryId(hostInfoType, standardizedQuery);

        // 3. add query to user queries list
        int rowsUpdated = addQueriesToUserCustomQueriesList(hostInfoType, queryId);

        if (rowsUpdated != 1) {
            if (rowsUpdated != 0) {
                throw new AssertionError("This should never happen: updated not 0 and not 1 rows.");
            }

            // no queries added to list
            throw new UserException(UserProblem.SUCH_CUSTOM_QUERY_ALREADY_ADDED, "Custom query '" + standardizedQuery + "' is already added to list");
        }
    }

    abstract protected int getMaxCustomQueriesForHost();

    abstract protected Long getCustomQueryId(HostInfoType hostInfoType, String standardizedQuery) throws InternalException;

    abstract protected void addCustomQueryToDb(HostInfoType hostInfoType, String standardizedQuery) throws InternalException;

    abstract protected int addQueriesToUserCustomQueriesList(HostInfoType hostInfoType, Long ... queryIds) throws InternalException;

    abstract protected List<Long> getCustomQueriesIds(HostInfoType hostInfoType, List<String> standardizedQueries) throws InternalException;

    abstract protected void addCustomQueriesToDb(HostInfoType hostInfoType, List<String> standardizedQueries) throws InternalException;

    abstract protected int getCustomQueriesCountForHost(HostInfoType hostInfoType) throws InternalException;

//    public String standardizeQuery(String query) throws InternalException, UserException {
//        log.debug("Query before standardize: '" + query + "'");
//
//        StringBuilder sb = new StringBuilder(query.toLowerCase());
//
//        // Tab symbols => space symbols
//        replaceAll(sb, "\t", " ");
//
//        // Double space => ordinary space
//        replaceAll(sb, "  ", " ");
//
//        // Remove leading spaces
//        while (sb.toString().startsWith(" ")) {
//            sb.deleteCharAt(0);
//        }
//
//        // Remove trailing symbols to make the query fit into MAX_BYTES_FOR_QUERY bytes.
//        try {
//            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
//            OutputStreamWriter out = new OutputStreamWriter(outputStream, "UTF8");
//
//            outputStream.reset();
//            out.write(sb.toString());
//            out.flush();
//            if (outputStream.size() > MAX_BYTES_FOR_QUERY) {
//                throw new UserException(UserProblem.CUSTOM_QUERY_IS_TOO_LARGE, "Custom query has " + outputStream.size() + " bytes, while max size is " + MAX_BYTES_FOR_QUERY + " bytes.");
//            }
//            if (outputStream.size() <= 0) {
//                throw new UserException(UserProblem.CUSTOM_QUERY_IS_EMPTY, "Custom query is empty.");
//            }
//        } catch (UnsupportedEncodingException e) {
//            throw new AssertionError("shit happens 1!");
//        } catch (IOException e) {
//            throw new AssertionError("shit happens 2!");
//        }
//
//        // Remove trailing spaces
//        while (sb.toString().endsWith(" ")) {
//            sb.deleteCharAt(sb.length() - 1);
//        }
//
//        log.debug("Query after standardize: '" + sb.toString() + "'");
//
//        return sb.toString();
//    }

    public String standardizeQuery(String query) throws InternalException, UserException {
        log.debug("Query before standardize: '" + query + "'");
        StringBuilder sb = new StringBuilder(query.length());
        int position = 0;
        // Skip leading white spaces
        while(position < query.length() && Character.isWhitespace(query.charAt(position))) {
            position++;
        }

        boolean whitespaceBlock = false;
        for (; position < query.length(); position++) {
            if (Character.isWhitespace(query.charAt(position))) {
                whitespaceBlock = true;
            } else {
                if (whitespaceBlock) {
                    sb.append(' ');
                    whitespaceBlock = false;
                }
                sb.append(Character.toLowerCase(query.charAt(position)));
            }
        }
        String newQuery = sb.toString();
        if (newQuery.length() > MAX_BYTES_FOR_QUERY) {
            throw new UserException(UserProblem.CUSTOM_QUERY_IS_TOO_LARGE,
                    "Custom query has " + newQuery.length() + " chars, while max size is " + MAX_BYTES_FOR_QUERY + " chars.");
        }
        if (newQuery.length() <= 0) {
            throw new UserException(UserProblem.CUSTOM_QUERY_IS_EMPTY, "Custom query is empty.");
        }

        log.debug("Query after standardize: '" + newQuery + "'");
        return newQuery;
    }

//    private StringBuilder replaceAll(StringBuilder sb, String bad, String good) {
//        while (!Thread.interrupted()) {
//            int start = sb.indexOf(bad);
//            if (start < 0) {
//                break;
//            }
//            sb = sb.replace(start, start + bad.length(), good);
//        }
//
//        return sb;
//    }

    public static boolean existsInKeyRegions(Integer region, List<RegionInfo> keyRegions) {
        if (region == null || region == 0) {
            return true;
        }

        boolean found = false;
        for (RegionInfo info : keyRegions) {
            if (info.getId() == region) {
                found = true;
                break;
            }
        }

        return found;
    }

    /**
     * Приводит дату к ближайшей верхней границе периодов.
     *
     * @param date          Дата, указанная пользователем
     * @param rightBorders  список правых границ диапазонов, отсортированный по возрастанию дат
     * @return              правую границу диапазона, включающего дату или null, если такого диапазона нет
     */
    public static Date getRightBorderOfTimePeriod(Date date, List<Date> rightBorders) {
        if (date == null) {
            return null;
        }

        if (rightBorders.isEmpty()) {
            return null;
        } else {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(rightBorders.iterator().next());
            calendar.add(Calendar.DATE, -6);
            final Date minDate = calendar.getTime();
            final Date maxDate = rightBorders.get(rightBorders.size() - 1);
            if (date.before(minDate) || date.after(maxDate)) {
                return null;
            } else if (date.equals(minDate)) {
                return rightBorders.iterator().next();
            } else {
                Date prev = minDate;
                for (Date d : rightBorders) {
                    if (date.after(prev) && (date.before(d) || date.equals(d))) {
                        return d;
                    }
                    prev = d;
                }
                return null;
            }
        }
    }

    public HostRegionsInfo tryToUseHomeRegion(HostInfoType hostInfoType, List<RegionInfo> keyRegions) throws InternalException {
        HostRegionsInfo hostRegionsInfo = getVisibleRegionsForHost(hostInfoType);
        if ((hostRegionsInfo == null) || (hostRegionsInfo.getRegions() == null) || (hostRegionsInfo.getRegions().size() != 1)) {
            return null;
        }

        Integer defaultRegionId = hostRegionsInfo.getRegions().iterator().next().getRegionInfo().getId();

        if (Integer.valueOf(0).equals(defaultRegionId)) {
            hostRegionsInfo = null;
        }

        if (!existsInKeyRegions(defaultRegionId, keyRegions)) {
            hostRegionsInfo = null;
        }

        return hostRegionsInfo;
    }

    abstract protected HostRegionsInfo getVisibleRegionsForHost(HostInfoType hostInfoType) throws InternalException;

    public List<Date> getKeyRegionsChangeDates(Map<Date, List<RegionInfo>> keyRegionsForDates) {
        List<Date> allDates = new ArrayList<Date>(keyRegionsForDates.keySet());
        Collections.sort(allDates);

        Set<RegionInfo> previous = null;

        List<Date> resultDates = new ArrayList<Date>();

        for (Date date : allDates) {
            Set<RegionInfo> current = new HashSet<RegionInfo>(keyRegionsForDates.get(date));
            removeBadRegions(current);
            if (!current.equals(previous)) {
                previous = current;
                resultDates.add(date);
            }
        }
        return resultDates;
    }

    public Map<Date, List<RegionInfo>> getKeyRegionsForAllTime(HostInfoType hostInfoType) throws InternalException {
        Map<Date, List<RegionInfo>> result = new HashMap<Date, List<RegionInfo>>();

        List<Date> allDates = getTopInfoDatesByHost(hostInfoType);

        for (Date date : allDates) {
            result.put(date, getCustomRegionService().getKeyRegionsForHostAndDate(hostInfoType, date));
        }
        return result;
    }

    protected void removeBadRegions(Collection<RegionInfo> regions) {
        for (Iterator<RegionInfo> it = regions.iterator(); it.hasNext();) {
            RegionInfo info = it.next();
            if (info.getId() == RegionInfo.zeroRegion.getId() || info.getId() == RegionInfo.wholeWorldRegion.getId()) {
                it.remove();
            }
        }
    }

    abstract protected <T> NavigableMap<Date, T> getHistoryGraph(final Integer region,
                                                                 final List<RegionInfo> keyRegions,
                                                                 final HostInfoType hostInfoType,
                                                                 final long queryId,
                                                                 final String queryString,
                                                                 final ParameterizedMapRowMapper<Date, T> mapper,
                                                                 final boolean includeSubregions) throws InternalException;

    public List<TopQueryHistoryInfo> getQueriesHistoryInfo(Integer region, TopQueryHistoryTypeEnum type, List<RegionInfo> keyRegions, HostInfoType hostInfoType, int count, boolean includeSubregions, List<Long> queryIds, Map<Date, List<RegionInfo>> keyRegionsForDate) throws InternalException {
        if (queryIds.isEmpty()) {
            return Collections.emptyList();
        }

        List<Date> validDates = new ArrayList<Date>();
        List<RegionInfo> regionsForRequest = new ArrayList<RegionInfo>();
        getDatesAndRegionsForRequest(validDates, regionsForRequest, region, keyRegions, includeSubregions, keyRegionsForDate);

        return executeGetQueriesHistoryInfoQuery(type, region, hostInfoType, count, includeSubregions, queryIds, validDates, regionsForRequest);
    }

    abstract protected List<TopQueryHistoryInfo> executeGetQueriesHistoryInfoQuery(TopQueryHistoryTypeEnum type, Integer region, HostInfoType hostInfoType, int count, boolean includeSubregions, List<Long> queryIds, List<Date> validDates, List<RegionInfo> regionsForRequest) throws InternalException;
}
