# 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 os
import re
import subprocess

# py2-compat
try:
    from json.decoder import JSONDecodeError
except ImportError:
    JSONDecodeError = ValueError

from mozfile import which
from mozlint import result
from mozlint.util.implementation import LintProcess

here = os.path.abspath(os.path.dirname(__file__))
CODESPELL_REQUIREMENTS_PATH = os.path.join(here, "codespell_requirements.txt")

CODESPELL_NOT_FOUND = """
Could not find codespell! Install codespell and try again.

    $ pip install -U --require-hashes -r {}
""".strip().format(
    CODESPELL_REQUIREMENTS_PATH
)


CODESPELL_INSTALL_ERROR = """
Unable to install correct version of codespell
Try to install it manually with:
    $ pip install -U --require-hashes -r {}
""".strip().format(
    CODESPELL_REQUIREMENTS_PATH
)

results = []

CODESPELL_FORMAT_REGEX = re.compile(r"(.*):(.*): (.*) ==> (.*)$")


class CodespellProcess(LintProcess):
    fixed = 0
    _fix = None

    def process_line(self, line):
        try:
            match = CODESPELL_FORMAT_REGEX.match(line)
            abspath, line, typo, correct = match.groups()
        except AttributeError:
            if "FIXED: " not in line:
                print("Unable to match regex against output: {}".format(line))
            return

        if CodespellProcess._fix:
            CodespellProcess.fixed += 1

        # Ignore false positive like aParent (which would be fixed to apparent)
        # See https://github.com/lucasdemarchi/codespell/issues/314
        m = re.match(r"^[a-z][A-Z][a-z]*", typo)
        if m:
            return
        res = {
            "path": abspath,
            "message": typo.strip() + " ==> " + correct,
            "level": "error",
            "lineno": line,
        }
        results.append(result.from_config(self.config, **res))


def run_process(config, cmd):
    proc = CodespellProcess(config, cmd)
    proc.run()
    try:
        proc.wait()
    except KeyboardInterrupt:
        proc.kill()


def get_codespell_binary():
    """
    Returns the path of the first codespell binary available
    if not found returns None
    """
    binary = os.environ.get("CODESPELL")
    if binary:
        return binary

    return which("codespell")


def setup(root, **lintargs):
    virtualenv_manager = lintargs["virtualenv_manager"]
    try:
        virtualenv_manager.install_pip_requirements(
            CODESPELL_REQUIREMENTS_PATH, quiet=True
        )
    except subprocess.CalledProcessError:
        print(CODESPELL_INSTALL_ERROR)
        return 1


def get_codespell_version(binary):
    return subprocess.check_output(
        [which("python"), binary, "--version"],
        universal_newlines=True,
        stderr=subprocess.STDOUT,
    )


def get_ignored_words_file(config):
    config_root = os.path.dirname(config["path"])
    return os.path.join(config_root, "spell", "exclude-list.txt")


def lint(paths, config, fix=None, **lintargs):
    log = lintargs["log"]
    binary = get_codespell_binary()
    if not binary:
        print(CODESPELL_NOT_FOUND)
        if "MOZ_AUTOMATION" in os.environ:
            return 1
        return []

    config["root"] = lintargs["root"]

    exclude_list = get_ignored_words_file(config)
    cmd_args = [
        which("python"),
        binary,
        "--disable-colors",
        # Silence some warnings:
        # 1: disable warnings about wrong encoding
        # 2: disable warnings about binary file
        # 4: shut down warnings about automatic fixes
        #    that were disabled in dictionary.
        "--quiet-level=7",
        "--ignore-words=" + exclude_list,
    ]

    if "exclude" in config:
        cmd_args.append("--skip=*.dic,{}".format(",".join(config["exclude"])))

    log.debug("Command: {}".format(" ".join(cmd_args)))
    log.debug("Version: {}".format(get_codespell_version(binary)))

    if fix:
        CodespellProcess._fix = True

    base_command = cmd_args + paths
    run_process(config, base_command)

    if fix:
        global results
        results = []
        cmd_args.append("--write-changes")
        log.debug("Command: {}".format(" ".join(cmd_args)))
        log.debug("Version: {}".format(get_codespell_version(binary)))
        base_command = cmd_args + paths
        run_process(config, base_command)
        CodespellProcess.fixed = CodespellProcess.fixed - len(results)
    else:
        CodespellProcess.fixed = 0

    return {"results": results, "fixed": CodespellProcess.fixed}
