# coding: utf-8
"""utils"""
import requests
from .const import IMG_MAX_DOWNLOAD, ARGS_FILE_PATH, IMG_DIR_PATH
from .configure_reports import return_report_name
from .error import ParseError
from xlsx_chart import get_charts_config
from xlsx_chart.chart import (
    COLORS,
    GidesChart,
    customize_chart_config,
    add_series_by_rows,
    add_series_by_columns
)


try:
    import urllib2
except ImportError:
    import urllib.request as urllib2
from urlparse import urlsplit, urlunsplit
from os.path import isfile, splitext, basename, join as pjoin, dirname
from datetime import datetime
from collections import namedtuple
from glob import glob
import json
import re
import logging

from PIL import Image
import pandas as pd


def get_image_shape(img_hash):
    """
    (str) -> typle<str, int?, int?>

    >>> get_image_shape("1ASndLYnISWIO68c5ksAUA")
    ('1ASndLYnISWIO68c5ksAUA', 173, 150)
    """
    url = "https://direct.yandex.ru/images/%s" % img_hash
    height, width = None, None
    try:
        res = urllib2.urlopen(url)
        if res.code == 200:
            img = Image.open(res)
            height, width = img.size
            with open(pjoin(IMG_DIR_PATH, "%s.png" % img_hash)) as fd:
                img.save(fd)
    finally:
        return img_hash, height, width


def classify_group_format(row, groups):
    group_id = row['GroupID']
    is_standart = groups[group_id]['YTImageSizeIsStandart']
    is_wide = groups[group_id]['YTImageSizeIsWide']
    has_image = groups[group_id]['HasYTImageSize']

    if is_standart and not is_wide:
        return 'standart'
    elif is_wide and not is_standart:
        return 'wide'
    elif is_standart and is_wide:
        return 'both'
    elif not has_image:
        return 'None'
    else:
        return 'small'


def save_report_meta(writer, meta, sheet_name, *_, **kw_opt):
    """
    meta - dict с ключами
    - 'report_type'
    - 'client'
    - 'period' = {'start': , 'end'}
    - 'currency'
    - 'report_date'
    """
    vat = u'без НДС' if meta.get('report_local_name') == 'search_queries' else u'с НДС'

    wb = writer.book
    ws = wb.add_worksheet(sheet_name)

    title_format = wb.add_format({'font_size': 24,
                                  'bold': 1,
                                  'align': 'center',
                                  'valign': 'vcenter',
                                  'text_wrap': 'true'
                                  })

    subtitle_format = wb.add_format({'font_size': 14,
                                     'bold': 1,
                                     'align': 'center',
                                     'valign': 'vcenter'
                                     })

    text_format = wb.add_format({'font_size': 11,
                                 'align': 'center',
                                 'valign': 'vcenter',
                                 'num_format': '@'
                                 })

    ws.merge_range('A1:F4', meta.get('report_type'), title_format)

    ws.write('H2', u'Скрыть все примечания к ячейкам в отчете:')
    ws.insert_image('H3', pjoin(dirname(__file__), 'hide_comments.png'))

    ws.merge_range('B7:E8', u'Клиент:', subtitle_format)
    ws.merge_range('B10:E10', meta.get('client'), text_format)

    ws.merge_range('B13:E14', u'Период:', subtitle_format)
    ws.merge_range('B16:C16', meta.get('period').get('start'), text_format)
    ws.merge_range('D16:E16', meta.get('period').get('end'), text_format)

    ws.merge_range('B19:E20', u'Валюта:', subtitle_format)
    ws.merge_range('B22:E22', meta.get('currency'), text_format)
    ws.write('F22', vat)

    ws.merge_range('B25:E26', u'Дата построения отчета:', subtitle_format)
    ws.merge_range('B28:E28', meta.get('report_date'), text_format)

    logging.info(sheet_name)

    return writer


def process_login(name):
    "(str) -> str"

    return name.replace('.', '-').lower().strip()


def extract_login(filename):
    "(str) -> str"

    return process_login(splitext(basename(filename))[0].split("_")[1])


def extract_period_from_tar(tar_path):
    """(str) -> list <str>
    tar_path = "<login>_<currency>_<yyyy-mm-dd>-<yyyy-mm-dd>.tar.gz"
    """
    code = tar_path.split("_").pop()
    code = code.strip().replace(".tar.gz", "")

    start_date, end_date = datetime.strptime(code[:10], "%Y-%m-%d"), datetime.strptime(code[-10:], "%Y-%m-%d")

    return [start_date, end_date]


def extract_period_from_tsv(tsv_path):
    """(str) -> list <str>
    tar_path = "<login>_<currency>_<yyyy-mm-dd>-<yyyy-mm-dd>_<report_name>.tar.gz"
    """
    code = tsv_path.split("_")[2]
    code = code.strip().replace(".tar.gz", "")

    start_date, end_date = datetime.strptime(code[:10], "%Y-%m-%d"), datetime.strptime(code[-10:], "%Y-%m-%d")

    return [start_date, end_date]


def get_meta_client(sources, all=False):
    """(list <str>) -> str
    sources = list of *tar.gz files
    """
    logins = list(set(map(extract_login, sources)))
    if len(logins) == 1:
        return logins.pop()
    else:
        return u"Суммарно"


def get_meta_currency(sources):
    """(list <str>) -> str
    sources = list of *tar.gz files
    """
    curs = list(set(map(extract_str_currency_from_tar, sources)))
    if len(curs) == 1:
        return curs.pop()
    else:
        return u"Не определена"


def get_meta_period(df):
    "(list [<str>, <str>]) -> {'start': <str>, 'end': <str>}"
    start_period = df['DateBegin'].unique().tolist().pop()
    end_period = df['DateFinish'].unique().tolist().pop()

    return {"start": start_period, "end": end_period}


def set_report_meta(df, report_type):

    if df is None:
        return None

    src_wildcard = "./*.tar.gz"
    sources = glob(src_wildcard)

    info = {"report_type": return_report_name(report_type),
            "report_local_name": report_type,
            "client": get_meta_client(sources),
            "logins": get_meta_client(sources, all=True),
            "currency": get_meta_currency(sources),
            "period": get_meta_period(df),
            "report_date": datetime.now().strftime("%Y-%m-%d")}

    setattr(df, "meta", info)
    return df


def get_report_meta(df):
    "(DataFrame) -> dict()"
    return getattr(df, "meta")


def get_max_images():
    if isfile(ARGS_FILE_PATH):
        with open(ARGS_FILE_PATH) as src:
            ARGS = json.load(src)
            return 0 if "--no-images" in ARGS and ARGS["--no-images"] else IMG_MAX_DOWNLOAD
    else:
        return 0


def try_trim_urls_4excel(url):
    u"""Excel's url max size is 255"""
    if len(url) < 255:
        return url
    url_parts = urlsplit(url)
    STORE_HOSTS = {
        "itunes.apple.com",
    }
    if url_parts.hostname in STORE_HOSTS:
        return urlunsplit(url_parts._replace(query='', fragment=''))

    return None


def extract_currency(file_name):
    "(str) -> float"
    if "_" in file_name:
        k = float(file_name.rsplit(".", 1)[0].rsplit("_", 1)[-1])
    else:
        k = 1.0

    return k


def extract_str_currency_from_tar(tar_path):
    """(str) -> str
    tar_path = "<issue>_<login>_<currency>.tar.gz"
    """
    code = tar_path.split("_", 2)[2].replace('.tar.gz', '').strip()
    if ',' in code:
        raise ParseError("forbidden symbol in <%s>: %s" % (tar_path, ','))
    if not code:
        raise ParseError("there is no currency code: <%s>" % code)

    return code


def extract_currency_from_tar(tar_path):
    """(str) -> float
    tar_path = "<issue>_<login>_<currency>.tar.gz"
    """
    currency_str = extract_str_currency_from_tar(tar_path)
    try:
        return float(currency_str)
    except ValueError as err:
        raise ParseError(err.message)


def get_all_campaigns(row, camp_df):
    # Чтобы сджоинить все кампании на логин с определенной Strategy для ссылки review_keywords_url
    if 'LoginName' in row.index:
        try:
            return ', '.join(
                [str(x) for x in list(camp_df.loc[(row['LoginName'], row['StrategyType']), :].values)]
            ).replace('[', '').replace(']', '')
        except KeyError:
            return ''
    else:
        return ', '.join(
            [str(x) for x in list(camp_df.loc[row['Login'], :].values)]
        ).replace('[', '').replace(']', '')


def review_keywords_url(row):
    url = \
        "https://direct.yandex.ru/registered/main.pl?show_stat=1&cmd=showStat" \
        "&group_by_date=none" \
        "&&group_by=campaign%2Ctargettype" \
        "&ulogin={login}" \
        "&date_from={date_from}&date_to={date_to}" \
        "&with_nds=1" \
        "&fl_campaign_type__eq%5B%5D=text" \
        "&fl_campaign__eq%5B%5D={campaigns}" \
        "&fl_banner_type__eq=text" \
        "&fl_contexttype_orig__eq%5B%5D=phrases" \
        "&fl_match_type__eq%5B%5D=none" \
        "&sort=shows".format(
            login=row['LoginName'], campaigns='%2C'.join([str(cid.strip()) for cid in row['Campaigns'].split(',')]),
            date_from=row['DateBegin'], date_to=row['DateFinish']
        )
    return url


def review_search_queries_url(row):
    url = \
        "https://direct.yandex.ru/registered/main.pl?show_stat=1&cmd=showStat" \
        "&stat_type=search_queries" \
        "&group_by_date=none" \
        "&group_by=search_query%2Ccampaign%2Cbanner%2Ccontextcond_orig " \
        "&ulogin={login}" \
        "&date_from={date_from}&date_to={date_to}" \
        "&with_nds=0" \
        "&fl_campaign_type__eq%5B%5D=text" \
        "&fl_campaign__eq%5B%5D={campaigns}" \
        "&fl_banner_type__eq=text" \
        "&fl_contexttype_orig__eq%5B%5D=phrases" \
        "&fl_page__eq=Яндекс" \
        "&fl_match_type__eq%5B%5D=none".format(
            login=row['Login'], campaigns='%2C'.join([str(cid.strip()) for cid in row['Campaigns'].split(',')]),
            date_from=row['DateBegin'], date_to=row['DateFinish']
        )
    return url


def search_cid(s):
    return "https://direct.yandex.ru/registered/main.pl?searchcid=%s&cmd=search&who=camps" % s


def search_bid(s):
    return "https://direct.yandex.ru/registered/main.pl?cmd=searchBanners&where=direct&what=num&text_search=%s" % s


def get_cid_bid(c, b):
    return "https://direct.yandex.ru/campaigns/%s/banners/%s" % (int(c), int(b))


def get_cid(c):
    return "https://direct.yandex.ru/campaigns/%s" % int(c)


def get_group_url(campaign_id, group_id):
    return "https://direct.yandex.ru/campaigns/%s/adgroups/%s" % (int(campaign_id), int(group_id))


def get_strategy_type(row):
    """
    (dict<str, float>) -> 'None' | 'only_rsya' | 'only_search' | 'both'

    expected keys: ShowsRSYA, ShowsSearch
    """

    has_rsya = row['ShowsRSYA'] > 0
    has_search = row['ShowsSearch'] > 0

    if has_rsya and has_search:
        return 'both'
    elif has_rsya:
        return 'only_rsya'
    elif has_search:
        return 'only_search'
    else:
        return 'None'


def extract_timetarget(s):
    """(str)->DataFrame
    #fixme try to use map(literal_eval, literal_eval(s)["Schedule"]["Items"])
    """
    return pd.DataFrame(
        map(lambda x: map(int, x.split(",")),
            re.findall("(\d[\d,]+\d)", s.split("Items", 1)[1].encode("u8"))),
        index=(u"Пн", u"Вт", u"Ср", u"Чт", u"Пт", u"Сб", u"Вс"))


def drop_antiwords(text):

    return re.split("( [-~][^ ]+)+",
                    text.strip())[0].strip()  # drop anti-words, ~0


def text_to_words(text):
    translate_table = dict((ord(char), u" ") for char in u"!?.,")
    text = text.translate(translate_table)
    text = re.split("[ ]+", text.strip())

    return text


def is_uppercase_word(text):
    """(unicode) -> bool"""
    if not isinstance(text, unicode):
        raise TypeError("expected unicode for `text`. in fact: %s" %
                        type(text))

    white_list = {u"РФ"}

    return (len(text) > 1) and all(map(unicode.isupper, text)) and (
        text not in white_list)


def has_uppercase_word(text):
    """(unicode) -> bool"""
    if not isinstance(text, unicode):
        raise TypeError("expected unicode for `text`. in fact: %s" %
                        type(text))

    words = text_to_words(text)

    return any(map(is_uppercase_word, words))


def phrase_process(text, to_lower=True):
    """(str|unicode) -> unicode"""
    try:
        text = text.strip("\"[]()")
    except AttributeError:
        return u""

    if not isinstance(text, unicode):
        text = text.decode("utf-8")

    if to_lower:
        text = text.lower()
    text = drop_antiwords(text)

    return " ".join(sorted(text_to_words(text)))


def classify_imgsize(shape):
    """
    (tuple<width:int, height:int>) -> 'bad_img (<= 450px)' | 'good_img (>= 1000)' | 'norm_img (others)'
    """
    w, h = shape
    if h <= 450 or w <= 450:
        return 'bad_img (<= 450px)'
    if h >= 1000 or w >= 1000:
        return 'good_img (>= 1000)'

    return 'norm_img (others)'


def note_maker(text, cell="A1", **kw_opt):
    """(str, str, **kw_opt) -> ((ExcelWriter, str, *args, **kw_args) -> ExcelWriter)"""

    def note(writer, sheet_name, *_, **__):
        sheet = writer.book.get_worksheet_by_name(sheet_name)
        sheet.write_comment(cell, text, kw_opt)

        return writer

    return note


def return_format(wb, col_format):
    if not type(col_format) is dict:
        format_type = col_format
    else:
        format_type = col_format.get('type')
    assert format_type in {'header', 'string', 'hyperlink', 'int', 'float', 'percent', 'bool', 'gain', None}, \
        'format_type is unknown: %s' % format_type

    format = None
    if format_type == 'string':
        format = wb.add_format({'font_size': 11,
                                'align': 'left',
                                'valign': 'vleft',
                                'num_format': '@'
                                })
    elif format_type == 'int':
        format = wb.add_format({'font_size': 11,
                                'align': 'right',
                                'valign': 'vright',
                                'num_format': '#,##0'
                                })
    elif format_type == 'float':
        format = wb.add_format({'font_size': 11,
                                'align': 'right',
                                'valign': 'vright',
                                'num_format': '#,##0.00'
                                })
    elif format_type == 'percent':
        format = wb.add_format({'font_size': 11,
                                'align': 'right',
                                'valign': 'vright',
                                'num_format': '0.00%'
                                })
    elif format_type == 'gain':
        format = wb.add_format({'font_size': 11,
                                'align': 'right',
                                'valign': 'vright',
                                'num_format': '+0%; -0%'
                                })
    elif format_type == 'header':
        format = wb.add_format({'font_size': 11,
                                'bold': 1,
                                'align': 'left',
                                'valign': 'vleft',
                                'num_format': '@'
                                })
    elif format_type == 'bool':
        format = wb.add_format({'font_size': 11,
                                'align': 'left',
                                'valign': 'vleft',
                                'num_format': u'"ИСТИНА"; "ЛОЖЬ"; "ЛОЖЬ"'
                                })

    return format


def write_df_with_formats(writer, df, sheet_name, **kwargs):
    if hasattr(df, 'formats'):  # форматирование по названию колонки
        formats = df.formats
        for col_name in df.formats.iterkeys():  # Проверка, что колонки в форматах есть в df
            assert col_name in df.columns, u'unknown column in formats: %s' % col_name
        w_type = 'cols'

    elif hasattr(df, 'row_formats'):  # форматирование по номеру строки
        formats = df.row_formats
        w_type = 'rows'

    elif hasattr(df, 'table_formats'):  # форматирование всех значений таблицы, запись по колонкам
        formats = df.table_formats
        w_type = 'cols'

    else:
        formats = None
        w_type = 'cols'

    wb = writer.book
    ws = wb.get_worksheet_by_name(sheet_name)
    if ws is None:
        ws = writer.book.add_worksheet(sheet_name)

    start_row, start_col = kwargs.get('startrow', 0), kwargs.get('startcol', 0)
    if kwargs.get('index', True):  # Количество колонок-индексов
        ind_cols = len(df.index.levels) if hasattr(df.index, 'levels') else 1
    else:
        ind_cols = 0

    # Пишу названия строк (индексы)
    if ind_cols == 1:
        indices = [df.index.name] + df.index.tolist()
        ws.write_column(start_row, start_col, indices, return_format(wb, 'header'))
    elif ind_cols != 0:
        for i, ind_name in enumerate(df.index.names):
            indices = [ind_name] + [ind[i] for ind in df.index.tolist()]
            ws.write_column(start_row, start_col + i, indices, return_format(wb, 'header'))

    # Пишу названия столбцов
    if kwargs.get('header', 'True'):
        tar_col = start_col + ind_cols
        ws.write_row(start_row, tar_col, df.columns, return_format(wb, 'header'))
        start_row += 1

    tar_col = start_col + ind_cols
    df = df.replace(to_replace=[pd.np.nan, pd.np.inf, -pd.np.inf], value='')

    if w_type == 'cols':
        for col_num, col_name in enumerate(df.columns):
            try:
                format = return_format(wb, formats.get(col_name))
            except:
                format = return_format(wb, formats)

            if isinstance(formats, dict):
                if formats.get(col_name) == 'hyperlink':
                    for i, col_value in enumerate(df[col_name].values):

                        for try_ in range(0, 4):
                            try:
                                r = requests.get('https://nda.ya.ru/--', params={'url': col_value})
                                ws.write_url(start_row + i, tar_col + col_num, url=r.content)
                                break
                            except:
                                pass

                        # ws.write_column(start_row, tar_col + col_num, df[col_name].values, format)
                else:
                    ws.write_column(start_row, tar_col + col_num, df[col_name].values, format)
            else:
                ws.write_column(start_row, tar_col + col_num, df[col_name].values, format)

    elif w_type == 'rows':
        for row_num, row_values in enumerate(df.itertuples()):
            format = return_format(wb, formats.get(row_num + 1, None)) if formats else None
            ws.write_row(start_row + row_num, tar_col, row_values[1:], format)

    return writer


def add_sheet_to_xlsx(writer, df, sheet_name, sheet_fmt, **kw_opt):
    """(ExcelWriter, DataFrame, str, [(ExcelWriter, str, DataFrame) -> ExcelWriter], **kw_opt) ->
       ExcelWriter
    Используя `ExcelWriter` сохраняет `DataFrame` на вкладку с именем `sheet_name`
    """

    if df is None or df.shape[0] == 0:
        logging.warn(sheet_name)
        return writer

    write_df_with_formats(writer, df, sheet_name, **kw_opt)

    for fmt in sheet_fmt:
        fmt(writer, sheet_name, df)

    if getattr(df, "show_comments", False):
        ws = writer.book.get_worksheet_by_name(sheet_name)
        ws.show_comments()

    logging.info(sheet_name)

    return writer


def plot_maker(chart_names, **kwargs):

    def plot(writer, sheet_name, df, **__):
        sheet = writer.book.get_worksheet_by_name(sheet_name)

        for chart_name in chart_names:
            charts_config = get_charts_config()
            chart_params = charts_config[chart_name]
            custom_config = customize_chart_config(charts_config['base_chart'], chart_params)
            if custom_config.get('last_row') is None:
                custom_config['last_row'] = df.index.size + custom_config['first_row']

            if custom_config.get('colors') is None:
                custom_config['colors'] = COLORS

            chart = GidesChart(writer.book, **custom_config)
            if custom_config.get('columns') is None:
                chart = add_series_by_rows(sheet_name, chart, custom_config)
            else:
                add_series_by_columns(sheet_name, chart, custom_config)

            sheet.insert_chart(custom_config['target_cell'], chart)

        return writer

    return plot


def filter_by_adtype(df, adtype):

    assert adtype in {
        "TEXT_AD",
        "MOBILE_APP_AD"
    }

    if "AdType" in df.columns:
        return df.loc[df["AdType"] == adtype]

    return df


def colorize_str_to_console(text, color):
    _CONSOLE_COLOR = {
        "default": "\033[0m",  # white (normal)
        "red": "\033[31m",
        "green": "\033[32m",
        "orange": "\033[33m",
        "blue": "\033[34m",
        "purple": "\033[35m",
    }
    if color not in _CONSOLE_COLOR:
        raise TypeError("`color` is not in %s" % _CONSOLE_COLOR.keys())

    options = {
        "color": _CONSOLE_COLOR[color],
        "text": text,
        "default": _CONSOLE_COLOR["default"]
    }
    template = "{color}{text}{default}"

    return template.format(**options)


def translate_cols(df):
    "(pd.DataFrame) -> None"
    en_ru = {
        "LoginName": u"Логин",
        "CampaignName": u"Название капании",
        "CampaignID": u"Кампания №",
        "GroupName": u"Название группы",
        "GroupID": u"Группа №",
        "DirectBannerID": u"Баннер №",
        "Title": u"Заголовок",
        "Body": u"Объявление",
        "URL": u"URL",
        "DirectPhraseID": u"Фраза №",
        "BSPhraseID": u"БК-Фраза №",
        "PhraseText": u"Текст фразы",
        "AntiWords": u"Минус-слова",
        "BoldedWordsCnt": u"Подсвеченные слова",
        "WordsCnt": u"Кол-во слов",
        "NonBoldedWords": u"Слова без подсветки",
        "ImageHash": u"Хэш картинки",
        "SitelinksCnt": u"Кол-во сайтлинков",
        "HasTemplate": u"Есть шаблон",
        "IsCorrectTemplate": u"Корректный шаблон",
        "CalloutsCnt": u"Кол-во уточнений",
        "AvgPosShowsSpec": u"Ср. поз. показа в (спец.)",
        "AvgPosClicksSpec": u"Ср. поз. клика в (спец.)",
        "AvgPosShowsOther": u"Ср. поз. показа (юж./пр. блок)",
        "AvgPosClicksOther": u"Ср. поз. клика (юж./пр. блок)",
        "Productivity": u" Продуктивность",
        "BidSearch": u"Ставка на поиске",
        "BidRSYA": u"Ставка в сетях",
        "DayBudget": u"Дневной лимит",
        "TimeTarget": u"Временно таргетинг",
        "Strategy": u"Стратегия",
        "SearchStrategy": u"Стратегия (поиск)",
        "RSYAStrategy": u"Стратегия (сети)",
        "DisplayUrlHasTemplate": u"Шаблон в отображаемой ссылке",
        "DescriptionSLCnt": u"Описания к быстрым ссылок",
        "VisitCardUsage": u"Визитка используется",
        "isMobileBanner": u"Баннер для мобильных",
        "AdType": u"Тип креатива",
        "AdSubtype": u"Подтип креатива",
        "FixedImageSize": u"Размер изображения",
        "ShowsMobile": u"Показы (моб.)",
        "ClicksMobile": u"Клики (моб.)",
        "CostMobile": u"Затраты (моб.)",
        "CTRMobile": u"CTR (моб.)",
        "CPCSpec": u"CPC (спец.)",
        "ShowsSpec": u"Показы (спец.)",
        "ClicksSpec": u"Клики (спец.)",
        "CostSpec": u"Затраты (спец.)",
        "CTRSpec": u"CTR (спец.)",
        "CPCSpec": u"CPC (спец.)",
        "ShowsOther": u"Показы (юж./пр. блок)",
        "ClicksOther": u"Клики (юж./пр. блок)",
        "CostOther": u"Затраты (юж./пр. блок)",
        "CTROther": u"CTR (юж./пр. блок)",
        "CPCOther": u"CPC (юж./пр. блок)",
        "ShowsRSYA": u"Показы (сети)",
        "ClicksRSYA": u"Клики (сети)",
        "CostRSYA": u"Затраты (сети)",
        "CTRRSYA": u"CTR (сети)",
        "CPCRSYA": u"CPC (сети)",
        "HelperUrl": u"Ссылка на кампанию",
    }
    df.rename(columns=en_ru, inplace=True)


TEMPLATE_CHAR = re.compile(r'#')
IGNORED_CHARS = re.compile(r"""[.,!"':;]""")
SPEC_CHARS = re.compile(r"""[!+]""")
IGNORED_MAX_COUNT = 15


def _drop_antiwords(phrase):
    antiword_start_idx = phrase.find(" -")
    if antiword_start_idx > 0:
        return phrase[: antiword_start_idx].strip()
    return phrase


def _process_phrase(phrase):
    return SPEC_CHARS.sub('', _drop_antiwords(phrase)).strip()


def _has_template(text):
    return text.count('#') >= 2


def _size_of(text):
    u"""размер текста без учета пунктуации с ограниченим
        по количеству игнорируемых символов"""
    _, replacements = IGNORED_CHARS.subn('', text)
    return len(text) - min(replacements, IGNORED_MAX_COUNT)


def is_correct_template_usage(text, phrase, max_size):
    u"""Пытается подставить `phrase` в `text` вместо шаблона (#{to_replace}#) с контролем по `max_size`.

    Returns:
      False - используется фраза по умолчанию
      True - подставляет фраза
      None - подставлять нечего или некуда (отсутствует шаблон)

    Signature:
    (Utf8?, Utf8?, Uint) -> Bool?

    Note:
    Известные ограничения (на 2017-09-19):
    * Основной заголовок - 35
    * Вспомогательный заголовок - 30
    * Тело баннера - 81
    * При подсчете длины после подстановок не учитывается до 15 символов пунктуации.
    * !Парность # не проверяется, избыток затирается!
    * `phrase` очищается от `SPEC_CHARS`
    * минус-слова из `phrase` отбрасываются

    [Подробнее про шаблоны](https://yandex.ru/support/direct/features/ad-templates.html?lang=ru)
    """

    if text is None:
        return None

    if not _has_template(text):
        return None

    if phrase is None:
        return None

    phrase = _process_phrase(phrase)
    if not phrase:
        return None

    if not isinstance(text, unicode):
        raise TypeError("type of `text` is not unicode")
    if not isinstance(phrase, unicode):
        raise TypeError("type of `phrase` is not unicode")
    if max_size and max_size < 0:
        raise ValueError("`max_size` is < 0")

    formated_text = re.sub(r'(#.*?#)', phrase, text)
    formated_text = TEMPLATE_CHAR.sub('', formated_text).strip()

    return _size_of(formated_text) <= max_size


PhraseInfo = namedtuple("PhraseInfo", ['fixed_phrase', 'fixed_form', 'fixed_stopwords', 'fixed_order'])


def get_phrase_info(phrase):
    "unicode -> PhraseInfo<fixed_phrase:bool, fixed_form:bool, fixed_stopwords:bool, fixed_order:bool>"
    assert isinstance(phrase, unicode), "unexpected class of argument `phrase`: %s" % type(phrase)
    phrase = drop_antiwords(phrase).strip()
    fixed_phrase = phrase.startswith('"') and phrase.endswith('"')
    fixed_form = phrase.find('!') >= 0
    fixed_stopwords = phrase.find('+') >= 0
    fixed_order = phrase.find('[') >= 0 and phrase.find(']') > 0

    return PhraseInfo(fixed_phrase, fixed_form, fixed_stopwords, fixed_order)
