# 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 imp
import os
import sys
import uuid

from .types import supported_types
from .errors import LinterNotFound, LinterParseError


class Parser(object):
    """Reads and validates `.lint` files."""
    required_attributes = (
        'name',
        'description',
        'type',
        'payload',
    )

    def __call__(self, path):
        return self.parse(path)

    def _load_linter(self, path):
        # Ensure parent module is present otherwise we'll (likely) get
        # an error due to unknown parent.
        parent_module = 'mozlint.linters'
        if parent_module not in sys.modules:
            mod = imp.new_module(parent_module)
            sys.modules[parent_module] = mod

        write_bytecode = sys.dont_write_bytecode
        sys.dont_write_bytecode = True

        module_name = '{}.{}'.format(parent_module, uuid.uuid1().get_hex())
        imp.load_source(module_name, path)

        sys.dont_write_bytecode = write_bytecode

        mod = sys.modules[module_name]

        if not hasattr(mod, 'LINTER'):
            raise LinterParseError(path, "No LINTER definition found!")

        definition = mod.LINTER
        definition['path'] = path
        return definition

    def _validate(self, linter):
        missing_attrs = []
        for attr in self.required_attributes:
            if attr not in linter:
                missing_attrs.append(attr)

        if missing_attrs:
            raise LinterParseError(linter['path'], "Missing required attribute(s): "
                                                   "{}".format(','.join(missing_attrs)))

        if linter['type'] not in supported_types:
            raise LinterParseError(linter['path'], "Invalid type '{}'".format(linter['type']))

        for attr in ('include', 'exclude'):
            if attr in linter and (not isinstance(linter[attr], list) or
                                   not all(isinstance(a, basestring) for a in linter[attr])):
                raise LinterParseError(linter['path'], "The {} directive must be a "
                                                       "list of strings!".format(attr))

    def parse(self, path):
        """Read a linter and return its LINTER definition.

        :param path: Path to the linter.
        :returns: Linter definition (dict)
        :raises: LinterNotFound, LinterParseError
        """
        if not os.path.isfile(path):
            raise LinterNotFound(path)

        if not path.endswith('.lint'):
            raise LinterParseError(path, "Invalid filename, linters must end with '.lint'!")

        linter = self._load_linter(path)
        self._validate(linter)
        return linter
