# -*- Mode: python; tab-width: 8; indent-tabs-mode: nil -*-
# vim: set ts=8 sts=4 et sw=4 tw=80:
# 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 json
import os
import re

KEY_XRE = "{xre}"
DEFAULT_DURATION = 100.0


class Allowlist:
    # we need to find the root dir of the profile at runtime
    PRE_PROFILE = ""

    def __init__(
        self,
        test_name,
        paths,
        path_substitutions,
        name_substitutions,
        event_sources=None,
        init_with=None,
    ):
        self.test_name = test_name
        self.listmap = init_with if init_with else {}
        self.dependent_libs = (
            self.load_dependent_libs() if init_with and KEY_XRE in paths else {}
        )
        self.paths = paths
        self.path_substitutions = path_substitutions
        self.name_substitutions = name_substitutions
        self.expected_event_sources = event_sources or []

    def load(self, filename):
        if not self.load_dependent_libs():
            return False

        try:
            with open(filename, "r") as fHandle:
                temp = json.load(fHandle)

            for allowlist_name in temp:
                self.listmap[allowlist_name.lower()] = temp[allowlist_name]

        except IOError as e:
            print("%s: %s" % (e.filename, e.strerror))
            return False
        return True

    def sanitize_filename(self, filename):
        filename = filename.lower()
        filename.replace(" (x86)", "")

        for path, subst in self.path_substitutions.items():
            parts = filename.split(path)
            if len(parts) >= 2:
                if self.PRE_PROFILE == "" and subst == "{profile}":
                    fname = self.sanitize_filename(parts[0])
                    self.listmap[fname] = {}
                    # Windows can have {appdata}\local\temp\longnamedfolder
                    # or {appdata}\local\temp\longna~1
                    self.listmap[fname] = {}
                    if not fname.endswith("~1"):
                        # parse the longname into longna~1
                        dirs = fname.split("\\")
                        dirs[-1] = "%s~1" % (dirs[-1][:6])
                        # now we want to ensure that every parent dir is
                        # added since we seem to be accessing them sometimes
                        diter = 2
                        while diter < len(dirs):
                            self.listmap["\\".join(dirs[:diter])] = {}
                            diter = diter + 1
                        self.PRE_PROFILE = fname

                filename = "%s%s" % (subst, path.join(parts[1:]))

        for old_name, new_name in self.name_substitutions.items():
            if isinstance(old_name, re.Pattern):
                filename = re.sub(old_name, new_name, filename)
            else:
                parts = filename.split(old_name)
                if len(parts) >= 2:
                    filename = "%s%s" % (parts[0], new_name)

        return filename.strip("/\\\ \t")

    def check(self, test, file_name_index, event_source_index=None):
        errors = {}
        for row_key in test.keys():
            filename = self.sanitize_filename(row_key[file_name_index])

            if filename in self.listmap:
                if (
                    "ignore" in self.listmap[filename]
                    and self.listmap[filename]["ignore"]
                ):
                    continue
            elif filename in self.dependent_libs:
                continue
            elif (
                event_source_index is not None
                and row_key[event_source_index] in self.expected_event_sources
            ):
                continue
            else:
                if filename not in errors:
                    errors[filename] = []
                errors[filename].append(test[row_key])
        return errors

    def checkDuration(self, test, file_name_index, file_duration_index):
        errors = {}
        for idx, (row_key, row_value) in enumerate(test.items()):
            if row_value[file_duration_index] > DEFAULT_DURATION:
                filename = self.sanitize_filename(row_key[file_name_index])
                if (
                    filename in self.listmap
                    and "ignoreduration" in self.listmap[filename]
                ):
                    # we have defined in the json manifest max values
                    # (max found value * 2) and will ignore it
                    if (
                        row_value[file_duration_index]
                        <= self.listmap[filename]["ignoreduration"]
                    ):
                        continue

                if filename not in errors:
                    errors[filename] = []
                errors[filename].append(
                    "Duration %s > %s" % (row_value[file_duration_index]),
                    DEFAULT_DURATION,
                )
        return errors

    def filter(self, test, file_name_index):
        for row_key in test.keys():
            filename = self.sanitize_filename(row_key[file_name_index])
            if filename in self.listmap:
                if (
                    "ignore" in self.listmap[filename]
                    and self.listmap[filename]["ignore"]
                ):
                    del test[row_key]
                    continue
            elif filename in self.dependent_libs:
                del test[row_key]
                continue

    @staticmethod
    def get_error_strings(errors):
        error_strs = []
        for filename, data in errors.items():
            for datum in data:
                error_strs.append(
                    "File '%s' was accessed and we were not"
                    " expecting it: %r" % (filename, datum)
                )
        return error_strs

    def print_errors(self, error_strs):
        for error_msg in error_strs:
            print("TEST-UNEXPECTED-FAIL | %s | %s" % (self.test_name, error_msg))

    # Note that we don't store dependent libs in listmap. This makes
    # save_baseline cleaner. Since a baseline allowlist should not include
    # the dependent_libs, we would need to filter them out if everything was
    # stored in the same dict.
    def load_dependent_libs(self):
        filename = "%s%sdependentlibs.list" % (self.paths[KEY_XRE], os.path.sep)
        try:
            with open(filename, "r") as f:
                libs = f.readlines()
            self.dependent_libs = {
                "%s%s%s" % (KEY_XRE, os.path.sep, lib.strip()): {"ignore": True}
                for lib in libs
            }
            return True
        except IOError as e:
            print("%s: %s" % (e.filename, e.strerror))
            return False
