# coding: utf-8
"""utils"""
from .xlsx_search_queries.new_ctr import new_ctr_col
from .xlsx_search_queries.new_cpc import new_cpc_col
from .utils import (
    extract_currency_from_tar,
    extract_period_from_tsv,
    extract_str_currency_from_tar,
    try_trim_urls_4excel,
    set_report_meta,
    classify_group_format
)
from .error import (
    SourcesError,
    ParseError
)
import re
import logging
from glob import glob
import tarfile as tar
from os.path import isfile, splitext, split as psplit
import pandas as pd


def sources_from_tar(report_suffix):
    src_wildcard = "./*.tar.gz"
    sources = glob(src_wildcard)
    if not sources:
        raise SourcesError("there are no files for mask: %s" % src_wildcard)
    sources = [tar_ for tar_ in sources if '_Empty.' not in tar_]
    if not sources:
        raise SourcesError("all sources are empty")
    currency = reduce(
        lambda x, y: x if x == y else None, map(extract_str_currency_from_tar, sources)
    )
    has_equal_currency = currency is not None or len(sources) == 1

    def is_report(members, suffix):
        for tarinfo in members:
            # mac os specific https://discussions.apple.com/thread/4947897
            if psplit(tarinfo.path)[-1].startswith("._"):
                continue
            if splitext(tarinfo.path)[0].endswith(suffix) and '_Empty_' not in tarinfo.path:
                yield tarinfo

    def extract_reports(tarpath):
        with tar.open(tarpath, "r:gz") as src:
            dfs = []
            for report in is_report(src, report_suffix):
                try:
                    df = pd.read_table(
                        src.extractfile(report),
                        quoting=3,  # None
                        encoding="utf-8",
                        low_memory=True,
                        na_values=("Null", "null", "NULL"),
                        na_filter=True,
                        engine="c")
                except pd.io.common.EmptyDataError:
                    msg = "file is empty: %s" % report.path
                    logging.error(msg, exc_info=0)
                    continue
                try:
                    df["Currency"] = extract_currency_from_tar(tarpath)
                except ParseError:
                    if has_equal_currency:
                        df["Currency"] = 1.0
                    else:
                        raise
                try:
                    report_period = extract_period_from_tsv(report.name)
                    df['DateBegin'] = report_period[0].strftime('%Y-%m-%d')
                    df['DateFinish'] = report_period[1].strftime('%Y-%m-%d')
                except ParseError:
                    raise

                if df.shape[0] > 0:
                    dfs.append(df)
                else:
                    msg = "tar is empty: %s" % tarpath
                    logging.error(msg)
            if dfs:
                return pd.concat(dfs)
            else:
                None

    logging.info("\nSources from `*%s.*`:" % report_suffix)
    data = filter(lambda x: x is not None, map(extract_reports, sources))
    if not data:
        logging.warn("\ - there are no files")
        return None

    print('\n')
    for x in sources:
        logging.info(x)

    print("{:-<15}".format(""))
    logging.info("files: {}".format(len(sources)))

    if len(data) > 1:
        columns = map(lambda x: x.columns, data)
        has_eq_cols = len(reduce(lambda x, y: x if set(x) == set(y) else [], columns)) > 0
        if not has_eq_cols:
            raise ValueError("sources are not the same")

    df = pd.concat(data)
    if len(df['DateBegin'].unique().tolist()) > 1 or len(df['DateFinish'].unique().tolist()) > 1:
        raise SourcesError("different periods between sources")

    print("{:-<15}".format(""))
    msg = "read rows: {}".format(df.shape[0])
    logging.info(msg)

    return df


def filter_by_campaigns(df):

    if isfile("./campaigns.txt"):
        try:
            with open("./campaigns.txt", "rU") as src:
                cid_filter = src.readlines()
                cid_filter = {int(x.strip()) for x in cid_filter if x not in {"", "\n"}}
            if cid_filter:
                logging.info("data has been filtered by ./campaigns.txt")
                return df.loc[df.CampaignID.isin(cid_filter)]
        except Exception:
            msg = "file ./campaigns.txt exists but something went wrong :("
            logging.error(msg, exc_info=1)
    else:
        return df


def rename_report(df):
    if df is None or df.shape[0] == 0:
        return None

    columns = {
        # Dimensions
        u"DateBegin": u"DateBegin",
        u"DateFinish": u"DateFinish",
        u"Currency": u"Currency",
        u"Логин": "LoginName",
        u"Название кампании": "CampaignName",
        u"ID кампании": "CampaignID",
        u"Название группы": "GroupName",
        u"ID группы": "GroupID",
        u"ID баннера": "DirectBannerID",
        u"Заголовок баннера": "Title",
        u"текст баннера": "Body",
        u"URL баннера": "URL",
        u"ID ключевого слова (direct)": "DirectPhraseID",
        u"ID ключевого слова (BS)": "BSPhraseID",
        u"Ключевое слово (без минус-слов)": "PhraseText",
        u"Все минус-слова, действующие для фразы (собственные и на кампанию), через пробел": "AntiWords",
        u"Количество слов из ключевого слова, входящих в заголовок и текст баннера": "BoldedWordsCnt",
        u"Количество слов в ключевом слове": "WordsCnt",
        u"Список слов фразы, не входящих в баннер (через пробел)": "NonBoldedWords",
        u"Хеш картинки баннера": "ImageHash",
        u"количество сайтлинков": "SitelinksCnt",
        u"флаг наличия шаблона в баннере": "HasTemplate",
        u"Флаг корректного использования шаблона (при подстановке текущего ключевого слова баннер соответствует ограничениям директа)": \
        u"IsCorrectTemplate",
        u"Количество уточнений": "CalloutsCnt",  # Уточнения https://yandex.ru/support/direct/features/callout.xml?lang=en
        u"Cредняя позиция показов (spec)": "AvgPosShowsSpec",
        u"cредняя позиция кликов (spec)": "AvgPosClicksSpec",
        u"Cредняя позиция показов (others)": "AvgPosShowsOther",
        u"cредняя позиция кликов (others)": "AvgPosClicksOther",
        u"Продуктивность фразы": "Productivity",
        u"текущая ставка на поиске": "BidSearch",
        u"текущая ставка на тематических площадках": "BidRSYA",
        u"дневной лимит на кампанию": "DayBudget",
        u"код временного таргетинга": "TimeTarget",
        u"Стратегия показа": "Strategy",
        # Dimensions added 26.12.2016
        u"Использование шаблона в отображаемой ссылке": "DisplayUrlHasTemplate",
        u'количество описаний сайтлинков': "DescriptionSLCnt",
        u'Заполненность визитки': "VisitCardUsage",
        # Mobile Dimensions added 10.01.2017
        u'Мобильное объявление': "isMobileBanner",
        # Dimensions added 19.01.2017
        # https://st.yandex-team.ru/CUSTOMSOLUTIONS-1938
        # https://tech.yandex.ru/direct/doc/dg/objects/adimage-docpage/#type
        # https://tech.yandex.ru/direct/doc/dg/objects/ad-docpage/#types
        u'Тип объявления': "AdType",
        u'Подтип объявления': "AdSubtype",
        u'Размер изображения (для фикс. размеров)': "FixedImageSize",
        # https://st.yandex-team.ru/CUSTOMSOLUTIONS-2027
        u'Размер изображения (из yt, HxW)': "YTImageSizeHW",
        # Mobile Metrics added 10.01.2017
        u'Показы на мобильных': "ShowsMobile",
        u'Клики на мобильных': "ClicksMobile",
        u'Расходы на мобильных': "CostMobile",
        # Metrics
        u"Показы на поиске в блоке спецразмещения": "ShowsSpec",
        u"Клики на поиске в блоке спецразмещения": "ClicksSpec",
        u"Расходы на поиске за клики в блоке спецразмещения": "CostSpec",
        u"Показы на поиске в блоках гарантированных и динамических показов": "ShowsOther",
        u"Клики на поиске в блоках гарантированных и динамических показов": "ClicksOther",
        u"Расходы на поиске за клики в блоке гарантированных и динамических показов": "CostOther",
        u"Показы на тематических площадках": "ShowsRSYA",
        u"Клики на тематических площадках": "ClicksRSYA",
        u"Расходы за клики на тематических площадках": "CostRSYA",
    }

    df.rename(columns=columns, inplace=True)
    if not all(df.columns.isin(columns.values())):
        err = SourcesError(u"Some columns hasn't been translated")
        msg = u"Missed: %s" % df.columns[~df.columns.isin(columns.values())]
        err.message += "\n" + msg
        logging.error(msg, exc_info=1)
        raise err

    return df


def check_image_type(row, image_type):
    assert image_type in ['Wide', 'Standart'], 'Unknown image_type %s' % image_type

    if image_type == 'Wide':
        if 1080 <= row["YTImageSizeWidth"] <= 5000 and 607 <= row["YTImageSizeHeight"] <= 2812:
            proportion = row["YTImageSizeWidth"] * 9.0 - row["YTImageSizeHeight"] * 16.0
            if abs(proportion) <= 10:
                return True

    elif image_type == 'Standart':
        if 450 <= row["YTImageSizeWidth"] <= 5000 and 450 <= row["YTImageSizeHeight"] <= 5000:
            proportion = row["YTImageSizeWidth"] * 9.0 - row["YTImageSizeHeight"] * 16.0
            if abs(proportion) > 10:
                return True

    return False


def preprocess_keywords(df):

    if df is None or df.shape[0] == 0:
        return None

    df = rename_report(df)

    df["Shows"] = df.ShowsOther + df.ShowsRSYA + df.ShowsSpec
    df["Clicks"] = df.ClicksOther + df.ClicksRSYA + df.ClicksSpec
    df["Cost"] = df.CostOther + df.CostRSYA + df.CostSpec
    df["ShowsSearch"] = df.ShowsOther + df.ShowsSpec
    df["ClicksSearch"] = df.ClicksOther + df.ClicksSpec
    df["CostSearch"] = df.CostOther + df.CostSpec

    if df.VisitCardUsage.dtype.name not in {"float32", "float64", "float128"}:
        df.VisitCardUsage.replace({"None": pd.np.nan}, inplace=True)
        df.VisitCardUsage = df.VisitCardUsage.astype(pd.np.float64)

    assert df.VisitCardUsage.dtype == 'float64'
    df.VisitCardUsage = df.VisitCardUsage.apply(lambda x: None if pd.isnull(x) else x >= .5)

    if "BoldedWordsCnt" in df.columns and df.BoldedWordsCnt.dtype.name not in {"float64", "int64"}:
        df.BoldedWordsCnt = df.BoldedWordsCnt.replace({"None": pd.np.nan}).astype(pd.np.float64)
    # todo(n-bar) remove it if CUSTOMSOLUTIONS-1950 is closed
    if "BoldedWordsCnt" in df.columns and "WordsCnt" in df.columns:
        fix_cnt = lambda row: min(row["BoldedWordsCnt"], row["WordsCnt"])  # noqa: E731
        df["BoldedWordsCnt"] = df.apply(fix_cnt, axis=1)
    if "BoldedWordsCnt" in df.columns and "WordsCnt" in df.columns and "NonBoldedWords" in df.columns:
        process_bolded = lambda r: u"" if r["BoldedWordsCnt"] == r["WordsCnt"] else r["NonBoldedWords"]
        df.NonBoldedWords = df.apply(process_bolded, axis=1)

    if df.DisplayUrlHasTemplate.dtype != 'bool':
        df.DisplayUrlHasTemplate = df.DisplayUrlHasTemplate.replace({"None": None, "True": True, "False": False})

    if "IsCorrectTemplate" in df.columns:
        if df.IsCorrectTemplate.dtype != 'bool':
            df.IsCorrectTemplate = df.IsCorrectTemplate.replace({"None": None, "True": True, "False": False})

    if "PhraseText" in df.columns:
        filtered_rows = df.PhraseText.isnull()
        if sum(filtered_rows) > 0:
            df = df.loc[~filtered_rows]
            msg = "filtered by `PhraseText is null`: %s" % sum(filtered_rows)
            logging.warning(msg)

    if "WordsCnt" in df.columns:
        filtered_rows = df.WordsCnt > 0
        if sum(~filtered_rows) > 0:
            df = df.loc[filtered_rows]
            msg = "filtered by `WordsCnt > 0`: %s" % sum(~filtered_rows)
            logging.warning(msg)

    if df.HasTemplate.dtype != 'bool':
        df.HasTemplate = df.HasTemplate.replace({"None": None, "True": True, "False": False})

    if "isMobileBanner" in df.columns and df.isMobileBanner.dtype != 'bool':
        df.isMobileBanner = df.isMobileBanner.replace({"None": None, "True": True, "False": False})

    if "YTImageSizeHW" in df.columns:
        df.YTImageSizeHW = df.YTImageSizeHW.replace({"None": None, "NONE": None})
        df["YTImageSizeWidth"] = df.YTImageSizeHW.apply(lambda x: int(x.split("x")[1]) if x > '' and 'x' in x else 0).astype('uint32')
        df["YTImageSizeHeight"] = df.YTImageSizeHW.apply(lambda x: int(x.split("x")[0]) if x > '' and 'x' in x else 0).astype('uint32')

        df["YTImageSizeIsStandart"] = df.apply(lambda row: check_image_type(row, 'Standart'), axis=1)

        # Широкоформатность https://yandex.ru/support/direct/efficiency/images.html
        df["YTImageSizeIsWide"] = df.apply(lambda row: check_image_type(row, 'Wide'), axis=1)
        df["HasYTImageSize"] = (df["YTImageSizeWidth"] > 0) & (df["YTImageSizeHeight"] > 0)

        if "FixedImageSize" in df.columns:
            del df["FixedImageSize"]
        del df["YTImageSizeHW"]

        groups = (df.loc[:, ['GroupID', 'HasYTImageSize', 'YTImageSizeIsStandart', 'YTImageSizeIsWide']].copy()
                  .groupby(["GroupID"], as_index=True)
                  .agg({"HasYTImageSize": pd.np.max,
                        "YTImageSizeIsStandart": pd.np.max,
                        "YTImageSizeIsWide": pd.np.max})
                  .to_dict(orient='index')
                  )

        df["GroupYTImageFormat"] = df.apply(lambda row: classify_group_format(row, groups), axis=1)

    # convert currency
    currency_cols = [
        "Cost",
        "CostSearch",
        "CostRSYA",
        "CostSpec",
        "CostOther",
        "CostMobile",
        "BidRSYA",
        "BidSearch"]

    for col in currency_cols:
        df[col] = df[col].replace('None', '0')
        df[col] = df[col].replace('', '0')
        df[col] = df[col].astype(pd.np.float64) * df.Currency

    df.drop(["Currency"], axis=1, inplace=True)
    df = filter_by_campaigns(df)
    df["ShowsDesktop"] = df["Shows"] - df["ShowsMobile"]
    df["ClicksDesktop"] = df["Clicks"] - df["ClicksMobile"]
    df["CostDesktop"] = df["Cost"] - df["CostMobile"]

    return df


def preprocess_search_queries(df):
    if df is None or df.shape[0] == 0:
        return None

    def split_text_by_comma(text):

        if text is pd.np.nan:
            text = u""

        if not isinstance(text, unicode):
            raise TypeError(u"Expect <type 'unicode'>, in fact: %s" % type(text))

        return [t.strip() for t in re.split(u", *", text) if t]

    expected_columns = (
        "DateBegin",
        "DateFinish",
        "Query",
        "MatchType",
        "Login",
        "ClientID",
        "OrderID",
        "CampaignID",
        "CampaignName",
        "CampaignType",
        "Currency",
        "GroupID",
        "DirectBannerID",
        "BSBannerID",
        "DirectPhraseID",
        "PhraseText",
        "AdvSite",
        "Shows",
        "eShows",
        "MaxX",
        "Clicks",
        "Cost",
        "CostUnit",
        "Title",
        "Body",
        "TitleDB",
        "BodyDB",
        "Href",
        "SLText",
        "PhraseWordsCnt",
        "QueryWordsCnt",
        "DSSMScore",
        # "DSSMScore2",
        "ClicksRelevance",  # Средняя релевантность показа
        "ClicksSumRel",  # Сумма всех кликовых релевантностей
        "ShowsRelevance",  # Средняя релевантность показа
        "ShowsSumRel",  # Сумма всех релевантностей показа
        "QueryHighlighTitleBody",
        "QueryNonBoldedWordsTitleBody"
    )
    if not all(df.columns.isin(expected_columns)):
        err = SourcesError(u"Some columns hasn't been recognized")
        msg = u"Missed: %s" % df.columns[~df.columns.isin(expected_columns)]
        err.message += "\n" + msg
        logging.error(msg, exc_info=1)
        raise err

    # filters
    df = filter_by_campaigns(df)

    filtered_rows = ~(df.Shows > 0)
    if sum(filtered_rows) > 0:
        msg = "filtered by `not (df.Shows > 0)`: %s" % sum(filtered_rows)
        logging.warning(msg)
        df = df.loc[~filtered_rows, :]

    filtered_rows = df.DirectPhraseID.isnull()
    if sum(filtered_rows) > 0:
        msg = "filtered by `DirectPhraseID is null`: %s" % sum(filtered_rows)
        logging.warning(msg)
        df = df.loc[~filtered_rows, :]

    filtered_rows = (df.PhraseText.isnull() | (df.PhraseText == ''))
    if sum(filtered_rows) > 0:
        msg = "filtered by Null in PhraseText: %s" % sum(filtered_rows)
        logging.warning(msg)
        df = df.loc[~filtered_rows, :]

    filtered_rows = df.QueryWordsCnt < df.PhraseWordsCnt
    if sum(filtered_rows) > 0:
        msg = "filtered by `QueryWordsCnt < PhraseWordsCnt`: %s" % sum(filtered_rows)
        logging.warning(msg)
        df = df.loc[~filtered_rows, :]

    filtered_rows = ~((df.QueryWordsCnt > 0) & (df.PhraseWordsCnt > 0))
    if sum(filtered_rows) > 0:
        msg = "filtered by `(df.QueryWordsCnt > 0) & (df.PhraseWordsCnt > 0)`: %s" % sum(filtered_rows)
        logging.warning(msg)
        df = df.loc[~filtered_rows, :]

    filtered_rows = df.AdvSite != u"Яндекс"
    if sum(filtered_rows) > 0:
        msg = "filtered by `AdvSite != Яндекс`: %s" % sum(filtered_rows)
        logging.warning(msg)
        df = df.loc[~filtered_rows, :]

    # convert numbers
    int_cols = (
        "ClientID",
        "OrderID",
        "CampaignID",
        "GroupID",
        "DirectBannerID",
        "BSBannerID",
        "DirectPhraseID",
        "Shows",
        "Clicks",
        "PhraseWordsCnt",
        "QueryWordsCnt",
        "ShowsSumRel",
        "ClicksSumRel",
    )
    for col_name in int_cols:
        if df[col_name].dtype != 'int64':
            try:
                df[col_name] = df[col_name].astype('int64')
            except:
                print col_name
                raise
    float_cols = (
        "eShows",
        "MaxX",
        "CostUnit",
        "Cost",
        "DSSMScore",
        # "DSSMScore2",
        "ShowsRelevance",
        "ClicksRelevance",
    )
    for col_name in float_cols:
        if df[col_name].dtype != 'float64':
            df[col_name] = df[col_name].astype('float64')

    # convert currency
    df["Cost"] = df["Cost"] * df["Currency"]
    df.drop(["Currency"], axis=1, inplace=True)
    df["QueryWordsCnt"] = df["QueryWordsCnt"].apply(lambda x: min(x, 7))
    df["PhraseWordsCnt"] = df["PhraseWordsCnt"].apply(lambda x: min(x, 7))
    df["QueryNonBoldedWordsTitleBody"] = df["QueryNonBoldedWordsTitleBody"].fillna('').apply(lambda x: u" ".join(sorted(split_text_by_comma(unicode(x)))))
    df["NewCTRFromNested"] = new_ctr_col(df)
    df["NewCPCFromNested"] = new_cpc_col(df)
    df["NewClicksFromNested"] = (df["Shows"] * df["NewCTRFromNested"] - df["Clicks"]).apply(lambda val: max(0, val))
    df["NewCostFromNested"] = df["NewClicksFromNested"] * df["NewCPCFromNested"]

    df = df.assign(MaxX=lambda xf: xf.apply(
        lambda xr: max(xr["eShows"], xr["MaxX"]), axis=1))

    df = df.assign(
        NewClicks=lambda xf: xf.Clicks / xf.eShows * (xf.MaxX - xf.eShows),
        NewShows=lambda xf: xf.MaxX - xf.eShows,
        eShowsShare=lambda xf: xf.eShows / xf.MaxX
    )

    return df


def preprocess_mobile(df):
    """(DataFrame) -> DataFrame"""

    if df is None or df.shape[0] == 0:
        return None

    columns = {
        # Dimensions
        u"DateBegin": u"DateBegin",
        u"DateFinish": u"DateFinish",
        u"Currency": u"Currency",
        u"Логин": "LoginName",
        u"Название кампании": "CampaignName",
        u"ID кампании": "CampaignID",
        u"Название группы": "GroupName",
        u"ID группы": "GroupID",
        u"ID баннера": "DirectBannerID",
        u"Заголовок баннера": "Title",
        u"текст баннера": "Body",
        u"ID ключевого слова (direct)": "DirectPhraseID",
        u"ID ключевого слова (BS)": "BSPhraseID",
        u"Ключевое слово (без минус-слов)": "PhraseText",
        u"Количество слов из ключевого слова, входящих в заголовок и текст баннера": "BoldedWordsCnt",
        u"Количество слов в ключевом слове": "WordsCnt",
        u"Список слов фразы, не входящих в баннер (через пробел)": "NonBoldedWords",
        u"Хеш картинки баннера": "ImageHash",
        u"флаг наличия шаблона в баннере": "HasTemplate",
        u"Флаг корректного использования шаблона (при подстановке текущего ключевого слова баннер соответствует ограничениям директа)": \
        u"IsCorrectTemplate",
        u"Cредняя позиция показов (spec)": "AvgPosShowsSpec",
        u"cредняя позиция кликов (spec)": "AvgPosClicksSpec",
        u"Cредняя позиция показов (others)": "AvgPosShowsOther",
        u"cредняя позиция кликов (others)": "AvgPosClicksOther",
        u"текущая ставка на поиске": "BidSearch",
        u"текущая ставка на тематических площадках": "BidRSYA",
        u"дневной лимит на кампанию": "DayBudget",
        u"код временного таргетинга": "TimeTarget",
        u"Стратегия показа": "Strategy",
        # MOBILE_APP_AD (как TEXT_AD), IMAGE_AD (вместо MOBILE_APP_IMAGE_AD)
        # https://st.yandex-team.ru/CUSTOMSOLUTIONS-1938
        # https://tech.yandex.ru/direct/doc/dg/objects/ad-docpage/
        # https://tech.yandex.ru/direct/doc/dg/objects/adimage-docpage/#type
        u'Тип объявления': "AdType",
        u'Размер изображения (для фикс. размеров)': "FixedImageSize",
        # https://st.yandex-team.ru/CUSTOMSOLUTIONS-2027
        u'Размер изображения (из yt, HxW)': "YTImageSize",
        # Mobile Metrics added 10.01.2017
        u'Показы на мобильных': "ShowsMobile",
        u'Клики на мобильных': "ClicksMobile",
        u'Расходы на мобильных': "CostMobile",
        # Metrics
        u"Показы на поиске в блоке спецразмещения": "ShowsSpec",
        u"Клики на поиске в блоке спецразмещения": "ClicksSpec",
        u"Расходы на поиске за клики в блоке спецразмещения": "CostSpec",
        u"Показы на поиске в блоках гарантированных и динамических показов": "ShowsOther",
        u"Клики на поиске в блоках гарантированных и динамических показов": "ClicksOther",
        u"Расходы на поиске за клики в блоке гарантированных и динамических показов": "CostOther",
        u"Показы на тематических площадках": "ShowsRSYA",
        u"Клики на тематических площадках": "ClicksRSYA",
        u"Расходы за клики на тематических площадках": "CostRSYA",
        # MobileApp Specific
        u"Ссылка на приложение": "AppLink",
        u"Возрастные ограничения": "AgeLimit",
        # Targeting
        u"Показывать на смартфонах": "ShowOnSmartphones",
        u"Показывать на планшетах": "ShowOnTablets",
        u"Показывать в мобильной сети": "ShowOnCellular",
        u"Тип ОС": "OSType",
        u"Версия ОС": "OSVersion",
        # Additional parameters
        u"Используется трекинговая ссылка": "HasTrackingLink",
        u"Отображать иконку": "ShowIcon",
        u"Отображать цену": "ShowPrice",
        u"Отображать рейтинг": "ShowRating",
        u"Отображать количество оценок": "ShowRatingsCount",
        # Metrics
        u"Количество установок": "Installations",
        u"Установки (mobile Andriod)": "InstallsMobileAndroid",
        u"Установки (mobile iOS)": "InstallsMobileIOS",
        u"Установки (tablet Andriod)": "InstallsTabletAndroid",
        u"Установки (tablet iOS)": "InstallsTabletiOS"
    }

    df.rename(columns=columns, inplace=True)
    if not all(df.columns.isin(columns.values())):
        err = SourcesError(u"Some columns hasn't been translated")
        msg = u"Missed: %s" % df.columns[~df.columns.isin(columns.values())]
        err.message += "\n" + msg
        logging.error(msg, exc_info=1)
        raise err

    df["Shows"] = df.ShowsOther + df.ShowsRSYA + df.ShowsSpec
    df["Clicks"] = df.ClicksOther + df.ClicksRSYA + df.ClicksSpec
    df["Cost"] = df.CostOther + df.CostRSYA + df.CostSpec
    df["ShowsSearch"] = df.ShowsOther + df.ShowsSpec
    df["ClicksSearch"] = df.ClicksOther + df.ClicksSpec
    df["CostSearch"] = df.CostOther + df.CostSpec

    if "AppLink" in df.columns:
        df.AppLink = df.AppLink.apply(try_trim_urls_4excel)

    if "BoldedWordsCnt" in df.columns and df.BoldedWordsCnt.dtype.name not in {"float64", "int64"}:
        df.BoldedWordsCnt = df.BoldedWordsCnt.replace({"None": pd.np.nan}).astype(pd.np.float64)
    if "BoldedWordsCnt" in df.columns and "WordsCnt" in df.columns:
        fix_cnt = lambda row: min(row["BoldedWordsCnt"], row["WordsCnt"])  # noqa: E731
        df["BoldedWordsCnt"] = df.apply(fix_cnt, axis=1)
    if "BoldedWordsCnt" in df.columns and "WordsCnt" in df.columns and "NonBoldedWords" in df.columns:
        process_bolded = lambda r: u"" if r["BoldedWordsCnt"] == r["WordsCnt"] else r["NonBoldedWords"]
        df.NonBoldedWords = df.apply(process_bolded, axis=1)

    if "IsCorrectTemplate" in df.columns:
        if df.IsCorrectTemplate.dtype != 'bool':
            df.IsCorrectTemplate = df.IsCorrectTemplate.replace({"None": None, "True": True, "False": False})

    if df.HasTemplate.dtype != 'bool':
        df.HasTemplate = df.HasTemplate.replace({"None": None, "True": True, "False": False})

    # convert currency
    for col in ["Cost", "CostSearch", "CostRSYA", "CostSpec", "CostOther", "CostMobile"]:
        df[col] = df[col].replace('None', '0')
        df[col] = df[col].replace('', '0')
        df[col] = df[col].astype(pd.np.float64) * df.Currency

    df.drop(["Currency"], axis=1, inplace=True)

    df = filter_by_campaigns(df)

    df["ShowsDesktop"] = df["Shows"] - df["ShowsMobile"]
    df["ClicksDesktop"] = df["Clicks"] - df["ClicksMobile"]
    df["CostDesktop"] = df["Cost"] - df["CostMobile"]

    return df


def KeywordsDF():
    logging.debug("start preprocessing of _keywords")
    df_report = preprocess_keywords(sources_from_tar("_keywords"))
    logging.debug("finish preprocessing of _keywords")

    return set_report_meta(df_report, "keywords")


def SearchQueriesDF():
    logging.debug("start preprocessing of _search_queries")
    df_report = sources_from_tar("_search_queries")
    df_report = preprocess_search_queries(df_report)

    logging.debug("finish preprocessing of _search_queries")

    return set_report_meta(df_report, "search_queries")


def MobileDF():
    logging.debug("start preprocessing of _mobile")
    df_report = preprocess_mobile(sources_from_tar("_mobile"))
    logging.debug("finish preprocessing of _mobile")

    return set_report_meta(df_report, "mobile")
