# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import errno
import logging
from compare_locales import mozpath
from .project import ProjectConfig
from .matcher import expand
import toml


class ConfigNotFound(EnvironmentError):
    def __init__(self, path):
        super().__init__(
            errno.ENOENT,
            'Configuration file not found',
            path)


class ParseContext:
    def __init__(self, path, env, ignore_missing_includes):
        self.path = path
        self.env = env
        self.ignore_missing_includes = ignore_missing_includes
        self.data = None
        self.pc = ProjectConfig(path)


class TOMLParser:
    def parse(self, path, env=None, ignore_missing_includes=False):
        ctx = self.context(
            path, env=env, ignore_missing_includes=ignore_missing_includes
        )
        self.load(ctx)
        self.processBasePath(ctx)
        self.processEnv(ctx)
        self.processPaths(ctx)
        self.processFilters(ctx)
        self.processIncludes(ctx)
        self.processExcludes(ctx)
        self.processLocales(ctx)
        return self.asConfig(ctx)

    def context(self, path, env=None, ignore_missing_includes=False):
        return ParseContext(
            path,
            env if env is not None else {},
            ignore_missing_includes,
        )

    def load(self, ctx):
        try:
            with open(ctx.path, 'rt') as fin:
                ctx.data = toml.load(fin)
        except (toml.TomlDecodeError, OSError):
            raise ConfigNotFound(ctx.path)

    def processBasePath(self, ctx):
        assert ctx.data is not None
        ctx.pc.set_root(ctx.data.get('basepath', '.'))

    def processEnv(self, ctx):
        assert ctx.data is not None
        ctx.pc.add_environment(**ctx.data.get('env', {}))
        # add parser environment, possibly overwriting file variables
        ctx.pc.add_environment(**ctx.env)

    def processLocales(self, ctx):
        assert ctx.data is not None
        if 'locales' in ctx.data:
            ctx.pc.set_locales(ctx.data['locales'])

    def processPaths(self, ctx):
        assert ctx.data is not None
        for data in ctx.data.get('paths', []):
            paths = {
                "l10n": data['l10n']
            }
            if 'locales' in data:
                paths['locales'] = data['locales']
            if 'reference' in data:
                paths['reference'] = data['reference']
            if 'test' in data:
                paths['test'] = data['test']
            ctx.pc.add_paths(paths)

    def processFilters(self, ctx):
        assert ctx.data is not None
        for data in ctx.data.get('filters', []):
            paths = data['path']
            if isinstance(paths, str):
                paths = [paths]
            rule = {
                "path": paths,
                "action": data['action']
            }
            if 'key' in data:
                rule['key'] = data['key']
            ctx.pc.add_rules(rule)

    def processIncludes(self, ctx):
        for child in self._processChild(ctx, 'includes'):
            ctx.pc.add_child(child)

    def processExcludes(self, ctx):
        for child in self._processChild(ctx, 'excludes'):
            ctx.pc.exclude(child)

    def _processChild(self, ctx, field):
        assert ctx.data is not None
        if field not in ctx.data:
            return
        for child_config in ctx.data[field]:
            # resolve child_config['path'] against our root and env
            p = mozpath.normpath(
                expand(
                    ctx.pc.root,
                    child_config['path'],
                    ctx.pc.environ
                )
            )
            try:
                child = self.parse(
                    p, env=ctx.env,
                    ignore_missing_includes=ctx.ignore_missing_includes
                )
            except ConfigNotFound as e:
                if not ctx.ignore_missing_includes:
                    raise
                (logging
                    .getLogger('compare-locales.io')
                    .error('%s: %s', e.strerror, e.filename))
                continue
            yield child

    def asConfig(self, ctx):
        return ctx.pc
