## @file
# Copyright (c) 2023, The OCE Build Authors. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
##
"""Methods for retrieving and handling SSDT binaries and source code."""
from collections import OrderedDict
from contextlib import contextmanager
from functools import partial
from graphlib import TopologicalSorter
from os import makedirs, SEEK_END
from shutil import copyfile, rmtree, which
from tempfile import mkdtemp, NamedTemporaryFile
from typing import Callable, Generator, List, Optional, Union
from ocebuild.filesystem import glob, remove
from ocebuild.filesystem.cache import CACHE_DIR, UNPACK_DIR
from ocebuild.parsers.asl import parse_ssdt_namespace
from ocebuild.sources import request
from ocebuild.sources.binary import get_binary_ext, wrap_binary
from ocebuild.sources.github import github_file_url
#NOTE: This import was remapped from 'third_party' to 'ocebuild.third_party'.
from ocebuild.third_party.cpython.pathlib import Path
@contextmanager
@contextmanager
[docs]def iasl_wrapper(cache: bool=True
                 ) -> Generator[Callable[[List[str]], str], any, None]:
  """Returns a subprocess wrapper for an existing or extracted iasl binary.
  By default, this method will attempt to locate an existing iasl binary on the
  system. If one is not found, it will extract a temporary iasl binary from the
  Qonfused/iASL repository.
  Args:
    cache: Whether to cache the extracted iasl binary.
  Yields:
    A subprocess wrapper for the iasl binary.
  """
  tmp_wrapper = None
  try:
    # Check if iasl is already installed
    if (path := which('iasl')):
      iasl = partial(wrap_binary, binary_path=path)
    # Otherwise, extract a temporary iasl binary
    elif not tmp_wrapper:
      with extract_iasl_binary(cache=cache, persist=True) as tmp_wrapper:
        iasl = tmp_wrapper
    #TODO: Add option to provide -da or -e flag w/ user-provided DSDT/SSDTs
    # @see https://www.tonymacx86.com/threads/guide-patching-laptop-dsdt-ssdts.152573/
    # @see https://github.com/acpica/acpica/issues/414#issuecomment-432378819
    yield iasl
  finally:
    if tmp_wrapper and not cache:
      remove(iasl.keywords['binary_path']) 
@contextmanager
[docs]def translate_ssdts(filepaths: List[Union[str, Path]],
                    directory: Optional[Union[str, Path]]=None,
                    persist: bool=False
                    ) -> Generator[List[Path], any, None]:
  """Decompiles or compiles SSDT tables using iasl.
  Args:
    filepaths: A list of filepaths to SSDT *.aml or *.dsl files.
    persist: Whether to persist the SSDT files.
  Yields:
    A list of filepaths to the compiled + decompiled SSDT files.
  """
  tmp_dir = Path(mkdtemp(dir=directory))
  try:
    with iasl_wrapper() as iasl:
      for filepath in map(Path, filepaths):
        tmp_copy = tmp_dir.joinpath(filepath.name)
        copyfile(filepath, tmp_copy)
        iasl(['-ve', tmp_copy])
    yield list(map(Path, tmp_dir.iterdir()))
  finally:
    # Cleanup after context exits
    if not persist: rmtree(tmp_dir) 
[docs]def sort_ssdt_symbols(filepaths: List[Union[str, Path]]) -> OrderedDict:
  """Sorts the injection order of SSDT tables by resolving symbolic references.
  This is a naive implementation that does not prune conditional branches or
  build flags outside of standard ACPI spec. It is intended to be used as a
  baseline reference for the injection order of SSDT tables in the absence of
  information about the system's DSDT.
  Args:
    filepaths: A list of filepaths to SSDT *.dsl files.
  Returns:
    An ordered dictionary of SSDT table names with their exported symbols.
  """
  ssdt_names = list(Path(f).stem for f in filepaths)
  # Extract flat tree of SSDT symbols and tables
  dependency_tree = OrderedDict()
  for ssdt, filepath in sorted(zip(ssdt_names, filepaths)):
    with open(filepath, 'r', encoding='UTF-8') as file:
      namespace = parse_ssdt_namespace(file)
    dependency_tree[ssdt] = list(namespace['imports'].keys())
    for symbol in namespace['statements']:
      if not symbol in dependency_tree:
        dependency_tree[symbol] = [ssdt]
      else:
        dependency_tree[symbol].append(ssdt)
  # Sort table load order
  sorted_dependencies = OrderedDict()
  table = 'DSDT'
  for symbol in TopologicalSorter(dependency_tree).static_order():
    # Handle SSDT dependencies
    if symbol in ssdt_names:
      table = symbol
      sorted_dependencies[table] = [s for s,d in dependency_tree.items()
                                    if table in d]
    # Handle DSDT dependencies
    elif table not in sorted_dependencies:
      sorted_dependencies[table] = []
    elif table not in ssdt_names:
      sorted_dependencies[table].append(symbol)
  # Sort table dependencies by root -> alphabetically
  for k, arr in sorted_dependencies.items():
    sorted_dependencies[k] = sorted(arr, key=lambda s: (s.count('.'), s))
  return sorted_dependencies 
__all__ = [
  # Functions (5)
  "extract_iasl_binary",
  "iasl_wrapper",
  "translate_ssdts",
  "sort_ssdt_symbols",
  "extract_ssdts"
]