# -*- coding: utf-8 -*-

from __future__ import unicode_literals

from copy import deepcopy

from pypinyin.compat import text_type, callable_check
from pypinyin.constants import (
    PHRASES_DICT, PINYIN_DICT,
    RE_HANS
)
from pypinyin.contrib.uv import V2UMixin
from pypinyin.contrib.neutral_tone import NeutralToneWith5Mixin
from pypinyin.contrib.tone_sandhi import ToneSandhiMixin
from pypinyin.exceptions import PinyinNotFoundException
from pypinyin.utils import _remove_dup_and_empty
from pypinyin.style import auto_discover
from pypinyin.style import convert as convert_style

auto_discover()


class Converter(object):

    def convert(self, words, style, heteronym, errors, strict, **kwargs):
        # TODO: use ``abc`` module
        raise NotImplementedError  # pragma: no cover


class DefaultConverter(Converter):
    def __init__(self, **kwargs):
        pass

    def convert(self, words, style, heteronym, errors, strict, **kwargs):
        """根据参数把汉字转成相应风格的拼音结果。

        :param words: 汉字字符串
        :type words: unicode
        :param style: 拼音风格
        :param heteronym: 是否启用多音字
        :type heteronym: bool
        :param errors: 如何处理没有拼音的字符
        :param strict: 只获取声母或只获取韵母相关拼音风格的返回结果
                       是否严格遵照《汉语拼音方案》来处理声母和韵母，
                       详见 :ref:`strict`
        :type strict: bool
        :return: 按风格转换后的拼音结果
        :rtype: list

        """
        pys = []
        # 初步过滤没有拼音的字符
        if RE_HANS.match(words):
            pys = self._phrase_pinyin(words, style=style, heteronym=heteronym,
                                      errors=errors, strict=strict)
            post_data = self.post_pinyin(words, heteronym, pys)
            if post_data is not None:
                pys = post_data

            pys = self.convert_styles(
                pys, words, style, heteronym, errors, strict)

        else:
            py = self.handle_nopinyin(words, style=style, errors=errors,
                                      heteronym=heteronym, strict=strict)
            if py:
                pys.extend(py)

        return _remove_dup_and_empty(pys)

    def pre_convert_style(self, han, orig_pinyin, style, strict, **kwargs):
        """在把原始带声调的拼音按拼音风格转换前会调用 ``pre_convert_style`` 方法。

        如果返回值不为 ``None`` 会使用返回的结果代替 ``orig_pinyin``
        来进行后面的风格转换。

        :param han: 要处理的汉字
        :param orig_pinyin: 汉字对应的原始带声调拼音
        :param style: 要转换的拼音风格
        :param strict: 只获取声母或只获取韵母相关拼音风格的返回结果
                       是否严格遵照《汉语拼音方案》来处理声母和韵母，
                       详见 :ref:`strict`
        :param kwargs: 其他关键字参数，暂时无用，用于以后扩展新的参数。
        :return: ``None`` 或代替 ``orig_pinyin`` 参与拼音风格转换的拼音字符串。

        """
        pass

    def convert_style(self, han, orig_pinyin, style, strict, **kwargs):
        """按 ``style`` 的值对 ``orig_pinyin`` 进行处理，返回处理后的拼音

        转换风格前会调用 ``pre_convert_style`` 方法，
        转换后会调用 ``post_convert_style`` 方法。

        :param han: 要处理的单个汉字
        :param orig_pinyin: 汉字对应的原始带声调拼音
        :param style: 拼音风格
        :param strict: 只获取声母或只获取韵母相关拼音风格的返回结果
                       是否严格遵照《汉语拼音方案》来处理声母和韵母，
                       详见 :ref:`strict`
        :param kwargs: 其他关键字参数，暂时无用，用于以后扩展新的参数。
        :return: 按拼音风格转换处理后的拼音

        """
        pre_data = self.pre_convert_style(
            han, orig_pinyin, style=style, strict=strict)
        if pre_data is not None:
            pinyin = pre_data
        else:
            pinyin = orig_pinyin

        converted_pinyin = self._convert_style(
            han, pinyin, style=style, strict=strict, default=pinyin)

        post_data = self.post_convert_style(
            han, pinyin, converted_pinyin, style=style, strict=strict)
        if post_data is None:
            post_data = converted_pinyin

        return post_data

    def post_convert_style(self, han, orig_pinyin, converted_pinyin,
                           style, strict, **kwargs):
        """在把原始带声调的拼音按拼音风格转换前会调用 ``pre_convert_style`` 方法。

        如果返回值不为 ``None`` 会使用返回的结果代替 ``converted_pinyin``
        作为拼音风格转换后的最终拼音结果。

        :param han: 要处理的汉字
        :param orig_pinyin: 汉字对应的原始带声调拼音
        :param converted_pinyin: 按拼音风格转换处理后的拼音
        :param style: 要转换的拼音风格
        :param strict: 只获取声母或只获取韵母相关拼音风格的返回结果
                       是否严格遵照《汉语拼音方案》来处理声母和韵母，
                       详见 :ref:`strict`
        :param kwargs: 其他关键字参数，暂时无用，用于以后扩展新的参数。
        :return: ``None`` 或代替 ``converted_pinyin`` 作为拼音风格转换后的拼音结果。

        """
        pass

    def pre_handle_nopinyin(self, chars, style, heteronym, errors,
                            strict, **kwargs):
        """处理没有拼音的字符串前会调用 ``pre_handle_nopinyin`` 方法。

        如果返回值不为 ``None`` 会使用返回的结果作为处理没有拼音字符串的结果，
        不再使用内置方法进行处理。

        :param chars: 待处理的没有拼音的字符串
        :param errors: 如何处理
        :param heteronym: 是否需要处理多音字
        :param kwargs: 其他关键字参数，暂时无用，用于以后扩展新的参数。
        :return: ``None`` 或代替 ``chars`` 参与拼音风格转换的拼音字符串
                  或拼音结果 list。

        """
        pass

    def handle_nopinyin(self, chars, style, heteronym, errors,
                        strict, **kwargs):
        """处理没有拼音的字符串。

        处理前会调用 ``pre_handle_nopinyin`` 方法，
        处理后会调用 ``post_handle_nopinyin`` 方法。

        :param chars: 待处理的没有拼音的字符串
        :param style: 拼音风格
        :param errors: 如何处理
        :param heteronym: 是否需要处理多音字
        :param strict: 只获取声母或只获取韵母相关拼音风格的返回结果
                       是否严格遵照《汉语拼音方案》来处理声母和韵母，
                       详见 :ref:`strict`
        :return: 处理后的拼音结果，如果为 ``None`` 或空 list 表示忽略这个字符串.
        :rtype: list
        """
        pre_data = self.pre_handle_nopinyin(
            chars, style, errors=errors, heteronym=heteronym, strict=strict)

        if pre_data is not None:
            py = pre_data
        else:
            pre_data = chars
            py = self._convert_nopinyin_chars(
                pre_data, style, errors=errors,
                heteronym=heteronym, strict=strict)

        post_data = self.post_handle_nopinyin(
            chars, style, errors=errors, heteronym=heteronym, strict=strict,
            pinyin=py)
        if post_data is not None:
            py = post_data

        if not py:
            return []
        if isinstance(py, list):
            # 包含多音字信息
            if isinstance(py[0], list):
                if heteronym:
                    return py
                # [[a, b], [c, d]]
                # [[a], [c]]
                return [[x[0]] for x in py]

            return [[i] for i in py]
        else:
            return [[py]]

    def post_handle_nopinyin(self, chars, style, heteronym,
                             errors, strict,
                             pinyin, **kwargs):
        """处理完没有拼音的字符串后会调用 ``post_handle_nopinyin`` 方法。

        如果返回值不为 ``None`` 会使用返回的结果作为处理没有拼音的字符串的结果。

        :param chars: 待处理的没有拼音的字符串
        :param errors: 如何处理
        :param heteronym: 是否需要处理多音字
        :param strict: 只获取声母或只获取韵母相关拼音风格的返回结果
                       是否严格遵照《汉语拼音方案》来处理声母和韵母，
                       详见 :ref:`strict`
        :param pinyin: 处理后的拼音信息，值为空 list 或包含拼音信息的 list
        :param kwargs: 其他关键字参数，暂时无用，用于以后扩展新的参数。
        :return: ``None`` 或代替 ``pinyin`` 做为处理结果。

        """
        pass

    def post_pinyin(self, han, heteronym, pinyin, **kwargs):
        """找到汉字对应的拼音后，会调用 ``post_pinyin`` 方法。

        如果返回值不为 ``None`` 会使用返回的结果作为 han 的拼音数据。

        :param han: 单个汉字或者词语
        :param heteronym: 是否需要处理多音字
        :param pinyin: 单个汉字的拼音数据或词语的拼音数据 list
        :type pinyin: list
        :param kwargs: 其他关键字参数，暂时无用，用于以后扩展新的参数。
        :return: ``None`` 或代替 ``pinyin`` 作为 han 的拼音 list。

        """
        pass

    def _phrase_pinyin(self, phrase, style, heteronym, errors, strict):
        """词语拼音转换.

        :param phrase: 词语
        :param errors: 指定如何处理没有拼音的字符
        :param strict: 只获取声母或只获取韵母相关拼音风格的返回结果
                       是否严格遵照《汉语拼音方案》来处理声母和韵母，
                       详见 :ref:`strict`
        :return: 拼音列表
        :rtype: list
        """
        pinyin_list = []
        if phrase in PHRASES_DICT:
            pinyin_list = deepcopy(PHRASES_DICT[phrase])
        else:
            for han in phrase:
                py = self._single_pinyin(han, style, heteronym, errors, strict)
                pinyin_list.extend(py)

        return pinyin_list

    def convert_styles(self, pinyin_list, phrase, style, heteronym, errors,
                       strict, **kwargs):
        """转换多个汉字的拼音结果的风格"""
        for idx, item in enumerate(pinyin_list):
            han = phrase[idx]
            if heteronym:
                pinyin_list[idx] = [
                        self.convert_style(
                            han, orig_pinyin=x, style=style, strict=strict)
                        for x in item
                    ]
            else:
                orig_pinyin = item[0]
                pinyin_list[idx] = [
                    self.convert_style(
                        han, orig_pinyin=orig_pinyin, style=style,
                        strict=strict)]

        return pinyin_list

    def _single_pinyin(self, han, style, heteronym, errors, strict):
        """单字拼音转换.

        :param han: 单个汉字
        :param errors: 指定如何处理没有拼音的字符，详情请参考
                       :py:func:`~pypinyin.pinyin`
        :param strict: 只获取声母或只获取韵母相关拼音风格的返回结果
                       是否严格遵照《汉语拼音方案》来处理声母和韵母，
                       详见 :ref:`strict`
        :return: 返回拼音列表，多音字会有多个拼音项
        :rtype: list
        """
        num = ord(han)
        # 处理没有拼音的字符
        if num not in PINYIN_DICT:
            return self.handle_nopinyin(
                han, style=style, errors=errors,
                heteronym=heteronym, strict=strict)

        pys = PINYIN_DICT[num].split(',')  # 字的拼音列表
        return [pys]

    def _convert_style(self, han, pinyin, style, strict, default,
                       **kwargs):
        if not kwargs:
            kwargs = {}
        kwargs['han'] = han

        return convert_style(pinyin, style, strict, default=default, **kwargs)

    def _convert_nopinyin_chars(self, chars, style, heteronym, errors, strict):
        """转换没有拼音的字符。

        """
        if callable_check(errors):
            return errors(chars)

        if errors == 'default':
            return chars
        elif errors == 'ignore':
            return None
        elif errors == 'exception':
            raise PinyinNotFoundException(chars)
        elif errors == 'replace':
            if len(chars) > 1:
                return ''.join(text_type('%x' % ord(x)) for x in chars)
            else:
                return text_type('%x' % ord(chars))


class _v2UConverter(V2UMixin, DefaultConverter):
    pass


class _neutralToneWith5Converter(NeutralToneWith5Mixin, DefaultConverter):
    pass


class _toneSandhiConverter(ToneSandhiMixin, DefaultConverter):
    pass


class UltimateConverter(DefaultConverter):
    def __init__(self, v_to_u=False, neutral_tone_with_five=False,
                 tone_sandhi=False, **kwargs):
        super(UltimateConverter, self).__init__(**kwargs)
        self._v_to_u = v_to_u
        self._neutral_tone_with_five = neutral_tone_with_five
        self._tone_sandhi = tone_sandhi

    def post_convert_style(self, han, orig_pinyin, converted_pinyin,
                           style, strict, **kwargs):
        post_data = super(UltimateConverter, self).post_convert_style(
            han, orig_pinyin, converted_pinyin, style, strict, **kwargs)
        if post_data is not None:
            converted_pinyin = post_data

        if self._v_to_u:
            post_data = _v2UConverter().post_convert_style(
                han, orig_pinyin, converted_pinyin, style, strict, **kwargs)
            if post_data is not None:
                converted_pinyin = post_data

        if self._neutral_tone_with_five:
            post_data = _neutralToneWith5Converter().post_convert_style(
                han, orig_pinyin, converted_pinyin, style, strict, **kwargs)
            if post_data is not None:
                converted_pinyin = post_data

        return converted_pinyin

    def post_pinyin(self, han, heteronym, pinyin, **kwargs):
        post_data = super(UltimateConverter, self).post_pinyin(
            han, heteronym, pinyin, **kwargs)
        if post_data is not None:
            pinyin = post_data

        if self._tone_sandhi:
            post_data = _toneSandhiConverter().post_pinyin(
                han, heteronym, pinyin, **kwargs)
            if post_data is not None:
                pinyin = post_data

        return pinyin


_mixConverter = UltimateConverter
