# @file CharEncodingCheck.py
#
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: BSD-2-Clause-Patent
##


import os
import logging
from edk2toolext.environment.plugintypes.ci_build_plugin import ICiBuildPlugin
from edk2toolext.environment.var_dict import VarDict

##
# map
##
EcodingMap = {
    ".md": 'utf-8',
    ".dsc": 'utf-8',
    ".dec": 'utf-8',
    ".c": 'utf-8',
    ".h": 'utf-8',
    ".asm": 'utf-8',
    ".masm": 'utf-8',
    ".nasm": 'utf-8',
    ".s": 'utf-8',
    ".inf": 'utf-8',
    ".asl": 'utf-8',
    ".uni": 'utf-8',
    ".py": 'utf-8'
}


class CharEncodingCheck(ICiBuildPlugin):
    """
    A CiBuildPlugin that scans each file in the code tree and confirms the encoding is correct.

    Configuration options:
    "CharEncodingCheck": {
        "IgnoreFiles": []
    }
    """

    def GetTestName(self, packagename: str, environment: VarDict) -> tuple:
        """ Provide the testcase name and classname for use in reporting
            testclassname: a descriptive string for the testcase can include whitespace
            classname: should be patterned <packagename>.<plugin>.<optionally any unique condition>

            Args:
              packagename: string containing name of package to build
              environment: The VarDict for the test to run in
            Returns:
                a tuple containing the testcase name and the classname
                (testcasename, classname)
        """
        return ("Check for valid file encoding for " + packagename, packagename + ".CharEncodingCheck")

    ##
    # External function of plugin.  This function is used to perform the task of the ci_build_plugin Plugin
    #
    #   - package is the edk2 path to package.  This means workspace/packagepath relative.
    #   - edk2path object configured with workspace and packages path
    #   - PkgConfig Object (dict) for the pkg
    #   - EnvConfig Object
    #   - Plugin Manager Instance
    #   - Plugin Helper Obj Instance
    #   - Junit Logger
    #   - output_stream the StringIO output stream from this plugin via logging
    def RunBuildPlugin(self, packagename, Edk2pathObj, pkgconfig, environment, PLM, PLMHelper, tc, output_stream=None):
        overall_status = 0
        files_tested = 0

        abs_pkg_path = Edk2pathObj.GetAbsolutePathOnThisSystemFromEdk2RelativePath(packagename)

        if abs_pkg_path is None:
            tc.SetSkipped()
            tc.LogStdError("No Package folder {0}".format(abs_pkg_path))
            return 0

        for (ext, enc) in EcodingMap.items():
            files = self.WalkDirectoryForExtension([ext], abs_pkg_path)
            files = [Edk2pathObj.GetEdk2RelativePathFromAbsolutePath(x) for x in files]  # make edk2relative path so can process ignores

            if "IgnoreFiles" in pkgconfig:
                for a in pkgconfig["IgnoreFiles"]:
                    a = a.replace(os.sep, "/")
                    try:
                        tc.LogStdOut("Ignoring File {0}".format(a))
                        files.remove(a)
                    except:
                        tc.LogStdError("CharEncodingCheck.IgnoreInf -> {0} not found in filesystem.  Invalid ignore file".format(a))
                        logging.info("CharEncodingCheck.IgnoreInf -> {0} not found in filesystem.  Invalid ignore file".format(a))

            files = [Edk2pathObj.GetAbsolutePathOnThisSystemFromEdk2RelativePath(x) for x in files]
            for a in files:
                files_tested += 1
                if not self.TestEncodingOk(a, enc):
                    tc.LogStdError("Encoding Failure in {0}.  Not {1}".format(a, enc))
                    overall_status += 1

        tc.LogStdOut("Tested Encoding on {0} files".format(files_tested))
        if overall_status != 0:
            tc.SetFailed("CharEncoding {0} Failed.  Errors {1}".format(packagename, overall_status), "CHAR_ENCODING_CHECK_FAILED")
        else:
            tc.SetSuccess()
        return overall_status

    def TestEncodingOk(self, apath, encodingValue):
        try:
            with open(apath, "rb") as fobj:
                fobj.read().decode(encodingValue)
        except Exception as exp:
            logging.error("Encoding failure: file: {0} type: {1}".format(apath, encodingValue))
            logging.debug("EXCEPTION: while processing {1} - {0}".format(exp, apath))
            return False

        return True
