package ru.yandex.wmconsole.service;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
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 org.springframework.beans.factory.annotation.Required;
import org.springframework.jdbc.core.simple.ParameterizedRowMapper;
import org.springframework.transaction.TransactionStatus;

import ru.yandex.common.framework.pager.Pager;
import ru.yandex.common.util.collections.Pair;
import ru.yandex.common.util.db.IntegerRowMapper;
import ru.yandex.common.util.db.LongRowMapper;
import ru.yandex.common.util.db.OrderByClause;
import ru.yandex.wmconsole.data.info.HostDbHostInfo;
import ru.yandex.wmconsole.data.info.WMCTopInfo;
import ru.yandex.wmconsole.data.info.WMCTopInfoItem;
import ru.yandex.wmconsole.data.partition.WMCPartition;
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.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.service.TopInfoService;
import ru.yandex.wmtools.common.service.TopTypeEnum;
import ru.yandex.wmtools.common.util.ParameterizedMapRowMapper;
import ru.yandex.wmtools.common.util.ServiceTransactionCallbackWithoutResult;
import ru.yandex.wmtools.common.util.SqlUtil;

public class WMCTopInfoService extends TopInfoService<HostDbHostInfo, WMCTopInfo, WMCTopInfoItem, TotalsInfo> {
    private static final Logger log = LoggerFactory.getLogger(WMCTopInfoService.class);

    // TODO: get limits from code instead of constants
    private static final int TOP_QUERIES_COUNT = 25;
    private static final int MAX_CUSTOM_QUERIES_FOR_HOST = 500;
    private static final int MAX_QUERY_SIZE = 1000000;

    public static final String FIELD_COUNT = "count";
    public static final String FIELD_VALUE = "value";
    public static final String FIELD_PART = "part";
    public static final String FIELD_POSITION = "position";
    public static final String FIELD_QUERY = "query";
    public static final String FIELD_SHOWS = "shows";
    public static final String FIELD_CLICKS = "clicks";
    public static final String FIELD_SHOWS_POS_WEIGHTED = "shows_pos_weighted";
    public static final String FIELD_CLICKS_POS_WEIGHTED = "clicks_pos_weighted";
    public static final String FIELD_TOTAL_SHOWS = "total_shows";
    public static final String FIELD_TOTAL_CLICKS = "total_clicks";
    public static final String FIELD_DAY = "day";
    public static final String FIELD_QUERY_ID = "query_id";
    public static final String FIELD_HOST_ID = "host_id";
    public static final String FIELD_REGION = "region";
    public static final String FIELD_DIVISOR = "posdivisor";
    public static final String FIELD_CLICKS_COUNT = "clicks_count";
    public static final String FIELD_SHOWS_COUNT = "shows_count";
    public static final String FIELD_CUSTOM_QUERY_SHOWS_TOP_RANK = "custom_query_shows_top_rank";
    public static final String FIELD_CUSTOM_QUERY_CLICKS_TOP_RANK = "custom_query_clicks_top_rank";
    public static final String FIELD_CUSTOM_QUERY_SHOWS_TOP_RANK_INCL = "custom_query_shows_top_rank_incl";
    public static final String FIELD_CUSTOM_QUERY_CLICKS_TOP_RANK_INCL = "custom_query_clicks_top_rank_incl";
    public static final String FIELD_IS_CUSTOM = "is_custom";

    private HostRegionService hostRegionService;

    private static final String SELECT_CUSTOMQUERY_HISTORY_QUERY =
            "SELECT " +
                    "   host_id AS " + FIELD_HOST_ID + ", " +
                    "   day AS " + FIELD_DAY + ", " +
                    "   region_id AS " + FIELD_REGION + ", " +
                    "   shows_toprank AS " + FIELD_CUSTOM_QUERY_SHOWS_TOP_RANK + ", " +
                    "   clicks_toprank AS " + FIELD_CUSTOM_QUERY_CLICKS_TOP_RANK + ", " +
                    "   shows_toprank_incl AS " + FIELD_CUSTOM_QUERY_SHOWS_TOP_RANK_INCL + ", " +
                    "   clicks_toprank_incl AS " + FIELD_CUSTOM_QUERY_CLICKS_TOP_RANK_INCL + ", " +
                    "   query AS " + FIELD_QUERY + " " +
                    "FROM " +
                    "   tbl_customquery_history ch " +
                    "JOIN " +
                    "   tbl_queries q " +
                    "ON " +
                    "   ch.query_id = q.query_id " +
                    "WHERE " +
                    "   query IS NOT NULL " +
                    "AND " +
                    "   host_id = ?";

    private static final String INSERT_CUSTOMQUERY_HISTORY_QUERY =
            "INSERT IGNORE INTO " +
                    "   tbl_customquery_history " +
                    "(host_id, day, region_id, query_id, shows_toprank, clicks_toprank, shows_toprank_incl, clicks_toprank_incl) " +
                    "VALUES ";

    private static final String INSERT_CUSTOMQUERY_HISTORY_VALUES =
            "(?, ?, ?, ?, ?, ?, ?, ?)";

    private static final String SELECT_QUERY_STATS_QUERY =
            "SELECT " +
                    "   host_id AS " + FIELD_HOST_ID + ", " +
                    "   day AS " + FIELD_DAY + ", " +
                    "   region_id AS " + FIELD_REGION + ", " +
                    "   shows AS " + FIELD_SHOWS + ", " +
                    "   clicks AS " + FIELD_CLICKS + ", " +
                    "   shows_pos_weighted AS " + FIELD_SHOWS_POS_WEIGHTED + ", " +
                    "   clicks_pos_weighted AS " + FIELD_CLICKS_POS_WEIGHTED + ", " +
                    "   query AS " + FIELD_QUERY + " " +
                    "FROM " +
                    "   tbl_query_stats qs " +
                    "JOIN " +
                    "   tbl_queries q " +
                    "ON " +
                    "   qs.query_id = q.query_id " +
                    "WHERE " +
                    "   query IS NOT NULL " +
                    "AND " +
                    "   host_id = ?";

    private static final String INSERT_QUERY_STATS_QUERY =
            "INSERT IGNORE INTO " +
                    "   tbl_query_stats " +
                    "(host_id, day, region_id, query_id, shows, clicks, shows_pos_weighted, clicks_pos_weighted)" +
                    "VALUES ";

    private static final String INSERT_QUERY_STATS_VALUES =
            "(?, ?, ?, ?, ?, ?, ?, ?)";

    private static final String SELECT_TOTAL_QUERIES_STATS_QUERY =
            "SELECT " +
                    "   host_id AS " + FIELD_HOST_ID + ", " +
                    "   day AS " + FIELD_DAY + ", " +
                    "   region_id AS " + FIELD_REGION + ", " +
                    "   shows_count AS " + FIELD_SHOWS_COUNT + ", " +
                    "   clicks_count AS " + FIELD_CLICKS_COUNT + " " +
                    "FROM " +
                    "   tbl_total_queries_stats " +
                    "WHERE " +
                    "   host_id = ?";

    private static final String INSERT_TOTAL_QUERIES_STATS_QUERY =
            "INSERT IGNORE INTO " +
                    "   tbl_total_queries_stats " +
                    "(host_id, day, region_id, shows_count, clicks_count)" +
                    "VALUES ";

    private static final String INSERT_TOTAL_QUERIES_STATS_VALUES =
            "(?, ?, ?, ?, ?)";

    private static final String SELECT_CUSTOM_QUERIES_LIST_QUERY =
            "SELECT " +
                    "    hq.query_id AS " + FIELD_QUERY_ID + ", " +
                    "    q.query AS " + FIELD_QUERY + " " +
                    "FROM " +
                    "    tbl_custom_queries hq " +
                    "LEFT JOIN " +
                    "    tbl_queries q " +
                    "ON " +
                    "    q.query_id = hq.query_id " +
                    "WHERE " +
                    "    hq.host_id = ? " +
                    "ORDER BY " + FIELD_QUERY_ID;

    private static final String SELECT_CUSTOM_QUERIES_PAGED_LIST_QUERY =
            "SELECT " +
                    "    hq.query_id AS " + FIELD_QUERY_ID + ", " +
                    "    q.query AS " + FIELD_QUERY + " " +
                    "FROM " +
                    "    tbl_custom_queries hq " +
                    "LEFT JOIN " +
                    "    tbl_queries q " +
                    "ON " +
                    "    q.query_id = hq.query_id " +
                    "WHERE " +
                    "    hq.host_id = ? " +
                    " %1$s " +
                    " %2$s ";

    private static final String SELECT_CUSTOM_QUERIES_COUNT_QUERY =
            "SELECT " +
                    "    count(*) " +
                    "FROM " +
                    "    tbl_custom_queries " +
                    "WHERE " +
                    "    host_id = ? ";

    private static final String SELECT_QUERY_ID_FOR_QUERY_QUERY =
            "SELECT " +
                    "    q.query_id AS " + FIELD_QUERY_ID + " " +
                    "FROM " +
                    "    tbl_queries q " +
                    "WHERE " +
                    "    q.query = ? ";

    private static final String SELECT_QUERIES_IDS_FOR_QUERIES_QUERY =
            "SELECT " +
                    "    q.query_id AS " + FIELD_QUERY_ID + ", " +
                    "    q.query AS " + FIELD_QUERY + " " +
                    "FROM " +
                    "    tbl_queries q " +
                    "WHERE " +
                    "    q.query IN(%1$s) ";

    private static final String DELETE_CUSTOM_QUERIES_QUERY =
            "DELETE FROM " +
                    "    tbl_custom_queries " +
                    "WHERE " +
                    "    host_id = ? " +
                    "AND " +
                    "    query_id IN (%1$s) ";

    private static final String INSERT_CUSTOM_QUERY_QUERY =
            "INSERT IGNORE INTO " +
                    "    tbl_custom_queries (host_id, query_id) " +
                    "VALUES (?, ?) ";

    private static final String INSERT_CUSTOM_QUERIES_QUERY =
            "INSERT IGNORE INTO " +
                    "    tbl_custom_queries (host_id, query_id) " +
                    "VALUES %1$s";

    private static final String INSERT_QUERY_QUERY =
            "INSERT IGNORE INTO " +
                    "    tbl_queries (query) " +
                    "VALUES (?) ";

    private static final String INSERT_QUERIES_QUERY =
            "INSERT IGNORE INTO " +
                    "    tbl_queries (query) " +
                    "VALUES %1$s ";

    private static final String SELECT_TOP_INFO_DAYS_BY_HOST_QUERY =
            "SELECT " +
                    "    DISTINCT qs.day AS " + FIELD_DAY + " " +
                    "FROM " +
                    "    tbl_total_queries_stats qs " +
                    "WHERE " +
                    "    qs.host_id = ? " +
                    "    %1$s " +
                    "ORDER BY " + FIELD_DAY;

    private static final String SELECT_TOP_AND_CUSTOM_INFO_DAYS_BY_HOST_QUERY =
            "SELECT " +
                    "    DISTINCT day AS " + FIELD_DAY + " " +
                    "FROM " +
                    "(SELECT DISTINCT qs.day AS " + FIELD_DAY + " " +
                    "FROM " +
                    "    tbl_total_queries_stats qs " +
                    "WHERE " +
                    "    qs.host_id = ? " +
                    "    %1$s ) tqs " +
                    " UNION " +
                    "(SELECT DISTINCT qs.day AS " + FIELD_DAY + " " +
                    "FROM " +
                    "    tbl_customquery_history qs " +
                    "WHERE " +
                    "    qs.host_id = ? " +
                    "    %1$s ) " +
                    "ORDER BY " + FIELD_DAY;

    //note: using alias "qs" because method getWhereClauseForKeyRegions uses it.
    private static final String SELECT_CUSTOM_INFO_DAYS_BY_HOST_QUERY =
            "SELECT " +
                    "    DISTINCT qs.day AS " + FIELD_DAY + " " +
                    "FROM " +
                    "    tbl_customquery_history qs " +
                    "WHERE " +
                    "    qs.host_id = ? " +
                    "AND " +
                    "    qs.day IN (" +
                    SELECT_TOP_INFO_DAYS_BY_HOST_QUERY +
                    ")" +
                    "    %1$s " +
                    "ORDER BY " + FIELD_DAY;

    //All dates of updates which are between first and last dates of updates for specified host
    private static final String SELECT_TOP_INFO_UPDATE_DAYS_BY_HOST_QUERY =
            "SELECT " +
                    " DISTINCT qs.day AS " + FIELD_DAY + " " +
                    " FROM " +
                    "     tbl_total_queries_stats qs " +
                    " WHERE " +
                    "    qs.day >= " +
                    "    (SELECT DISTINCT day " +
                    "        FROM " +
                    "            tbl_total_queries_stats " +
                    "        WHERE " +
                    "            host_id = ? " +
                    "            %1$s " +
                    "        ORDER BY day ASC " +
                    "        LIMIT 1) " +
                    " AND " +
                    "    qs.day <= " +
                    "    (SELECT DISTINCT day " +
                    "        FROM " +
                    "            tbl_total_queries_stats " +
                    "        WHERE " +
                    "            host_id = ? " +
                    "            %1$s " +
                    "        ORDER BY day DESC " +
                    "        LIMIT 1) " +
                    " ORDER BY " + FIELD_DAY;

    private static final String SELECT_CUSTOM_QUERIES_HISTORY_INFO_QUERY =
            "    SELECT " +
                    "        cqh.host_id AS " + FIELD_HOST_ID + ", " +
                    "        cqh.day AS " + FIELD_DAY + ", " +
                    "        t.query_id AS " + FIELD_QUERY_ID + ", " +
                    "        q.query AS " + FIELD_QUERY + ", " +
                    "        1 AS " + FIELD_IS_CUSTOM + ", " +
                    "        cqh.shows_toprank%4$s AS " + FIELD_CUSTOM_QUERY_SHOWS_TOP_RANK + ", " +
                    "        cqh.clicks_toprank%4$s AS " + FIELD_CUSTOM_QUERY_CLICKS_TOP_RANK + ", " +
                    "        SUM(qs.shows) AS " + FIELD_SHOWS + ", " +
                    "        SUM(qs.clicks) AS " + FIELD_CLICKS + ", " +
                    "        SUM(qs.shows_pos_weighted) / SUM(qs.shows) AS " + FIELD_POSITION + " " +
                    "    FROM " +
                    "        (" +
                    "               SELECT DISTINCT(query_id) FROM" +
                    "               tbl_customquery_history " +
                    "               WHERE host_id = ? AND day = ? AND NOT (query_id = -1) " +
                    "        ) t " +
                    "    LEFT JOIN " +
                    "        tbl_customquery_history cqh " +
                    "    ON " +
                    "        cqh.query_id = t.query_id " +
                    "    AND " +
                    "        cqh.host_id = ? " +
                    "    AND " +
                    "        cqh.day = ? " +
                    "    AND " +
                    "        cqh.region_id = ? " +
                    "    LEFT JOIN " +
                    "        tbl_queries q " +
                    "    ON " +
                    "        t.query_id = q.query_id " +
                    "    LEFT JOIN " +
                    "        tbl_query_stats qs " +
                    "    ON " +
                    "        cqh.host_id = qs.host_id " +
                    "    AND " +
                    "        cqh.query_id = qs.query_id " +
                    "    AND " +
                    "        cqh.day = qs.day " +
                    "    %1$s " +
                    "    GROUP BY " +
                    "        t.query_id " +
                    "    %2$s " +
                    "    %3$s ";

    private static final String SELECT_CUSTOM_QUERIES_HISTORY_COUNT_QUERY =
            "    SELECT " +
                    "    count(distinct(t.query_id)) " +
                    "    FROM " +
                    "        (" +
                    "               SELECT DISTINCT(query_id) FROM" +
                    "               tbl_customquery_history " +
                    "               WHERE host_id = ? AND day = ? AND NOT (query_id = -1) " +
                    "        ) t " +
                    "    LEFT JOIN " +
                    "        tbl_customquery_history cqh " +
                    "    ON " +
                    "        cqh.query_id = t.query_id " +
                    "    AND " +
                    "        cqh.host_id = ? " +
                    "    AND " +
                    "        cqh.day = ? " +
                    "    AND " +
                    "        cqh.region_id = ? " +
                    "    LEFT JOIN " +
                    "        tbl_queries q " +
                    "    ON " +
                    "        t.query_id = q.query_id " +
                    "    LEFT JOIN " +
                    "        tbl_query_stats qs " +
                    "    ON " +
                    "        cqh.host_id = qs.host_id " +
                    "    AND " +
                    "        cqh.query_id = qs.query_id " +
                    "    AND " +
                    "        cqh.day = qs.day " +
                    "    %1$s ";

    private static final String SELECT_TOP_CUSTOM_SHOWS_INFO_QUERY =
            "    SELECT " +
                    "        cqh.host_id AS " + FIELD_HOST_ID + ", " +
                    "        cqh.day AS " + FIELD_DAY + ", " +
                    "        t.query_id AS " + FIELD_QUERY_ID + ", " +
                    "        q.query AS " + FIELD_QUERY + ", " +
                    "        1 AS " + FIELD_IS_CUSTOM + ", " +
                    "        cqh.shows_toprank%2$s AS " + FIELD_CUSTOM_QUERY_SHOWS_TOP_RANK + ", " +
                    "        cqh.clicks_toprank%2$s AS " + FIELD_CUSTOM_QUERY_CLICKS_TOP_RANK + ", " +
                    "        SUM(qs.shows) AS " + FIELD_SHOWS + ", " +
                    "        SUM(qs.clicks) AS " + FIELD_CLICKS + ", " +
                    "        SUM(qs.shows_pos_weighted) / SUM(qs.shows) AS " + FIELD_POSITION + " " +
                    "    FROM " +
                    "        (" +
                    "               SELECT DISTINCT(query_id) FROM" +
                    "               tbl_customquery_history " +
                    "               WHERE host_id = ? AND day = ? AND NOT (query_id = -1) " +
                    "        ) t " +
                    "    LEFT JOIN " +
                    "        tbl_customquery_history cqh " +
                    "    ON " +
                    "        cqh.query_id = t.query_id " +
                    "    AND " +
                    "        cqh.host_id = ? " +
                    "    AND " +
                    "        cqh.day = ? " +
                    "    AND " +
                    "        cqh.region_id = ? " +
                    "    LEFT JOIN " +
                    "        tbl_queries q " +
                    "    ON " +
                    "        t.query_id = q.query_id " +
                    "    LEFT JOIN " +
                    "        tbl_query_stats qs " +
                    "    ON " +
                    "        cqh.host_id = qs.host_id " +
                    "    AND " +
                    "        cqh.query_id = qs.query_id " +
                    "    AND " +
                    "        cqh.day = qs.day " +
                    "    AND " +
                    "        qs.shows > 0 " +
                    "    WHERE " +
                    "        cqh.shows_toprank%2$s < " + TOP_QUERIES_COUNT +
                    "    %1$s " +
                    "    GROUP BY " +
                    "        t.query_id " +
                    "    ORDER BY " + FIELD_CUSTOM_QUERY_SHOWS_TOP_RANK + " desc ";

    private static final String SELECT_TOP_SHOWS_INFO_QUERY =
            "    SELECT " +
                    "        qs.host_id AS " + FIELD_HOST_ID + ", " +
                    "        qs.day AS " + FIELD_DAY + ", " +
                    "        qs.query_id AS " + FIELD_QUERY_ID + ", " +
                    "        q.query AS " + FIELD_QUERY + ", " +
                    "        0 AS " + FIELD_IS_CUSTOM + ", " +
                    "        NULL AS " + FIELD_CUSTOM_QUERY_SHOWS_TOP_RANK + ", " +
                    "        NULL AS " + FIELD_CUSTOM_QUERY_CLICKS_TOP_RANK + ", " +
                    "        SUM(qs.shows) AS " + FIELD_SHOWS + ", " +
                    "        SUM(qs.clicks) AS " + FIELD_CLICKS + ", " +
                    "        SUM(qs.shows_pos_weighted) / SUM(qs.shows) AS " + FIELD_POSITION + " " +
                    "    FROM " +
                    "        tbl_query_stats qs " +
                    "    JOIN " +
                    "        tbl_queries q " +
                    "    ON " +
                    "        qs.query_id = q.query_id " +
                    "    WHERE " +
                    "        qs.host_id = ? " +
                    "        %1$s " +
                    "    AND " +
                    "        qs.day = ? " +
                    "    GROUP BY " +
                    "        qs.query_id " +
                    "    HAVING " +
                    "        " + FIELD_SHOWS + " > 0 " +
                    "    ORDER BY " + FIELD_SHOWS + " desc " +
                    "    LIMIT " + TOP_QUERIES_COUNT + " ";

    private static final String SELECT_TOP_CUSTOM_CLICKS_INFO_QUERY =
            "    SELECT " +
                    "        cqh.host_id AS " + FIELD_HOST_ID + ", " +
                    "        cqh.day AS " + FIELD_DAY + ", " +
                    "        t.query_id AS " + FIELD_QUERY_ID + ", " +
                    "        q.query AS " + FIELD_QUERY + ", " +
                    "        1 AS " + FIELD_IS_CUSTOM + ", " +
                    "        cqh.shows_toprank%2$s AS " + FIELD_CUSTOM_QUERY_SHOWS_TOP_RANK + ", " +
                    "        cqh.clicks_toprank%2$s AS " + FIELD_CUSTOM_QUERY_CLICKS_TOP_RANK + ", " +
                    "        SUM(qs.shows) AS " + FIELD_SHOWS + ", " +
                    "        SUM(qs.clicks) AS " + FIELD_CLICKS + ", " +
                    "        SUM(qs.clicks_pos_weighted) / SUM(qs.clicks) AS " + FIELD_POSITION + " " +
                    "    FROM " +
                    "        (" +
                    "               SELECT DISTINCT(query_id) FROM" +
                    "               tbl_customquery_history " +
                    "               WHERE host_id = ? AND day = ? AND NOT (query_id = -1) " +
                    "        ) t " +
                    "    LEFT JOIN " +
                    "        tbl_customquery_history cqh " +
                    "    ON " +
                    "        cqh.query_id = t.query_id " +
                    "    AND " +
                    "        cqh.host_id = ? " +
                    "    AND " +
                    "        cqh.day = ? " +
                    "    AND " +
                    "        cqh.region_id = ? " +
                    "    LEFT JOIN " +
                    "        tbl_queries q " +
                    "    ON " +
                    "        t.query_id = q.query_id " +
                    "    LEFT JOIN " +
                    "        tbl_query_stats qs " +
                    "    ON " +
                    "        cqh.host_id = qs.host_id " +
                    "    AND " +
                    "        cqh.query_id = qs.query_id " +
                    "    AND " +
                    "        cqh.day = qs.day " +
                    "    AND " +
                    "        qs.clicks > 0 " +
                    "    WHERE " +
                    "        cqh.clicks_toprank%2$s < " + TOP_QUERIES_COUNT +
                    "    %1$s " +
                    "    GROUP BY " +
                    "        t.query_id " +
                    "    ORDER BY " + FIELD_CUSTOM_QUERY_CLICKS_TOP_RANK + " desc ";

    private static final String SELECT_TOP_CLICKS_INFO_QUERY =
            "    SELECT " +
                    "        qs.host_id AS " + FIELD_HOST_ID + ", " +
                    "        qs.day AS " + FIELD_DAY + ", " +
                    "        qs.query_id AS " + FIELD_QUERY_ID + ", " +
                    "        q.query AS " + FIELD_QUERY + ", " +
                    "        0 AS " + FIELD_IS_CUSTOM + ", " +
                    "        NULL AS " + FIELD_CUSTOM_QUERY_SHOWS_TOP_RANK + ", " +
                    "        NULL AS " + FIELD_CUSTOM_QUERY_CLICKS_TOP_RANK + ", " +
                    "        SUM(qs.shows) AS " + FIELD_SHOWS + ", " +
                    "        SUM(qs.clicks) AS " + FIELD_CLICKS + ", " +
                    "        SUM(qs.clicks_pos_weighted) / SUM(qs.clicks) AS " + FIELD_POSITION + " " +
                    "    FROM " +
                    "        tbl_query_stats qs " +
                    "    JOIN " +
                    "        tbl_queries q " +
                    "    ON " +
                    "        qs.query_id = q.query_id " +
                    "    WHERE " +
                    "        qs.host_id = ? " +
                    "        %1$s " +
                    "    AND " +
                    "        qs.day = ? " +
                    "    GROUP BY " +
                    "        qs.query_id " +
                    "    HAVING " +
                    "        " + FIELD_CLICKS + " > 0 " +
                    "    ORDER BY " + FIELD_CLICKS + " desc " +
                    "    LIMIT " + TOP_QUERIES_COUNT + " ";

    private static final String SELECT_TOP_QUERIES_LIST_QUERY =
            "    ( " +
                    "    SELECT " +
                    "        query_id AS " + FIELD_QUERY_ID + ", " +
                    "        SUM(clicks) AS " + FIELD_CLICKS + " " +
                    "    FROM " +
                    "        tbl_query_stats " +
                    "    WHERE " +
                    "        host_id = ? " +
                    "        %1$s " +
                    "    AND " +
                    "        day = ? " +
                    "    GROUP BY " +
                    "        query_id " +
                    "    HAVING " +
                    "        " + FIELD_CLICKS + " > 0 " +
                    "    ORDER BY " + FIELD_CLICKS + " desc " +
                    "    LIMIT " + TOP_QUERIES_COUNT + " " +
                    " ) " +
                    " UNION " +
                    " (" +
                    "   SELECT " +
                    "        query_id AS " + FIELD_QUERY_ID + ", " +
                    "        SUM(shows) AS " + FIELD_SHOWS + " " +
                    "    FROM " +
                    "        tbl_query_stats " +
                    "    WHERE " +
                    "        host_id = ? " +
                    "        %1$s " +
                    "    AND " +
                    "        day = ? " +
                    "    GROUP BY " +
                    "        query_id " +
                    "    HAVING " +
                    "        " + FIELD_SHOWS + " > 0 " +
                    "    ORDER BY " + FIELD_SHOWS + " desc " +
                    "    LIMIT " + TOP_QUERIES_COUNT + " " +
                    " ) ";

    private static final String SELECT_TOP_TOTALS_QUERY =
            "SELECT " +
                    "    SUM(qs.shows_count) AS " + FIELD_TOTAL_SHOWS + ", " +
                    "    SUM(qs.clicks_count) AS " + FIELD_TOTAL_CLICKS + " " +
                    "FROM " +
                    "    tbl_total_queries_stats qs " +
                    "WHERE " +
                    "    host_id = ? " +
                    "  %1$s " +
                    "  AND " +
                    "    day = ? ";

    private static final String SELECT_TOP_SHOWS_QUERY_HISTORY_QUERY =
            "SELECT " +
                    "    qs.day AS " + FIELD_DAY + ", " +
                    "    SUM(qs.shows) AS " + FIELD_COUNT + " " +
                    "FROM " +
                    "    tbl_query_stats qs " +
                    "WHERE " +
                    "    qs.host_id = ? " +
                    "  %1$s " +
                    "  AND " +
                    "    qs.query_id = ? " +
                    "    %2$s " +
                    "GROUP BY " +
                    "    qs.day";


    private static final String SELECT_TOP_CLICKS_QUERY_HISTORY_QUERY =
            "SELECT " +
                    "    qs.day AS " + FIELD_DAY + ", " +
                    "    SUM(qs.clicks) AS " + FIELD_COUNT + " " +
                    "FROM " +
                    "    tbl_query_stats qs " +
                    "WHERE " +
                    "    qs.host_id = ? " +
                    "  %1$s " +
                    "  AND " +
                    "    qs.query_id = ? " +
                    "    %2$s " +
                    "GROUP BY " +
                    "    qs.day";

    private static final String SELECT_TOP_QUERY_CTR_HISTORY_QUERY =
            "SELECT " +
                    "    qs.day AS " + FIELD_DAY + ", " +
                    "    (SUM(qs.clicks) / SUM(qs.shows)) AS " + FIELD_VALUE + " " +
                    "FROM " +
                    "    tbl_query_stats qs " +
                    "WHERE " +
                    "    qs.host_id = ? " +
                    "  %1$s " +
                    "  AND " +
                    "    qs.query_id = ? " +
                    "    %2$s " +
                    "GROUP BY " +
                    "    qs.day";

    private static final String SELECT_TOP_QUERY_POSITION_HISTORY_QUERY =
            "SELECT " +
                    "    qs.day AS " + FIELD_DAY + ", " +
                    "    SUM(qs.shows_pos_weighted) / SUM(qs.shows) AS " + FIELD_POSITION + " " +
                    "FROM " +
                    "    tbl_query_stats qs " +
                    "WHERE " +
                    "    qs.host_id = ? " +
                    "  %1$s " +
                    "  AND " +
                    "    qs.query_id = ? " +
                    "    %2$s " +
                    "GROUP BY " +
                    "    qs.day";

    private static final String SELECT_TOP_QUERY_PART_HISTORY_QUERY =
            "SELECT " +
                    "    qs.day AS " + FIELD_DAY + ", " +
                    "    (100 * SUM(qs.clicks) / SUM(t.clicks_count)) AS " + FIELD_VALUE + " " +
                    "FROM " +
                    "    tbl_query_stats qs " +
                    "LEFT JOIN " +
                    "    tbl_total_queries_stats t " +
                    "ON " +
                    "   qs.host_id = t.host_id " +
                    "  AND " +
                    "   t.region_id = qs.region_id " +
                    "  AND " +
                    "   qs.day = t.day " +
                    "WHERE " +
                    "    qs.host_id = ? " +
                    "  %1$s " +
                    "  AND " +
                    "    qs.query_id = ? " +
                    "    %2$s " +
                    "GROUP BY " +
                    "    qs.day";

    private static final String SELECT_TOP_QUERIES_HISTORY_INFO_COUNT_QUERY =
            "SELECT count(*) " +
                    "FROM " +
                    "(SELECT " +
                    FIELD_QUERY_ID + ", " +
                    FIELD_QUERY + ", " +
                    "COUNT(" + FIELD_DAY + ") AS " + FIELD_COUNT + " " +
                    "FROM " +
                    "( " +
                    "    SELECT " +
                    "        qs.day AS " + FIELD_DAY + ", " +
                    "        q.query_id AS " + FIELD_QUERY_ID + ", " +
                    "        q.query AS " + FIELD_QUERY + ", " +
                    "        SUM(qs.shows) AS " + FIELD_SHOWS + ", " +
                    "        SUM(qs.clicks) AS " + FIELD_CLICKS + ", " +
                    "        SUM(qs.shows_pos_weighted) " + FIELD_DIVISOR + ", " +
                    "        SUM(t.clicks_count) AS " + FIELD_CLICKS_COUNT + " " +
                    "    FROM " +
                    "        tbl_queries q " +
                    "    JOIN " +
                    "        tbl_query_stats qs " +
                    "    ON " +
                    "        q.query_id = qs.query_id " +
                    "    LEFT JOIN " +
                    "        tbl_total_queries_stats t " +
                    "    ON " +
                    "        t.region_id = qs.region_id " +
                    "    AND " +
                    "        qs.host_id = t.host_id " +
                    "    AND " +
                    "        qs.day = t.day " +
                    "    LEFT JOIN" +
                    "        tbl_custom_queries cq " +
                    "    ON  " +
                    "        qs.host_id = cq.host_id " +
                    "    AND " +
                    "        qs.query_id = cq.query_id " +
                    "    WHERE " +
                    "        qs.host_id = ? " +
                    "    %1$s " +
                    "    %2$s " +
                    "    %3$s " +
                    "    GROUP BY " +
                    "        q.query_id, qs.day " +
                    ") tmp " +
                    "GROUP BY " +
                    "    query_id " +
                    "HAVING " +
                    "    count > ?) tmp2 ";

    private static final String SELECT_TOP_QUERIES_HISTORY_INFO_QUERY =
            "SELECT " +
                    FIELD_QUERY_ID + ", " +
                    FIELD_QUERY + ", " +
                    "AVG(" + FIELD_SHOWS + ") AS " + FIELD_SHOWS + ", " +
                    "AVG(" + FIELD_CLICKS + ") AS " + FIELD_CLICKS + ", " +
                    "AVG(" + FIELD_DIVISOR + " / " + FIELD_SHOWS + ") AS " + FIELD_POSITION + ", " +
                    "AVG(" + FIELD_CLICKS + " / " + FIELD_SHOWS + ") AS " + FIELD_VALUE + ", " +
                    "COUNT(" + FIELD_DAY + ") AS " + FIELD_COUNT + ", " +
                    "AVG(100 * " + FIELD_CLICKS + " / " + FIELD_CLICKS_COUNT + ") AS " + FIELD_PART + " " +
                    "FROM " +
                    "( " +
                    "    SELECT " +
                    "        qs.day AS " + FIELD_DAY + ", " +
                    "        q.query_id AS " + FIELD_QUERY_ID + ", " +
                    "        q.query AS " + FIELD_QUERY + ", " +
                    "        SUM(qs.shows) AS " + FIELD_SHOWS + ", " +
                    "        SUM(qs.clicks) AS " + FIELD_CLICKS + ", " +
                    "        SUM(qs.shows_pos_weighted) " + FIELD_DIVISOR + ", " +
                    "        SUM(t.clicks_count) AS " + FIELD_CLICKS_COUNT + " " +
                    "    FROM " +
                    "        tbl_queries q " +
                    "    JOIN " +
                    "        tbl_query_stats qs " +
                    "    ON " +
                    "        q.query_id = qs.query_id " +
                    "    LEFT JOIN " +
                    "        tbl_total_queries_stats t " +
                    "    ON " +
                    "        t.region_id = qs.region_id " +
                    "    AND " +
                    "        qs.host_id = t.host_id " +
                    "    AND " +
                    "        qs.day = t.day " +
                    "    LEFT JOIN" +
                    "        tbl_custom_queries cq " +
                    "    ON  " +
                    "        qs.host_id = cq.host_id " +
                    "    AND " +
                    "        qs.query_id = cq.query_id " +
                    "    WHERE " +
                    "        qs.host_id = ? " +
                    "    %4$s " +
                    "    %1$s " +
                    "    %5$s " +
                    "    GROUP BY " +
                    "        q.query_id, qs.day " +
                    ") tmp " +
                    "GROUP BY " +
                    "    query_id " +
                    "HAVING " +
                    "    count > ? " +
                    "    %2$s %3$s ";

    private static final String SELECT_QUERIES_WITH_HISTORY_QUERY =
            " SELECT " +
                    " DISTINCT qs.query_id AS " + FIELD_QUERY_ID +
                    " FROM " +
                    "   tbl_query_stats qs " +
                    " WHERE " +
                    "   qs.host_id = ? " +
                    "   %2$s " +
                    "   %2$s ";

    private static final String SELECT_TOP_QUERIES_HISTORY_INFO_BY_ID_QUERY =
            "SELECT " +
                    FIELD_QUERY_ID + ", " +
                    FIELD_QUERY + ", " +
                    "AVG(" + FIELD_SHOWS + ") AS " + FIELD_SHOWS + ", " +
                    "AVG(" + FIELD_CLICKS + ") AS " + FIELD_CLICKS + ", " +
                    "AVG(" + FIELD_DIVISOR + " / " + FIELD_SHOWS + ") AS " + FIELD_POSITION + ", " +
                    "AVG(" + FIELD_CLICKS + " / " + FIELD_SHOWS + ") AS " + FIELD_VALUE + ", " +
                    "COUNT(" + FIELD_DAY + ") AS " + FIELD_COUNT + ", " +
                    "AVG(100 * " + FIELD_CLICKS + " / " + FIELD_CLICKS_COUNT + ") AS " + FIELD_PART + " " +
                    "FROM " +
                    "( " +
                    "    SELECT " +
                    "        qs.day AS " + FIELD_DAY + ", " +
                    "        q.query_id AS " + FIELD_QUERY_ID + ", " +
                    "        q.query AS " + FIELD_QUERY + ", " +
                    "        SUM(qs.shows) AS " + FIELD_SHOWS + ", " +
                    "        SUM(qs.clicks) AS " + FIELD_CLICKS + ", " +
                    "        SUM(qs.shows_pos_weighted) " + FIELD_DIVISOR + ", " +
                    "        SUM(t.clicks_count) AS " + FIELD_CLICKS_COUNT + " " +
                    "    FROM " +
                    "        tbl_queries q " +
                    "    JOIN " +
                    "        tbl_query_stats qs " +
                    "    ON " +
                    "        q.query_id = qs.query_id " +
                    "    LEFT JOIN " +
                    "        tbl_total_queries_stats t " +
                    "    ON " +
                    "        t.region_id = qs.region_id " +
                    "    AND " +
                    "        qs.host_id = t.host_id " +
                    "    AND " +
                    "        qs.day = t.day " +
                    "    WHERE " +
                    "        qs.host_id = ? " +
                    "  %2$s " +
                    "  %3$s " +
                    "    AND " +
                    "        qs.query_id IN (%1$s) " +
                    "    GROUP BY " +
                    "        q.query_id, qs.day " +
                    ") tmp " +
                    "GROUP BY " +
                    "    query_id " +
                    "HAVING " +
                    "    count > ? ";

    private static final String SELECT_CHECK_FOR_ANY_DATA_QUERY =
            "SELECT " +
                    "       count(*) " +
                    "   FROM " +
                    "       tbl_customquery_history " +
                    "   WHERE " +
                    "       host_id = ? " +
                    "   UNION " +
                    "   SELECT " +
                    "       count(*) " +
                    "   FROM " +
                    "       tbl_total_queries_stats" +
                    "   WHERE " +
                    "       host_id = ? " +
                    "   UNION " +
                    "   SELECT " +
                    "       count(*) " +
                    "   FROM " +
                    "       tbl_query_stats " +
                    "   WHERE " +
                    "       host_id = ?";

    private static final ParameterizedRowMapper<WMCTopInfoItem> topInfoMapper = new ParameterizedRowMapper<WMCTopInfoItem>() {
        @Override
        public WMCTopInfoItem mapRow(ResultSet rs, int rowNum) throws SQLException {
            long queryId = rs.getLong(FIELD_QUERY_ID);
            String query = rs.getString(FIELD_QUERY);
            Integer shows = rs.getInt(FIELD_SHOWS);
            Integer clicks = rs.getInt(FIELD_CLICKS);

            Double position = rs.getDouble(FIELD_POSITION);
            if (rs.wasNull()) {
                position = null;
            }

            Integer customQueryShowsTopRank = rs.getInt(FIELD_CUSTOM_QUERY_SHOWS_TOP_RANK);
            if (rs.wasNull()) {
                customQueryShowsTopRank = null;
            }
            if (shows <= 0) {
                customQueryShowsTopRank = null;
            }

            Integer customQueryClicksTopRank = rs.getInt(FIELD_CUSTOM_QUERY_CLICKS_TOP_RANK);
            if (rs.wasNull()) {
                customQueryClicksTopRank = null;
            }
            if (clicks <= 0) {
                customQueryClicksTopRank = null;
            }

            boolean isCustom = rs.getBoolean(FIELD_IS_CUSTOM);

            return new WMCTopInfoItem(queryId, query, shows, clicks, position, isCustom, customQueryShowsTopRank, customQueryClicksTopRank, !isCustom);
        }
    };

    private class TopQueryHistoryInfoMapper implements ParameterizedRowMapper<TopQueryHistoryInfo> {
        private final TopQueryHistoryTypeEnum type;

        private TopQueryHistoryInfoMapper(TopQueryHistoryTypeEnum type) {
            this.type = type;
        }

        @Override
        public TopQueryHistoryInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
            Long queryId = rs.getLong(FIELD_QUERY_ID);
            String query = rs.getString(FIELD_QUERY);
            Long avgShows = Math.round(rs.getDouble(FIELD_SHOWS));
            Long avgClicks = Math.round(rs.getDouble(FIELD_CLICKS));
            Double avgCTR = rs.getDouble(FIELD_VALUE);
            Integer position = (int) (rs.getDouble(FIELD_POSITION) + 1);
            Integer count = rs.getInt(FIELD_COUNT);
            Double part = rs.getDouble(FIELD_PART);

            if (TopQueryHistoryTypeEnum.CLICKS.equals(type)) {
                return new TopQueryHistoryInfo<Long>(queryId, query, avgShows, avgClicks, avgCTR, position, part, count,
                        TopQueryHistoryTypeEnum.CLICKS.toString().toLowerCase(), avgClicks);
            } else if (TopQueryHistoryTypeEnum.CTR.equals(type)) {
                return new TopQueryHistoryInfo<Double>(queryId, query, avgShows, avgClicks, avgCTR, position, part, count,
                        TopQueryHistoryTypeEnum.CTR.toString().toLowerCase(), avgCTR);
            } else if (TopQueryHistoryTypeEnum.PART.equals(type)) {
                return new TopQueryHistoryInfo<Double>(queryId, query, avgShows, avgClicks, avgCTR, position, part, count,
                        TopQueryHistoryTypeEnum.PART.toString().toLowerCase(), part);
            } else if (TopQueryHistoryTypeEnum.SHOWS.equals(type)) {
                return new TopQueryHistoryInfo<Long>(queryId, query, avgShows, avgClicks, avgCTR, position, part, count,
                        TopQueryHistoryTypeEnum.SHOWS.toString().toLowerCase(), avgShows);
            } else if (TopQueryHistoryTypeEnum.POSITION.equals(type)) {
                return new TopQueryHistoryInfo<Integer>(queryId, query, avgShows, avgClicks, avgCTR, position, part, count,
                        TopQueryHistoryTypeEnum.POSITION.toString().toLowerCase(), position);
            }

            throw new AssertionError("Unknown type of TopQueryHistoryTypeEnum: " + type);
        }
    }

    private static final ParameterizedRowMapper<Date> topInfoDatesMapper = new ParameterizedRowMapper<Date>() {
        @Override
        public Date mapRow(ResultSet rs, int rowNum) throws SQLException {
            return rs.getDate(FIELD_DAY);
        }
    };

    private static final ParameterizedRowMapper<CustomQueryInfo> customQueryInfoMapper = new ParameterizedRowMapper<CustomQueryInfo>() {
        @Override
        public CustomQueryInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
            return new CustomQueryInfo(rs.getLong(FIELD_QUERY_ID), rs.getString(FIELD_QUERY));
        }
    };

    private static final ParameterizedRowMapper<TotalsInfo> topTotalsMapper = new ParameterizedRowMapper<TotalsInfo>() {
        @Override
        public TotalsInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
            return new TotalsInfo(rs.getInt(FIELD_TOTAL_SHOWS), rs.getInt(FIELD_TOTAL_CLICKS));
        }
    };

    private static final ParameterizedMapRowMapper<Date, Integer> topQueryHistoryMapper = new ParameterizedMapRowMapper<Date, Integer>() {
        @Override
        public Pair<Date, Integer> mapRow(ResultSet resultSet, int i) throws SQLException {
            return new Pair<Date, Integer>(resultSet.getDate(FIELD_DAY), resultSet.getInt(FIELD_COUNT));
        }
    };

    private static final ParameterizedMapRowMapper<Date, Double> topQueryDoubleValueHistoryMapper = new ParameterizedMapRowMapper<Date, Double>() {
        @Override
        public Pair<Date, Double> mapRow(ResultSet resultSet, int i) throws SQLException {
            return new Pair<Date, Double>(resultSet.getDate(FIELD_DAY), resultSet.getDouble(FIELD_VALUE));
        }
    };

    private static final ParameterizedMapRowMapper<Date, Integer> topQueryPositionHistoryMapper = new ParameterizedMapRowMapper<Date, Integer>() {
        @Override
        public Pair<Date, Integer> mapRow(ResultSet resultSet, int i) throws SQLException {
            return new Pair<Date, Integer>(resultSet.getDate(FIELD_DAY), (int) (resultSet.getDouble(FIELD_POSITION) + 1));
        }
    };

    protected int addQueryToUserCustomQueriesList(HostDbHostInfo hostDbHostInfo, Long queryId) throws InternalException {
        return getJdbcTemplate(new WMCPartition(hostDbHostInfo, null)).update(
                INSERT_CUSTOM_QUERY_QUERY, hostDbHostInfo.getHostDbHostId(), queryId);
    }

    @Override
    protected int addQueriesToUserCustomQueriesList(final HostDbHostInfo hostDbHostInfo, Long... queryIds) throws InternalException {
        if (queryIds.length > 0) {
            return getJdbcTemplate(new WMCPartition(hostDbHostInfo, null)).update(
                    String.format(INSERT_CUSTOM_QUERIES_QUERY, SqlUtil.getCommaSeparatedList(Arrays.asList(queryIds), new SqlUtil.ListParameterizer<Long>() {
                        @Override
                        public String getParameter(int i, Long obj) {
                            return "(" + hostDbHostInfo.getHostDbHostId() + ", " + obj + ")";
                        }

                        @Override
                        public int getParamNumber() {
                            return 1;
                        }
                    })));
        } else {
            return 0;
        }
    }

    @Override
    protected List<Long> getCustomQueriesIds(HostDbHostInfo hostDbHostInfo, List<String> standardizedQueries) throws InternalException {
        if (!standardizedQueries.isEmpty()) {
            String queryString = String.format(SELECT_QUERIES_IDS_FOR_QUERIES_QUERY, SqlUtil.createQuestionMarks(standardizedQueries.size()));
            return getJdbcTemplate(new WMCPartition(hostDbHostInfo, null)).query(queryString, new LongRowMapper(), standardizedQueries.toArray());
        } else {
            return new ArrayList<Long>();
        }
    }

    protected Map<String, Long> getCustomQueriesIdsMap(HostDbHostInfo hostDbHostInfo, Collection<String> standardizedQueries) throws InternalException {
        return getJdbcTemplate(new WMCPartition(hostDbHostInfo, null)).queryForNavigableMap(
                String.format(SELECT_QUERIES_IDS_FOR_QUERIES_QUERY,
                        SqlUtil.createQuestionMarks(standardizedQueries.size())),
                new ParameterizedMapRowMapper<String, Long>() {
                    @Override
                    public Pair<String, Long> mapRow(ResultSet resultSet, int i) throws SQLException {
                        return new Pair(resultSet.getString(FIELD_QUERY), resultSet.getLong(FIELD_QUERY_ID));
                    }
                }, standardizedQueries.toArray());
    }

    @Override
    protected void addCustomQueriesToDb(HostDbHostInfo hostDbHostInfo, List<String> standardizedQueries) throws InternalException {
        if (!standardizedQueries.isEmpty()) {
            String queryString = String.format(INSERT_QUERIES_QUERY, SqlUtil.createQuestionMarkGroups(standardizedQueries.size(), 1));
            getJdbcTemplate(new WMCPartition(hostDbHostInfo, null)).update(queryString, standardizedQueries.toArray());
        }
    }

    @Override
    protected Long getCustomQueryId(HostDbHostInfo hostDbHostInfo, String standardizedQuery) throws InternalException {
        return getJdbcTemplate(new WMCPartition(hostDbHostInfo, null)).queryForLong(SELECT_QUERY_ID_FOR_QUERY_QUERY, standardizedQuery);
    }

    @Override
    protected void addCustomQueryToDb(HostDbHostInfo hostDbHostInfo, String standardizedQuery) throws InternalException {
        getJdbcTemplate(new WMCPartition(hostDbHostInfo, null)).update(INSERT_QUERY_QUERY, standardizedQuery);
    }

    @Override
    protected int getCustomQueriesCountForHost(HostDbHostInfo hostDbHostInfo) throws InternalException {
        return getJdbcTemplate(new WMCPartition(hostDbHostInfo, null)).queryForInt(
                SELECT_CUSTOM_QUERIES_COUNT_QUERY, hostDbHostInfo.getHostDbHostId());
    }

    @Override
    protected TotalsInfo getInfoAboutTotals(HostDbHostInfo hostDbHostInfo, Date date, String whereClause) throws InternalException {
        return getJdbcTemplate(new WMCPartition(hostDbHostInfo, null)).safeQueryForObject(String.format(
                SELECT_TOP_TOTALS_QUERY, whereClause), topTotalsMapper, hostDbHostInfo.getHostDbHostId(), date);
    }

    @Override
    protected List<WMCTopInfoItem> getInfoAboutTopQueries(HostDbHostInfo hostDbHostInfo, Date date, TopTypeEnum type, String whereClause) throws InternalException {
        List<WMCTopInfoItem> topList;
        if (TopTypeEnum.CLICKS.equals(type)) {
            topList = getJdbcTemplate(new WMCPartition(hostDbHostInfo, null)).query(String.format(
                    SELECT_TOP_CLICKS_INFO_QUERY, whereClause), topInfoMapper, hostDbHostInfo.getHostDbHostId(), date);
        } else if (TopTypeEnum.SHOWS.equals(type)) {
            topList = getJdbcTemplate(new WMCPartition(hostDbHostInfo, null)).query(String.format(
                    SELECT_TOP_SHOWS_INFO_QUERY, whereClause), topInfoMapper, hostDbHostInfo.getHostDbHostId(), date);
        } else {
            throw new AssertionError("Unknown value of TopTypeEnum: " + type);
        }
        return topList;
    }

    private void setActualHasHistoryValues(List<WMCTopInfoItem> list, HostDbHostInfo hostDbHostInfo, String whereClause) throws InternalException {
        List<Long> queries = new ArrayList<Long>();
        Map<Long, WMCTopInfoItem> ids2Info = new HashMap<Long, WMCTopInfoItem>();
        for (WMCTopInfoItem info : list) {
            queries.add(info.getQueryId());
            ids2Info.put(info.getQueryId(), info);
        }

        queries = getQueriesWithHistoryIds(queries, hostDbHostInfo, whereClause);
        for (Long id : queries) {
            ids2Info.get(id).setHasHistory(true);
        }
    }

    @Override
    protected List<WMCTopInfoItem> getInfoAboutCustomQueries(HostDbHostInfo hostDbHostInfo, Date date, TopTypeEnum type, int regionForCustomQueryHistory, String whereClause, OrderByClause order, Pager pager, boolean includeSubregions) throws InternalException {
        List<WMCTopInfoItem> customList = getJdbcTemplate(new WMCPartition(hostDbHostInfo, null)).select(
                String.format(SELECT_CUSTOM_QUERIES_HISTORY_COUNT_QUERY, whereClause),
                String.format(SELECT_CUSTOM_QUERIES_HISTORY_INFO_QUERY, whereClause, "%1$s", "%2$s", topRankField(includeSubregions)),
                topInfoMapper,
                order,
                pager,
                hostDbHostInfo.getHostDbHostId(),
                date,
                hostDbHostInfo.getHostDbHostId(),
                date,
                regionForCustomQueryHistory);

        setActualHasHistoryValues(customList, hostDbHostInfo, whereClause);

        return customList;
    }

    private String topRankField(boolean includeSubregions) {
        return includeSubregions ? "_incl" : "";
    }

    @Override
    protected List<WMCTopInfoItem> getInfoAboutTopCustomQueries(HostDbHostInfo hostDbHostInfo, Date date, TopTypeEnum type, int regionForCustomQueryHistory, String whereClause, boolean includeSubregions) throws InternalException {
        List<WMCTopInfoItem> customList;
        if (TopTypeEnum.CLICKS.equals(type)) {
//            log.debug(String.format(TOP_CUSTOM_CLICKS_INFO_QUERY, whereClause, topRankField(includeSubregions)).replaceFirst("\\?", String.valueOf(hostDbHostInfo.getHostDbHostId())).replaceFirst("\\?", String.valueOf(date)).replaceFirst("\\?", String.valueOf(hostDbHostInfo.getHostDbHostId())).replaceFirst("\\?", String.valueOf(date)).replaceFirst("\\?", String.valueOf(regionForCustomQueryHistory)).replaceAll("(tbl_\\w+)","$1494"));
            customList = getJdbcTemplate(new WMCPartition(hostDbHostInfo, null)).query(String.format(
                    SELECT_TOP_CUSTOM_CLICKS_INFO_QUERY, whereClause, topRankField(includeSubregions)), topInfoMapper, hostDbHostInfo.getHostDbHostId(), date, hostDbHostInfo.getHostDbHostId(), date, regionForCustomQueryHistory);
        } else if (TopTypeEnum.SHOWS.equals(type)) {
            customList = getJdbcTemplate(new WMCPartition(hostDbHostInfo, null)).query(String.format(
                    SELECT_TOP_CUSTOM_SHOWS_INFO_QUERY, whereClause, topRankField(includeSubregions)), topInfoMapper, hostDbHostInfo.getHostDbHostId(), date, hostDbHostInfo.getHostDbHostId(), date, regionForCustomQueryHistory);
        } else {
            throw new AssertionError("Unknown value of TopTypeEnum: " + type);
        }
        for (WMCTopInfoItem info : customList) {
            info.setHasHistory(true);
        }
        return customList;
    }

    protected String getWhereClauseForDays(List<Date> days, String fieldName) {
//TODO: Include subregions feature
        if (days == null) {
            return " ";
        }
        return !days.isEmpty() ? " AND " + fieldName + " IN (" + SqlUtil.getCommaSeparatedList(days, new SqlUtil.ListParameterizer<Date>() {
            @Override
            public boolean isQuotesNeeded(int i) {
                return true;
            }

            @Override
            public String getParameter(int i, Date obj) {
                return obj.toString();
            }

            @Override
            public int getParamNumber() {
                return 1;
            }
        }) + ") " : " AND FALSE ";
//        return " ";
    }

//    protected String getWhereClauseForKeyRegions(Integer region, List<RegionInfo> keyRegions) {
//        return (region == null) ? " " :
//                " AND qs.region_id = " + region;

//        return (region == null) ? " " :
//                " AND qs.region_id IN (" + SqlUtil.getCommaSeparatedList(regionsTreeCacheService.getSubTreeForRegionByInfoList(region, keyRegions)) + ") ";
//    }

    //TODO: Include subregions feature
    protected String getWhereClauseForKeyRegions(Integer region, List<RegionInfo> keyRegions) throws InternalException {
        return getWhereClauseForKeyRegions(region, keyRegions, false);
    }

    @Override
    protected String getWhereClauseForKeyRegions(Integer region, List<RegionInfo> keyRegions, boolean includingSubregions) throws InternalException {
//TODO: Include subregions feature
        if (!includingSubregions) {
            return " AND qs.region_id = " + region;
        } else {
            List<Integer> searchRegions = getRegionsTreeCacheService().getKeyRegionsFromSubTreeByInfoList(region, keyRegions, false);
            searchRegions.add(region);
            return " AND qs.region_id IN (" + SqlUtil.getCommaSeparatedList(searchRegions) + ") ";
        }
//        return getWhereClauseForKeyRegions(region, keyRegions);
    }

    @Override
    public List<Date> getTopInfoDatesForRegionByHost(final Integer region, final HostDbHostInfo hostInfoType, boolean includeSubregions)
            throws InternalException {
        //TODO: Problem getting dates list without knowledge about key regions
        String whereClause = null;
        if (region == null || region == 0 && includeSubregions) {
            whereClause = " ";
        } else {
            Set<RegionInfo> keyRegionsSet = new HashSet<RegionInfo>(getRegionsTreeCacheService().getDefaultKeyRegionsInfo());
            keyRegionsSet.addAll(getCustomRegionService().getKeyRegionsForHostAndAllTheTime(hostInfoType));
            List<RegionInfo> keyRegions = new ArrayList<RegionInfo>(keyRegionsSet);
            whereClause = getWhereClauseForKeyRegions(region, keyRegions, includeSubregions);
        }
        return getJdbcTemplate(new WMCPartition(hostInfoType, null)).
                query(String.format(SELECT_TOP_INFO_DAYS_BY_HOST_QUERY, whereClause),
                        topInfoDatesMapper, hostInfoType.getHostDbHostId());
    }

    @Override
    public List<Date> getCustomInfoDatesForRegionByHost(final Integer region, HostDbHostInfo hostInfoType, boolean includeSubregions)
            throws InternalException {
        //TODO: Problem getting dates list without knowledge about key regions
        String whereClause = null;
        if (region == null || region == 0 && includeSubregions) {
            whereClause = " ";
        } else {
            Set<RegionInfo> keyRegionsSet = new HashSet<RegionInfo>(getRegionsTreeCacheService().getDefaultKeyRegionsInfo());
            keyRegionsSet.addAll(getCustomRegionService().getKeyRegionsForHostAndAllTheTime(hostInfoType));
            List<RegionInfo> keyRegions = new ArrayList<RegionInfo>(keyRegionsSet);
            whereClause = getWhereClauseForKeyRegions(region, keyRegions, includeSubregions);
        }
        return getJdbcTemplate(new WMCPartition(hostInfoType, null)).
                query(String.format(SELECT_CUSTOM_INFO_DAYS_BY_HOST_QUERY, whereClause),
                        topInfoDatesMapper,
                        hostInfoType.getHostDbHostId(),
                        hostInfoType.getHostDbHostId());
    }

    @Override
    public List<Date> getTopAndCustomInfoDatesForRegionByHost(Integer region, HostDbHostInfo hostDbHostInfo, boolean includeSubregions) throws InternalException {
        String whereClause = null;
        if ((region == null || region == 0) && includeSubregions) {
            whereClause = " ";
        } else {
            Set<RegionInfo> keyRegionsSet = new HashSet<RegionInfo>(getRegionsTreeCacheService().getDefaultKeyRegionsInfo());
            keyRegionsSet.addAll(getCustomRegionService().getKeyRegionsForHostAndAllTheTime(hostDbHostInfo));
            List<RegionInfo> keyRegions = new ArrayList<RegionInfo>(keyRegionsSet);
            whereClause = getWhereClauseForKeyRegions(region, keyRegions, includeSubregions);
        }
        return getJdbcTemplate(new WMCPartition(hostDbHostInfo, null)).
                query(String.format(SELECT_TOP_AND_CUSTOM_INFO_DAYS_BY_HOST_QUERY, whereClause),
                        topInfoDatesMapper, hostDbHostInfo.getHostDbHostId(), hostDbHostInfo.getHostDbHostId());
    }

    @Override
    public List<Date> getTopInfoUpdateDatesForRegionByHost(final Integer region, final HostDbHostInfo hostInfoType)
            throws InternalException {
        List<Date> list = getJdbcTemplate(new WMCPartition(hostInfoType, null)).
                query(String.format(SELECT_TOP_INFO_UPDATE_DAYS_BY_HOST_QUERY,
                        region == null ? " " : getWhereClauseForKeyRegions(region, new ArrayList<RegionInfo>()).replaceFirst("qs\\.", "")),
                        topInfoDatesMapper, hostInfoType.getHostDbHostId(), hostInfoType.getHostDbHostId());
        return list;
    }

    // ----------

    @Override
    protected <T> NavigableMap<Date, T> getHistoryGraph(final Integer region,
                                                        final List<RegionInfo> keyRegions,
                                                        final HostDbHostInfo hostInfoType,
                                                        final long queryId,
                                                        final String queryString,
                                                        final ParameterizedMapRowMapper<Date, T> mapper,
                                                        final boolean includeSubregions) throws InternalException {
//        List<RegionInfo> regionsForRequest = keyRegions;
//        List<Date> validDates = null;
//TODO: Include subregions feature
        List<Date> validDates = new ArrayList<Date>();
        List<RegionInfo> regionsForRequest = new ArrayList<RegionInfo>();
        getDatesAndRegionsForRequest(validDates, regionsForRequest, region, keyRegions, includeSubregions, hostInfoType);

        log.debug("Query for history graph" + String.format(queryString,
                getWhereClauseForKeyRegions(region, regionsForRequest, includeSubregions),
                getWhereClauseForDays(validDates, "qs.day")).replaceAll("(tbl_\\w+)", "$1494")
                .replaceFirst("\\?", String.valueOf(hostInfoType.getHostDbHostId()))
                .replaceFirst("\\?", String.valueOf(queryId)));

        return getJdbcTemplate(new WMCPartition(hostInfoType, null)).
                queryForNavigableMap(String.format(queryString,
                        getWhereClauseForKeyRegions(region, regionsForRequest, includeSubregions),
                        getWhereClauseForDays(validDates, "qs.day")), mapper,
                        hostInfoType.getHostDbHostId(), queryId);
    }

    // ----------

    @Override
    public NavigableMap<Date, Integer> getQueryShowsHistory(final Integer region,
                                                            final List<RegionInfo> keyRegions,
                                                            final HostDbHostInfo hostInfoType,
                                                            final long queryId,
                                                            final boolean includeSubregions) throws InternalException {
        return getHistoryGraph(
                region, keyRegions, hostInfoType, queryId, SELECT_TOP_SHOWS_QUERY_HISTORY_QUERY, topQueryHistoryMapper, includeSubregions);
    }

    @Override
    public NavigableMap<Date, Integer> getQueryClicksHistory(final Integer region,
                                                             final List<RegionInfo> keyRegions,
                                                             final HostDbHostInfo hostInfoType,
                                                             final long queryId,
                                                             final boolean includeSubregions) throws InternalException {
        return getHistoryGraph(
                region, keyRegions, hostInfoType, queryId, SELECT_TOP_CLICKS_QUERY_HISTORY_QUERY, topQueryHistoryMapper, includeSubregions);
    }

    @Override
    public NavigableMap<Date, Integer> getQueryPositionHistory(final Integer region,
                                                               final List<RegionInfo> keyRegions,
                                                               final HostDbHostInfo hostInfoType,
                                                               long queryId,
                                                               final boolean includeSubregions) throws InternalException {
        return getHistoryGraph(
                region, keyRegions, hostInfoType, queryId, SELECT_TOP_QUERY_POSITION_HISTORY_QUERY, topQueryPositionHistoryMapper, includeSubregions);
    }

    @Override
    public NavigableMap<Date, Double> getQueryCTRHistory(final Integer region,
                                                         final List<RegionInfo> keyRegions,
                                                         final HostDbHostInfo hostInfoType,
                                                         final long queryId,
                                                         final boolean includeSubregions) throws InternalException {
        return getHistoryGraph(
                region, keyRegions, hostInfoType, queryId, SELECT_TOP_QUERY_CTR_HISTORY_QUERY, topQueryDoubleValueHistoryMapper, includeSubregions);
    }

    @Override
    public NavigableMap<Date, Double> getQueryPartHistory(final Integer region,
                                                          final List<RegionInfo> keyRegions,
                                                          final HostDbHostInfo hostInfoType,
                                                          long queryId,
                                                          final boolean includeSubregions) throws InternalException {
        return getHistoryGraph(
                region, keyRegions, hostInfoType, queryId, SELECT_TOP_QUERY_PART_HISTORY_QUERY, topQueryDoubleValueHistoryMapper, includeSubregions);
    }

    public void getDatesAndRegionsForRequest(Collection<Date> validDates, Collection<RegionInfo> regions, Integer region, List<RegionInfo> keyRegions, boolean includeSubregions, HostDbHostInfo hostInfoType) throws InternalException {
        //All dates
        List<Date> dates = getTopInfoDatesByHost(hostInfoType);

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

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

            Set<RegionInfo> excludedSet = new HashSet<RegionInfo>(excluded);
            removeBadRegions(excludedSet);

            //dates with good keyregions
            for (Date date : dates) {
                //Regions excluded from region in specified date
                List<RegionInfo> excludedAt = getRegionsTreeCacheService().getRegionsInfo(
                        getRegionsTreeCacheService().getKeyRegionsFromSubTreeByInfoList(region, getCustomRegionService().getKeyRegionsForHostAndDate(hostInfoType, date), true));
                log.debug("Excluded at " + date + " : " + SqlUtil.getCommaSeparatedList(excludedAt));

                Set<RegionInfo> excludedAtSet = new HashSet<RegionInfo>(excludedAt);
                removeBadRegions(excludedAtSet);

                if (excludedAtSet.equals(excludedSet)) {
                    validDates.add(date);
                    log.debug("So good");
                }
            }
            if (region != null) {
                regions.add(getRegionsTreeCacheService().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 = getRegionsTreeCacheService().getRegionInfo(region);

            for (Date date : dates) {
                List<RegionInfo> keyRegionsForDate = getCustomRegionService().getKeyRegionsForHostAndDate(hostInfoType, date);

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

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

                toInclude.addAll(keyRegionsForDate);
            }

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

    // ----------
    @Override
    public List<TopQueryHistoryInfo> getQueriesHistoryInfo(Integer region, TopQueryHistoryTypeEnum type, final List<RegionInfo> keyRegions, final HostDbHostInfo hostInfoType, int count, boolean includeSubregions) throws InternalException {
        TopQueryHistoryInfoMapper mapper = new TopQueryHistoryInfoMapper(type);

//        List<RegionInfo> regionsForRequest = keyRegions;
//        List<Date> validDates = null;
//TODO: Include subregions feature
        List<Date> validDates = new ArrayList<Date>();
        List<RegionInfo> regionsForRequest = new ArrayList<RegionInfo>();
        getDatesAndRegionsForRequest(validDates, regionsForRequest, region, keyRegions, includeSubregions, hostInfoType);

        log.debug("Query for history graph" + String.format(SELECT_TOP_QUERIES_HISTORY_INFO_QUERY,
                getWhereClauseForKeyRegions(region, regionsForRequest, includeSubregions), "", "", getWhereClauseForDays(validDates, "qs.day"), "").replaceAll("(tbl_\\w+)", "$1494").replaceFirst("\\?", String.valueOf(hostInfoType.getHostDbHostId())).replaceFirst("\\?", String.valueOf(count)));

        return getJdbcTemplate(new WMCPartition(hostInfoType, null)).
                query(String.format(SELECT_TOP_QUERIES_HISTORY_INFO_QUERY,
                        getWhereClauseForKeyRegions(region, regionsForRequest, includeSubregions), "", "", getWhereClauseForDays(validDates, "qs.day"), ""), mapper,
                        hostInfoType.getHostDbHostId(), count);
    }

    @Override
    public List<Long> getQueriesWithHistoryIds(List<Long> queries, HostDbHostInfo hostDbHostInfo, String whereClause) throws InternalException {
        if (queries.isEmpty()) {
            return new ArrayList<Long>();
        } else {
            StringBuilder whereClauseForQueries = new StringBuilder(" AND qs.query_id IN (")
                    .append(SqlUtil.getCommaSeparatedList(queries))
                    .append(") ");
            return getJdbcTemplate(new WMCPartition(hostDbHostInfo, null)).query(
                    String.format(SELECT_QUERIES_WITH_HISTORY_QUERY, whereClause, whereClauseForQueries),
                    new LongRowMapper(), hostDbHostInfo.getHostDbHostId());
        }
    }

    public String getWhereClauseForTopQueries(HostDbHostInfo hostDbHostInfo, Date date, String whereClause, boolean includeCustomQueries) throws InternalException {
        String res = SqlUtil.getCommaSeparatedList(
                getJdbcTemplate(new WMCPartition(hostDbHostInfo, null))
                        .query(String.format(SELECT_TOP_QUERIES_LIST_QUERY, whereClause.replaceAll("qs\\.region_id", "region_id")),
                                new LongRowMapper(),
                                hostDbHostInfo.getHostDbHostId(),
                                date,
                                hostDbHostInfo.getHostDbHostId(),
                                date));
        if (res.trim().equals("")) {
            return !includeCustomQueries ? " AND FALSE " : " AND ( FALSE OR NOT cq.query_id IS NULL ) ";
        } else {
            return !includeCustomQueries ? " AND q.query_id IN (" + res + ") " : " AND ( q.query_id IN (" + res + ") OR NOT cq.query_id IS NULL ) ";
        }
    }

    @Override
    public List<TopQueryHistoryInfo> getQueriesHistoryInfo(Integer region, TopQueryHistoryTypeEnum type, final List<RegionInfo> keyRegions, final HostDbHostInfo hostInfoType, long count, boolean includeSubregions, OrderByClause order, Pager pager, Date day) throws InternalException {
        TopQueryHistoryInfoMapper mapper = new TopQueryHistoryInfoMapper(type);

//        List<RegionInfo> regionsForRequest = keyRegions;
//        List<Date> validDates = null;
//TODO: Include subregions feature
        List<Date> validDates = new ArrayList<Date>();
        List<RegionInfo> regionsForRequest = new ArrayList<RegionInfo>();
        getDatesAndRegionsForRequest(validDates, regionsForRequest, region, keyRegions, includeSubregions, hostInfoType);

        String whereClauseForKeyRegions = getWhereClauseForKeyRegions(region, regionsForRequest, includeSubregions);
        String whereClauseForTopQueries = getWhereClauseForTopQueries(hostInfoType, day, whereClauseForKeyRegions, true);

        log.debug("QUERIES LIST: " + String.format(SELECT_TOP_QUERIES_HISTORY_INFO_QUERY, whereClauseForKeyRegions,
                "%1$s", "%2$s", getWhereClauseForDays(validDates, "qs.day"), whereClauseForTopQueries));

        return getJdbcTemplate(new WMCPartition(hostInfoType, null)).select(
                String.format(SELECT_TOP_QUERIES_HISTORY_INFO_COUNT_QUERY, whereClauseForKeyRegions, getWhereClauseForDays(validDates, "qs.day"), whereClauseForTopQueries),
                String.format(SELECT_TOP_QUERIES_HISTORY_INFO_QUERY, whereClauseForKeyRegions,
                        "%1$s", "%2$s", getWhereClauseForDays(validDates, "qs.day"), whereClauseForTopQueries), mapper, order, pager, hostInfoType.getHostDbHostId(), count);
    }

    @Override
    protected List<TopQueryHistoryInfo> executeGetQueriesHistoryInfoQuery(TopQueryHistoryTypeEnum type, Integer region, HostDbHostInfo hostInfo, int count, boolean includeSubregions, List<Long> queryIds, List<Date> validDates, List<RegionInfo> regionsForRequest) throws InternalException {
        TopQueryHistoryInfoMapper mapper = new TopQueryHistoryInfoMapper(type);

        String queryString = String.format(SELECT_TOP_QUERIES_HISTORY_INFO_BY_ID_QUERY, SqlUtil.getCommaSeparatedList(queryIds), getWhereClauseForKeyRegions(region, regionsForRequest, includeSubregions), getWhereClauseForDays(validDates, "qs.day"));
        log.debug("SELECTED QUERIES: " + queryString);
        return getJdbcTemplate(new WMCPartition(hostInfo, null)).query(queryString, mapper, hostInfo.getHostDbHostId(), count);
    }


    @Override
    public List<CustomQueryInfo> getCustomQueriesForHost(HostDbHostInfo hostInfoType) throws InternalException {
        return getJdbcTemplate(new WMCPartition(hostInfoType, null)).query(
                SELECT_CUSTOM_QUERIES_LIST_QUERY,
                customQueryInfoMapper, hostInfoType.getHostDbHostId());
    }

    public List<CustomQueryInfo> getCustomQueriesForHost(HostDbHostInfo hostInfoType, OrderByClause order, Pager pager) throws InternalException {
        return getJdbcTemplate(new WMCPartition(hostInfoType, null)).select(SELECT_CUSTOM_QUERIES_COUNT_QUERY,
                SELECT_CUSTOM_QUERIES_PAGED_LIST_QUERY,
                customQueryInfoMapper, order, pager, hostInfoType.getHostDbHostId());
    }


    @Override
    public void removeCustomQueriesForHost(HostDbHostInfo hostInfoType, List<Long> queryIds) throws InternalException, UserException {
        int rowsUpdated = getJdbcTemplate(new WMCPartition(hostInfoType, null)).update(
                String.format(DELETE_CUSTOM_QUERIES_QUERY, SqlUtil.getCommaSeparatedList(queryIds)),
                hostInfoType.getHostDbHostId());

        if (rowsUpdated != queryIds.size()) {
            // some queries were not removed from list
            throw new UserException(UserProblem.NO_SUCH_CUSTOM_QUERY_FOUND, "Some queries to be deleted are not found.");
        }
    }

    @Override
    protected WMCTopInfo createTopInfo(List<WMCTopInfoItem> resultList, TotalsInfo totals) {
        return new WMCTopInfo(resultList, totals, null);
    }

    @Override
    protected WMCTopInfo getTopInfoStubEmpty() {
        return WMCTopInfo.EMPTY;
    }

    @Override
    protected WMCTopInfo getTopInfoStubDateNotSpecified() {
        return WMCTopInfo.DATE_NOT_SPECIFIED;
    }

    @Override
    protected WMCTopInfo getTopInfoStubNotAKeyRegion() {
        return WMCTopInfo.NOT_A_KEY_REGION;
    }

    @Override
    protected WMCTopInfoComparator getTopInfoComparator(OrderByClause orderBy) {
        return new WMCTopInfoComparator(orderBy);
    }

    @Override
    protected int getTopQueriesCount() {
        return TOP_QUERIES_COUNT;
    }

    @Override
    protected HostRegionsInfo getVisibleRegionsForHost(HostDbHostInfo hostDbHostInfo) throws InternalException {
        return hostRegionService.getVisibleRegionsForHost(hostDbHostInfo);
    }

    @Override
    protected WMCTopInfoItem[] createTopInfoItem() {
        return new WMCTopInfoItem[getTopQueriesCount() + getMaxCustomQueriesForHost() + 1];
    }

    private List<Pair<String, Object[]>> copyCustomQueryHistoryUpdateStrings(HostDbHostInfo oldHostInfo,
                                                                             HostDbHostInfo newHostInfo) throws InternalException {
        List<Map<String, Object>> params = getJdbcTemplate(new WMCPartition(oldHostInfo, null)).
                queryForList(SELECT_CUSTOMQUERY_HISTORY_QUERY, oldHostInfo.getHostDbHostId());
        log.debug("Copy custom query history " + params.size());

        List<Pair<String, Object[]>> res = new ArrayList<Pair<String, Object[]>>();

        if (params.size() > 0) {
            Map<String, Long> queryMap = addQueries(newHostInfo, params);

            List<Object> pars = new LinkedList<Object>();
            StringBuilder q = new StringBuilder();
            for (Map<String, Object> row : params) {
                pars.add(newHostInfo.getHostDbHostId());
                pars.add(row.get(FIELD_DAY));
                pars.add(row.get(FIELD_REGION));
                pars.add(queryMap.get(row.get(FIELD_QUERY)));
                pars.add(row.get(FIELD_CUSTOM_QUERY_SHOWS_TOP_RANK));
                pars.add(row.get(FIELD_CUSTOM_QUERY_CLICKS_TOP_RANK));
                pars.add(row.get(FIELD_CUSTOM_QUERY_SHOWS_TOP_RANK_INCL));
                pars.add(row.get(FIELD_CUSTOM_QUERY_CLICKS_TOP_RANK_INCL));

                if (q.length() == 0) {
                    q.append(INSERT_CUSTOMQUERY_HISTORY_QUERY);
                } else {
                    q.append(",");
                }
                q.append(INSERT_CUSTOMQUERY_HISTORY_VALUES);

                if (q.length() > MAX_QUERY_SIZE) {
                    res.add(Pair.of(q.toString(), pars.toArray()));
                    q = new StringBuilder();
                    pars.clear();
                }
            }

            if (!pars.isEmpty()) {
                res.add(Pair.of(q.toString(), pars.toArray()));
            }
        }
        return res;
    }

    private List<Pair<String, Object[]>> copyQueryStatsUpdateQueries(HostDbHostInfo oldHostDbHostInfo, HostDbHostInfo newHostDbHostInfo)
            throws InternalException {
        List<Map<String, Object>> params = getJdbcTemplate(new WMCPartition(oldHostDbHostInfo, null)).
                queryForList(SELECT_QUERY_STATS_QUERY, oldHostDbHostInfo.getHostDbHostId());
        log.debug("Copy query stats " + params.size());
        List<Pair<String, Object[]>> res = new ArrayList<Pair<String, Object[]>>();

        if (params.size() > 0) {
            Map<String, Long> queryMap = addQueries(newHostDbHostInfo, params);

            List<Object> pars = new LinkedList<Object>();
            StringBuilder q = new StringBuilder();
            for (Map<String, Object> row : params) {
                pars.add(newHostDbHostInfo.getHostDbHostId());
                pars.add(row.get(FIELD_DAY));
                pars.add(row.get(FIELD_REGION));
                pars.add(queryMap.get(row.get(FIELD_QUERY)));
                pars.add(row.get(FIELD_SHOWS));
                pars.add(row.get(FIELD_CLICKS));
                pars.add(row.get(FIELD_SHOWS_POS_WEIGHTED));
                pars.add(row.get(FIELD_CLICKS_POS_WEIGHTED));

                if (q.length() == 0) {
                    q.append(INSERT_QUERY_STATS_QUERY);
                } else {
                    q.append(",");
                }
                q.append(INSERT_QUERY_STATS_VALUES);

                if (q.length() > MAX_QUERY_SIZE) {
                    res.add(Pair.of(q.toString(), pars.toArray()));
                    q = new StringBuilder();
                    pars.clear();
                }
            }

            if (!pars.isEmpty()) {
                res.add(Pair.of(q.toString(), pars.toArray()));
            }
        }
        return res;
    }

    protected Map<String, Long> addQueries(HostDbHostInfo newHostDbHostInfo, List<Map<String, Object>> params)
            throws InternalException {
        Set<String> standardizedQueries = new HashSet<String>();
        for (Map<String, Object> row : params) {
            standardizedQueries.add((String) row.get(FIELD_QUERY));
        }
        addCustomQueriesToDb(newHostDbHostInfo, new ArrayList<String>(standardizedQueries));
        return getCustomQueriesIdsMap(newHostDbHostInfo, standardizedQueries);
    }

    private List<Pair<String, Object[]>> copyTotalQueriesStatsUpdateQueries(HostDbHostInfo oldHostDbHostInfo, HostDbHostInfo newHostDbHostInfo)
            throws InternalException {
        List<Map<String, Object>> params = getJdbcTemplate(new WMCPartition(oldHostDbHostInfo, null)).
                queryForList(SELECT_TOTAL_QUERIES_STATS_QUERY, oldHostDbHostInfo.getHostDbHostId());
        log.debug("Copy total queries stats " + params.size());
        List<Pair<String, Object[]>> res = new ArrayList<Pair<String, Object[]>>();

        if (params.size() > 0) {
            List<Object> pars = new LinkedList<Object>();
            StringBuilder q = new StringBuilder();
            for (Map<String, Object> row : params) {
                pars.add(newHostDbHostInfo.getHostDbHostId());
                pars.add(row.get(FIELD_DAY));
                pars.add(row.get(FIELD_REGION));
                pars.add(row.get(FIELD_SHOWS_COUNT));
                pars.add(row.get(FIELD_CLICKS_COUNT));

                if (q.length() == 0) {
                    q.append(INSERT_TOTAL_QUERIES_STATS_QUERY);
                } else {
                    q.append(",");
                }
                q.append(INSERT_TOTAL_QUERIES_STATS_VALUES);

                if (q.length() > MAX_QUERY_SIZE) {
                    res.add(Pair.of(q.toString(), pars.toArray()));
                    q = new StringBuilder();
                    pars.clear();
                }
            }

            if (!pars.isEmpty()) {
                res.add(Pair.of(q.toString(), pars.toArray()));
            }
        }
        return res;
    }

    public void tryToCopyTopData(final HostDbHostInfo oldHost, final HostDbHostInfo newHost) throws InternalException, UserException {
        if (!hasAnyData(newHost)) {
            final List<Pair<String, Object[]>> queries = new ArrayList<Pair<String, Object[]>>();
            log.debug("Loading custom queries history");
            queries.addAll(copyCustomQueryHistoryUpdateStrings(oldHost, newHost));
            log.debug("Loading query states");
            queries.addAll(copyQueryStatsUpdateQueries(oldHost, newHost));
            log.debug("Loading total queries stats");
            queries.addAll(copyTotalQueriesStatsUpdateQueries(oldHost, newHost));

            getServiceTransactionTemplate(new WMCPartition(newHost, null))
                    .executeInService(new ServiceTransactionCallbackWithoutResult() {
                        @Override
                        protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) throws UserException, InternalException {
                            log.debug("Applying " + queries.size() + " queries");
                            for (Pair<String, Object[]> query : queries) {
                                getJdbcTemplate(new WMCPartition(newHost, null))
                                        .update(query.first, query.second);
                            }
                        }
                    });
            log.debug("Queries info copied successful");
        } else {
            log.debug("Already have queries info for host " + newHost.getName());
        }
    }

    private boolean hasAnyData(HostDbHostInfo hostInfo) throws InternalException {
        List<Integer> res = getJdbcTemplate(new WMCPartition(hostInfo, null))
                .query(SELECT_CHECK_FOR_ANY_DATA_QUERY, new IntegerRowMapper()
                        , hostInfo.getHostDbHostId()
                        , hostInfo.getHostDbHostId()
                        , hostInfo.getHostDbHostId());
        for (Integer i : res) {
            if (i > 0) {
                return true;
            }
        }
        return false;
    }

    @Required
    public void setHostRegionService(HostRegionService hostRegionService) {
        this.hostRegionService = hostRegionService;
    }

    @Override
    protected int getMaxCustomQueriesForHost() {
        return MAX_CUSTOM_QUERIES_FOR_HOST;
    }
}
