import fontforge
import shutil
import os
from tomlkit.toml_file import TOMLFile, TOMLDocument
from typing import Iterable, Optional, Union

fontbakery_path = None
fontspector_path = None
fontbakery_config = TOMLDocument()
fontspector_config = TOMLDocument()

plugin_config = TOMLDocument()
_plugin_dir = ''
CONFIGFILE = 'config.toml'
FONTBAKERY_CONFIGFILE = 'fontbakery.toml'
FONTSPECTOR_CONFIGFILE = 'fontspector.toml'

FBFS_CONFIG_HEADER = (
    "# This file is automatically generated with fontforge-fontchecker plugin.\n"
    "# Do not edit.\n\n"
)

profiles = {
    'opentype': 'OpenType (standards compliance)',
    'universal': 'Universal (community best practice)',
    'googlefonts': 'Google Fonts',
    'iso15008': 'ISO 15008 (in-car accessibility)',
    'fontwerk': 'Fontwerk',
    'adobefonts': 'Adobe Fonts',
    'fontbureau': 'Font Bureau',
    'microsoft': 'Microsoft',
    'notofonts': 'Noto fonts',
    'typenetwork': 'Type Network',
}
"""List of known Fontbakery/Fontspector check profile"""


def checkFontTools():
    global fontbakery_path, fontspector_path
    fontbakery_path = shutil.which('fontbakery')
    fontspector_path = shutil.which('fontspector')


def _validateConfItem(key: str, defaultVal, *, choice: Optional[Iterable] = None) -> bool:
    loaded = True
    if (key not in plugin_config) or (not isinstance(plugin_config[key], type(defaultVal))):
        plugin_config[key] = defaultVal  # load default
        loaded = False
    if isinstance(defaultVal, dict) and isinstance(plugin_config[key], dict):
        if missingKeys := defaultVal.keys() - plugin_config[key].keys():
            plugin_config[key] |= dict((k, v) for k, v in defaultVal.items() if k in missingKeys)
    if choice:
        if not any(plugin_config[key] == x for x in choice):
            fontforge.logWarning("Invalid " + key + " '" + str(plugin_config[key]) + "' ignored")
            plugin_config[key] = defaultVal
    return loaded


def _addDoNotEditHeader(config: TOMLDocument) -> TOMLDocument:
    import tomlkit
    myToml = config.as_string()
    if not myToml.startswith(FBFS_CONFIG_HEADER):
        myToml = FBFS_CONFIG_HEADER + myToml
        return tomlkit.parse(myToml)
    return config


def _validateConf():
    global profiles
    _validateConfItem('backend', 'auto', choice=['auto', 'fontbakery', 'fontspector'])
    _validateConfItem('check_as', 'ttf', choice=['ttf', 'ufo'])
    _validateConfItem('glyph_result', {
        'color': False, 'comment': False,
        'FAIL': 0xff0000, 'WARN': 0xffff00,
    })
    if _validateConfItem('profiles', profiles):
        profiles |= plugin_config['profiles']
    if _validateConfItem('profile', 'universal'):
        if plugin_config['profile'] not in profiles:
            profiles[plugin_config['profile']] = plugin_config['profile']
    _validateConfItem('explicit_checks', [])
    _validateConfItem('exclude_checks', [])


def fontBakeryConfigFile() -> str:
    return _plugin_dir + '/' + FONTBAKERY_CONFIGFILE


def fontSpectorConfigFile() -> str:
    return _plugin_dir + '/' + FONTSPECTOR_CONFIGFILE


def loadConf(confdir: str):
    global _plugin_dir, plugin_config, fontbakery_config, fontspector_config
    _plugin_dir = confdir
    try:
        plugin_config = TOMLFile(_plugin_dir + '/' + CONFIGFILE).read()
    except FileNotFoundError:
        pass

    _validateConf()

    try:
        fontbakery_config = TOMLFile(fontBakeryConfigFile()).read()
    except FileNotFoundError:
        pass
    try:
        fontspector_config = TOMLFile(fontSpectorConfigFile()).read()
    except FileNotFoundError:
        pass

    fontbakery_config = _addDoNotEditHeader(fontbakery_config)
    fontspector_config = _addDoNotEditHeader(fontspector_config)


def saveConf():
    os.makedirs(_plugin_dir, exist_ok=True)
    TOMLFile(_plugin_dir + '/' + CONFIGFILE).write(plugin_config)
    TOMLFile(_plugin_dir + '/' + FONTBAKERY_CONFIGFILE).write(fontbakery_config)
    TOMLFile(_plugin_dir + '/' + FONTSPECTOR_CONFIGFILE).write(fontspector_config)


def _writeBackendConf():
    fontspector_config['explicit_checks'] \
        = fontbakery_config['explicit_checks'] \
        = plugin_config['explicit_checks']
    fontspector_config['exclude_checks'] \
        = fontbakery_config['exclude_checks'] \
        = plugin_config['exclude_checks']
    for conf in (fontspector_config, fontbakery_config):
        for i in [x[0] for x in conf.items() if not x[1]]:
            conf.remove(i)


def _colorValToStr(col: Union[str, int]) -> str:
    if isinstance(col, int):
        return format(col, '06x')
    else:
        return str(col).removeprefix('#')


def _colorStrToVal(col: str) -> Union[str, int]:
    try:
        return int(str(col).removeprefix('#'), 16)
    except ValueError:
        return str(col)


def getColorVal(col: Union[str, int], defaultCol: int = -1) -> str:
    if isinstance(col, int):
        return col
    elif isinstance(colval := _colorStrToVal(col), int):
        return colval
    else:
        try:
            from webcolors import name_to_hex
            return int(name_to_hex(str(col)).removeprefix('#'), 16)
        except ModuleNotFoundError as e:
            fontforge.logWarning(str(e))
            return defaultCol  # default color if webcolors unavailable
        except ValueError as e:
            fontforge.logWarning(str(e))
            return defaultCol  # default color if no such color


def configInterface():
    ans = fontforge.askMulti(
        'Configuration',
        [
            {
                'type': 'choice',
                'question': 'Backend',
                'tag': 'backend',
                'checks': True,
                'answers': [
                    {'name': p.capitalize(), 'tag': p, 'default': plugin_config['backend'] == p}
                    for p in ['auto', 'fontbakery', 'fontspector']
                ],
            },
            {
                'type': 'choice',
                'question': 'Check as',
                'tag': 'check_as',
                'checks': True,
                'answers': [
                    {'name': p, 'tag': p.lower(), 'default': plugin_config['check_as'] == p.lower()}
                    for p in ['TTF', 'UFO']
                ],
            },
            {
                'type': 'choice',
                'question': 'Result for glyphs',
                'tag': 'glyph_result',
                'checks': True,
                'multiple': True,
                'answers': [
                    {'name': p, 'tag': p.lower(), 'default': plugin_config['glyph_result'][p.lower()]}
                    for p in ['Color', 'Comment']
                ],
            },
            {
                'type': 'string',
                'question': 'Color for FAIL',
                'tag': 'color_fail',
                'default': _colorValToStr(plugin_config['glyph_result']['FAIL']),
            },
            {
                'type': 'string',
                'question': 'Color for WARN',
                'tag': 'color_warn',
                'default': _colorValToStr(plugin_config['glyph_result']['WARN']),
            },
            {
                'type': 'choice',
                'question': 'Profile',
                'tag': 'profile',
                'answers': [
                    {'name': p[1], 'tag': p[0], 'default': plugin_config['profile'] == p[0]}
                    for p in profiles.items()
                ],
            },
            {
                'type': 'string',
                'question': 'Explicit checks\n(comma-separated)',
                'tag': 'explicit_checks',
                'default': ','.join(plugin_config['explicit_checks']),
            },
            {
                'type': 'string',
                'question': 'Excluded checks\n(comma-separated)',
                'tag': 'exclude_checks',
                'default': ','.join(plugin_config['exclude_checks']),
            },
        ]
    )
    if ans:
        plugin_config['backend'] = ans['backend']
        plugin_config['check_as'] = ans['check_as']
        plugin_config['profile'] = ans['profile']
        plugin_config['glyph_result']['color'] = (ans['glyph_result'] and ('color' in ans['glyph_result']))
        plugin_config['glyph_result']['comment'] = (ans['glyph_result'] and ('comment' in ans['glyph_result']))
        plugin_config['glyph_result']['FAIL'] = _colorStrToVal(ans['color_fail'] or '')
        plugin_config['glyph_result']['WARN'] = _colorStrToVal(ans['color_warn'] or '')
        plugin_config['explicit_checks'] = [a.strip() for a in (ans['explicit_checks'] or '').split(',') if a]
        plugin_config['exclude_checks'] = [a.strip() for a in (ans['exclude_checks'] or '').split(',') if a]
        _writeBackendConf()
        saveConf()
