import re
from typing import Dict, Set, Union

from ...const_singleton import ConstSingleton

from .language import Language
from .note import Note
from .exceptions import NotFoundNoteError, NotFoundNoteLanguageError


class _Data(ConstSingleton):

    LanguageStrDict: Dict[str, Language] = {
        "": Language.MIDI,
        "MIDI": Language.MIDI,
        "MID": Language.MIDI,

        "English": Language.English,
        "eng": Language.English,
        "en": Language.English,
        "us": Language.English,

        "Italian": Language.Italian,
        "Italy": Language.Italian,
        "ita": Language.Italian,
        "it": Language.Italian,
        "Latin": Language.Italian,
        "lat": Language.Italian,
        "la": Language.Italian,
        "Italia": Language.Italian,
        "Italiano": Language.Italian,
        "Romance": Language.Italian,
        "DoReMi": Language.Italian,

        "German": Language.German,
        "Germany": Language.German,
        "ger": Language.German,
        "deu": Language.German,
        "de": Language.German,
        "Deutschland": Language.German,
        "Deutsch": Language.German,

        "Japanese": Language.Japanese,
        "Japan": Language.Japanese,
        "jpn": Language.Japanese,
        "jp": Language.Japanese,
        "ja": Language.Japanese,
        "イロハ": Language.Japanese,

        "Dutch": Language.Dutch,

        "Byzantine": Language.Byzantine,

        "Indian": Language.Indian,

        "日本語": Language.ItalianInKana,
        "日本": Language.ItalianInKana,
        "日": Language.ItalianInKana,

        "ItalianInKana": Language.ItalianInKana,
        "ドレミ": Language.ItalianInKana,
        "カナ": Language.ItalianInKana,

        "JapaneseInRoman": Language.JapaneseInRoman,
        "Iroha": Language.JapaneseInRoman,

        "Chromatic": Language.Chromatic,
        "Siler": Language.Chromatic,

        "FixedDoInRoman": Language.FixedDoInRoman,
        "FixedDo": Language.FixedDoInRoman,
        "Fixed-do": Language.FixedDoInRoman,

        "FixedDoInKana": Language.FixedDoInKana,
        "固定ド": Language.FixedDoInKana,
    }

    NoteNameDict: Dict[Language, Dict[str, Note]] = {
        Language.MIDI: {
            "Cb": Note.B,
            "C": Note.C,
            "C#": Note.C_SHARP,
            "Db": Note.C_SHARP,
            "D": Note.D,
            "D#": Note.D_SHARP,
            "Eb": Note.D_SHARP,
            "E": Note.E,
            "E#": Note.F,
            "Fb": Note.E,
            "F": Note.F,
            "F#": Note.F_SHARP,
            "Gb": Note.F_SHARP,
            "G": Note.G,
            "G#": Note.G_SHARP,
            "Ab": Note.G_SHARP,
            "A": Note.A,
            "A#": Note.A_SHARP,
            "Bb": Note.A_SHARP,
            "B": Note.B,
            "B#": Note.C,
        },
        Language.English: {
            "Dobb": Note.A_SHARP,
            "Dob": Note.B,
            "Do": Note.C,
            "Do#": Note.C_SHARP,
            "Do##": Note.D,
            "Rebb": Note.C,
            "Reb": Note.C_SHARP,
            "Re": Note.D,
            "Re#": Note.D_SHARP,
            "Re##": Note.E,
            "Mibb": Note.D,
            "Mib": Note.D_SHARP,
            "Mi": Note.E,
            "Mi#": Note.F,
            "Mi##": Note.F_SHARP,
            "Fabb": Note.D_SHARP,
            "Fab": Note.E,
            "Fa": Note.F,
            "Fa#": Note.F_SHARP,
            "Fa##": Note.G,
            "Solbb": Note.F,
            "Solb": Note.F_SHARP,
            "Sol": Note.G,
            "Sol#": Note.G_SHARP,
            "Sol##": Note.A,
            "Labb": Note.G,
            "Lab": Note.G_SHARP,
            "La": Note.A,
            "La#": Note.A_SHARP,
            "La##": Note.B,
            "Tibb": Note.A,
            "Tib": Note.A_SHARP,
            "Ti": Note.B,
            "Ti#": Note.C,
            "Ti##": Note.C_SHARP,
        },
        Language.Italian: {
            "Dobb": Note.A_SHARP,
            "Dob": Note.B,
            "Do": Note.C,
            "Do#": Note.C_SHARP,
            "Do##": Note.D,
            "Rebb": Note.C,
            "Reb": Note.C_SHARP,
            "Re": Note.D,
            "Re#": Note.D_SHARP,
            "Re##": Note.E,
            "Mibb": Note.D,
            "Mib": Note.D_SHARP,
            "Mi": Note.E,
            "Mi#": Note.F,
            "Mi##": Note.F_SHARP,
            "Fabb": Note.D_SHARP,
            "Fab": Note.E,
            "Fa": Note.F,
            "Fa#": Note.F_SHARP,
            "Fa##": Note.G,
            "Solbb": Note.F,
            "Solb": Note.F_SHARP,
            "Sol": Note.G,
            "Sol#": Note.G_SHARP,
            "Sol##": Note.A,
            "Labb": Note.G,
            "Lab": Note.G_SHARP,
            "La": Note.A,
            "La#": Note.A_SHARP,
            "La##": Note.B,
            "Sibb": Note.A,
            "Sib": Note.A_SHARP,
            "Si": Note.B,
            "Si#": Note.C,
            "Si##": Note.C_SHARP,
        },
        Language.German: {
            "Ceses": Note.A_SHARP,
            "Ces": Note.B,
            "C": Note.C,
            "Cis": Note.C_SHARP,
            "Cisis": Note.D,
            "Deses": Note.C,
            "Des": Note.C_SHARP,
            "D": Note.D,
            "Dis": Note.D_SHARP,
            "Disis": Note.E,
            "Eses": Note.D,
            "Es": Note.D_SHARP,
            "E": Note.E,
            "Eis": Note.F,
            "Eisis": Note.F_SHARP,
            "Feses": Note.D_SHARP,
            "Fes": Note.E,
            "F": Note.F,
            "Fis": Note.F_SHARP,
            "Fisis": Note.G,
            "Geses": Note.F,
            "Ges": Note.F_SHARP,
            "G": Note.G,
            "Gis": Note.G_SHARP,
            "Gisis": Note.A,
            "Asas": Note.G,
            "Ases": Note.G,
            "As": Note.G_SHARP,
            "A": Note.A,
            "Ais": Note.A_SHARP,
            "Aisis": Note.B,
            "Heses": Note.A,
            "Bes": Note.A,
            "BB": Note.A,
            "B": Note.A_SHARP,
            "Hes": Note.A_SHARP,
            "H": Note.B,
            "His": Note.C,
            "Hisis": Note.C_SHARP,
        },
        Language.Japanese: {
            "重変ハ": Note.A_SHARP,
            "変ハ": Note.B,
            "ハ": Note.C,
            "嬰ハ": Note.C_SHARP,
            "重嬰ハ": Note.D,
            "重変ニ": Note.C,
            "変ニ": Note.C_SHARP,
            "ニ": Note.D,
            "嬰ニ": Note.D_SHARP,
            "重嬰ニ": Note.E,
            "重変ホ": Note.D,
            "変ホ": Note.D_SHARP,
            "ホ": Note.E,
            "嬰ホ": Note.F,
            "重嬰ホ": Note.F_SHARP,
            "重変ヘ": Note.D_SHARP,
            "変ヘ": Note.E,
            "ヘ": Note.F,
            "嬰ヘ": Note.F_SHARP,
            "重嬰ヘ": Note.G,
            "重変ト": Note.F,
            "変ト": Note.F_SHARP,
            "ト": Note.G,
            "嬰ト": Note.G_SHARP,
            "重嬰ト": Note.A,
            "重変イ": Note.G,
            "変イ": Note.G_SHARP,
            "イ": Note.A,
            "嬰イ": Note.A_SHARP,
            "重嬰イ": Note.B,
            "重変ロ": Note.A,
            "変ロ": Note.A_SHARP,
            "ロ": Note.B,
            "嬰ロ": Note.C,
            "重嬰ロ": Note.C_SHARP,
        },

        Language.Dutch: {
            "Ceses": Note.A_SHARP,
            "Ces": Note.B,
            "C": Note.C,
            "Cis": Note.C_SHARP,
            "Cisis": Note.D,
            "Deses": Note.C,
            "Des": Note.C_SHARP,
            "D": Note.D,
            "Dis": Note.D_SHARP,
            "Disis": Note.E,
            "Eses": Note.D,
            "Es": Note.D_SHARP,
            "E": Note.E,
            "Eis": Note.F,
            "Eisis": Note.F_SHARP,
            "Feses": Note.D_SHARP,
            "Fes": Note.E,
            "F": Note.F,
            "Fis": Note.F_SHARP,
            "Fisis": Note.G,
            "Geses": Note.F,
            "Ges": Note.F_SHARP,
            "G": Note.G,
            "Gis": Note.G_SHARP,
            "Gisis": Note.A,
            "Asas": Note.G,
            "Ases": Note.G,
            "As": Note.G_SHARP,
            "A": Note.A,
            "Ais": Note.A_SHARP,
            "Aisis": Note.B,
            "Beses": Note.A,
            "Bes": Note.A_SHARP,
            "B": Note.B,
            "Bis": Note.C,
            "Bisis": Note.C_SHARP,
        },
        Language.Byzantine: {
            "Nibb": Note.A_SHARP,
            "Nib": Note.B,
            "Ni": Note.C,
            "Ni#": Note.C_SHARP,
            "Ni##": Note.D,
            "Pabb": Note.C,
            "Pab": Note.C_SHARP,
            "Pa": Note.D,
            "Pa#": Note.D_SHARP,
            "Pa##": Note.E,
            "Vubb": Note.D,
            "Vub": Note.D_SHARP,
            "Vu": Note.E,
            "Vu#": Note.F,
            "Vu##": Note.F_SHARP,
            "Gabb": Note.D_SHARP,
            "Gab": Note.E,
            "Ga": Note.F,
            "Ga#": Note.F_SHARP,
            "Ga##": Note.G,
            "Dibb": Note.F,
            "Dib": Note.F_SHARP,
            "Di": Note.G,
            "Di#": Note.G_SHARP,
            "Di##": Note.A,
            "Kebb": Note.G,
            "Keb": Note.G_SHARP,
            "Ke": Note.A,
            "Ke#": Note.A_SHARP,
            "Ke##": Note.B,
            "Zobb": Note.A,
            "Zob": Note.A_SHARP,
            "Zo": Note.B,
            "Zo#": Note.C,
            "Zo##": Note.C_SHARP,
        },
        Language.Indian: {
            "Sabb": Note.A_SHARP,
            "Sab": Note.B,
            "Sa": Note.C,
            "Sa#": Note.C_SHARP,
            "Sa##": Note.D,
            "Rebb": Note.C,
            "Reb": Note.C_SHARP,
            "Re": Note.D,
            "Re#": Note.D_SHARP,
            "Re##": Note.E,
            "Gabb": Note.D,
            "Gab": Note.D_SHARP,
            "Ga": Note.E,
            "Ga#": Note.F,
            "Ga##": Note.F_SHARP,
            "Mabb": Note.D_SHARP,
            "Mab": Note.E,
            "Ma": Note.F,
            "Ma#": Note.F_SHARP,
            "Ma##": Note.G,
            "Pabb": Note.F,
            "Pab": Note.F_SHARP,
            "Pa": Note.G,
            "Pa#": Note.G_SHARP,
            "Pa##": Note.A,
            "Dhabb": Note.G,
            "Dhab": Note.G_SHARP,
            "Dha": Note.A,
            "Dha#": Note.A_SHARP,
            "Dha##": Note.B,
            "Nibb": Note.A,
            "Nib": Note.A_SHARP,
            "Ni": Note.B,
            "Ni#": Note.C,
            "Ni##": Note.C_SHARP,
        },

        Language.ItalianInKana: {
            "ドbb": Note.A_SHARP,
            "ドb": Note.B,
            "ド": Note.C,
            "ド#": Note.C_SHARP,
            "ド##": Note.D,
            "レbb": Note.C,
            "レb": Note.C_SHARP,
            "レ": Note.D,
            "レ#": Note.D_SHARP,
            "レ##": Note.E,
            "ミbb": Note.D,
            "ミb": Note.D_SHARP,
            "ミ": Note.E,
            "ミ#": Note.F,
            "ミ##": Note.F_SHARP,
            "ファbb": Note.D_SHARP,
            "ファb": Note.E,
            "ファ": Note.F,
            "ファ#": Note.F_SHARP,
            "ファ##": Note.G,
            "ソbb": Note.F,
            "ソb": Note.F_SHARP,
            "ソ": Note.G,
            "ソ#": Note.G_SHARP,
            "ソ##": Note.A,
            "ラbb": Note.G,
            "ラb": Note.G_SHARP,
            "ラ": Note.A,
            "ラ#": Note.A_SHARP,
            "ラ##": Note.B,
            "シbb": Note.A,
            "シb": Note.A_SHARP,
            "シ": Note.B,
            "シ#": Note.C,
            "シ##": Note.C_SHARP,
        },
        Language.JapaneseInRoman: {
            "JuuhenHa": Note.A_SHARP,
            "HenHa": Note.B,
            "Ha": Note.C,
            "EiHa": Note.C_SHARP,
            "JuueiHa": Note.D,
            "JuuhenNi": Note.C,
            "HenNi": Note.C_SHARP,
            "Ni": Note.D,
            "EiNi": Note.D_SHARP,
            "JuueiNi": Note.E,
            "JuuhenHo": Note.D,
            "HenHo": Note.D_SHARP,
            "Ho": Note.E,
            "EiHo": Note.F,
            "JuueiHo": Note.F_SHARP,
            "JuuhenHe": Note.D_SHARP,
            "HenHe": Note.E,
            "He": Note.F,
            "EiHe": Note.F_SHARP,
            "JuueiHe": Note.G,
            "JuuhenTo": Note.F,
            "HenTo": Note.F_SHARP,
            "To": Note.G,
            "EiTo": Note.G_SHARP,
            "JuueiTo": Note.A,
            "JuuhenI": Note.G,
            "HenI": Note.G_SHARP,
            "I": Note.A,
            "EiI": Note.A_SHARP,
            "JuueiI": Note.B,
            "JuuhenRo": Note.A,
            "HenRo": Note.A_SHARP,
            "Ro": Note.B,
            "EiRo": Note.C,
            "JuueiRo": Note.C_SHARP,
        },

        Language.Chromatic: {
            "Du": Note.A_SHARP,
            "Do": Note.B,
            "Da": Note.C,
            "De": Note.C_SHARP,
            "Di": Note.D,
            "Ru": Note.C,
            "Ro": Note.C_SHARP,
            "Ra": Note.D,
            "Re": Note.D_SHARP,
            "Ri": Note.E,
            "Mu": Note.D,
            "Mo": Note.D_SHARP,
            "Ma": Note.E,
            "Me": Note.F,
            "Mi": Note.F_SHARP,
            "Fu": Note.D_SHARP,
            "Fo": Note.E,
            "Fa": Note.F,
            "Fe": Note.F_SHARP,
            "Fi": Note.G,
            "Su": Note.F,
            "So": Note.F_SHARP,
            "Sa": Note.G,
            "Se": Note.G_SHARP,
            "Si": Note.A,
            "Lu": Note.G,
            "Lo": Note.G_SHARP,
            "La": Note.A,
            "Le": Note.A_SHARP,
            "Li": Note.B,
            "Tu": Note.A,
            "To": Note.A_SHARP,
            "Ta": Note.B,
            "Te": Note.C,
            "Ti": Note.C_SHARP,
        },
        Language.FixedDoInRoman: {
            "Do": Note.C,
            "Di": Note.C_SHARP,
            "Ra": Note.C_SHARP,
            "Re": Note.D,
            "Ri": Note.D_SHARP,
            "Me": Note.D_SHARP,
            "Ma": Note.D_SHARP,
            "Mi": Note.E,
            "Fa": Note.F,
            "Fi": Note.F_SHARP,
            "Se": Note.F_SHARP,
            "Sol": Note.G,
            "Si": Note.G_SHARP,
            "Le": Note.G_SHARP,
            "Lo": Note.G_SHARP,
            "La": Note.A,
            "Li": Note.A_SHARP,
            "Te": Note.A_SHARP,
            "Ta": Note.A_SHARP,
            "Ti": Note.B,
        },
        Language.FixedDoInKana: {
            "ド": Note.C,
            "デ": Note.C_SHARP,
            "レ": Note.D,
            "リ": Note.D_SHARP,
            "ミ": Note.E,
            "ファ": Note.F,
            "フィ": Note.F_SHARP,
            "ソ": Note.G,
            "サ": Note.G_SHARP,
            "ラ": Note.A,
            "チ": Note.A_SHARP,
            "シ": Note.B,
        },
    }

    AccidentalDict: Dict[Language, Dict[str, Set[str]]] = {
        Language.Unknown: {
            "": {"-", "_", ":", ",", ".", "@", "♮", },
            "#": {"♯", "＃", "sharp", },
            "b": {"♭", "flat", },
            "##": {chr(0x1d12a), },
            "bb": {chr(0x1d12b), },
            # "##": {"𝄪", },
            # "bb": {"𝄫", },
        },

        Language.English: {
            "": {"natural", },
            "##": {"doublesharp", },
            "bb": {"doubleflat", },
        },
        Language.Italian: {
            "": {"bequadro", "biquadro", "biqquadro", "beqquadro", },
            "#": {"diesis", },
            "b": {"bemolle", },
            "##": {"doppiodiesis", },
            "bb": {"doppiobemolle", },
        },
        Language.German: {
            "": {},
            "#": {},
            "b": {},
            "##": {},
            "bb": {},
        },
        Language.Japanese: {
            "": {"本位", "ほんい", },
            "嬰": {"えい", },
            "変": {"へん", },
            "重": {"じゅう", },
        },

        Language.Dutch: {
            "": {"auflösungszeichen", "auflosungszeichen", },
            "#": {"kreuz", },
            "b": {},
            "##": {"doppelkreuz", },
            "bb": {"doppelb", },
        },
        Language.Byzantine: {
            "": {},
            "#": {"diesis", },
            "b": {"hyphesis", },
            "##": {},
            "bb": {},
        },
        Language.Indian: {
            "": {},
            "#": {"tivra", },
            "b": {"komal", },
            "##": {},
            "bb": {},
        },

        Language.ItalianInKana: {
            "": {"ナチュラル", },
            "#": {"シャープ", },
            "b": {"フラット", },
            "##": {"ダブルシャープ", },
            "bb": {"ダブルフラット", },
        },
        Language.JapaneseInRoman: {
            "": {"honi", "本位", "ほんい", },
            "ei": {"嬰", "えい", },
            "hen": {"変", "へん", },
            "juu": {"重", "じゅう", },
        },

        Language.Chromatic: {},
        Language.FixedDoInRoman: {},
        Language.FixedDoInKana: {},
    }


DATA: _Data = _Data.get_instance()


class Interpreter:

    # region Class Method

    @classmethod
    def get_language(cls, language: Union[str, Language]) -> Language:
        if type(language) == Language:
            return language
        return Interpreter.get_language_from_str(language)

    @classmethod
    def get_language_from_str(cls, language: str) -> Language:
        for k, l in DATA.LanguageStrDict.items():
            if k.lower() == language.lower():
                return l
        raise NotFoundNoteLanguageError

    @classmethod
    def get_dict(cls, language: Union[str, Language]) -> Dict[str, Note]:
        return DATA.NoteNameDict[Interpreter.get_language(language)]

    @classmethod
    def to_note(cls, value: str, language: Union[str, Language]) -> Note:
        value = value.lower()
        value = re.sub(r"[\s]", "", value)
        value = Interpreter._replace_accidental_strs(value, language)
        for k, n in Interpreter.get_dict(language).items():
            if k.lower() == value:
                return n
        raise NotFoundNoteError

    # endregion

    # region Private Class Method

    @classmethod
    def _replace_accidental_strs(cls, value: str, language: Union[str, Language]) -> str:
        language: Language = Interpreter.get_language(language)
        value = Interpreter._replace_accidental_strs_from_dict(value, DATA.AccidentalDict[Language.Unknown])
        if language in DATA.AccidentalDict:
            value = Interpreter._replace_accidental_strs_from_dict(value, DATA.AccidentalDict[language])
        return value

    @classmethod
    def _replace_accidental_strs_from_dict(cls, value: str, replaced_dict: Dict[str, Set[str]]) -> str:
        for (new, old_list) in replaced_dict.items():
            for old in old_list:
                value = value.replace(old, new)
        return value

    # endregion
