# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
# All rights reserved.
# Confidential and Proprietary - Qualcomm Technologies, Inc.



import SCons
import os
import SCons.Script
import sys
import platform
import json
import re

def dummy(*args,**kwargs):
  pass

def exists(env):
  return True

def is_key_enable(env, keys):
  keys = [keys] if not isinstance(keys,list) else keys
  return len(set(keys).intersection(set(env.get('IMAGE_BUILD_TAGS',[])))) > 0

def check_alias(env, alias_list=None):
  if alias_list is None:
    alias_list = env['IMAGE_ALIASES']
  return len(set(alias_list).intersection(set(SCons.Script.COMMAND_LINE_TARGETS))) > 0

def init_image_vars(env, alias_list, proc, config='none', plat='l4',
   buildpath=None, flashtype='nand', target='${IMAGE_NAME}_${BUILD_ID}',
   build_kcfg=None, build_tags=[], deprecated_build_tags=[], tools=[]):

  env['PROC'] = proc
  env.Replace(IMAGE_ALIASES=alias_list)
  env.Replace(IMAGE_BUILD_TAGS=build_tags)
  env.Replace(BUILDPATH=env.subst(alias_list[0]).lower() + "/${PROC}/${BUILD_ID}")
  for t in build_tags:
    env[t] = True
  env.Replace(IMAGE_TOOLS=tools)

def init_build_config(env, *args, **kwargs):
  for tool in env.get('IMAGE_TOOLS', []):
    if env.IsFile(tool):
      env.LoadToolScript(tool)
    else:
      env.PrintWarning ("Cannot find tool named [%s]!!" % str(tool))

#we can't call Entry() on a path that may not exist, as scons will intern it and assume
# that it actually does exist, causing things like env.Glob to detect the non existant entry
def sconstruct_relative_to_abs(env,path):
  path = path.strip()
  path = env.subst(path)
  if path.startswith('#'):
    path = path[1:]
    if path.startswith('/'):
      path = path[1:]
    elif path.startswith('\\'):
      path = path[2:]
    path = os.path.join(env.Dir('#').get_abspath(),path)

  return path

#===============================================================================
# real_path
# we actually use os.path.abspath instead of os.path.realpath. The only
# functional difference is that realpath resolves symlinks and takes much, much
# longer. We don't care about resolving symlinks to the real path; the symlink
# works just fine for our purposes
#===============================================================================
def real_path(env, paths, posix=False):
  env_subst = lambda x: sconstruct_relative_to_abs(env,x)
  ospath_realpath = os.path.abspath

  if env.get('PLATFORM', 'none') == 'cygwin':
    posix = True

  if type(paths) is list:
    paths = [env_subst(path) for path in paths]
    paths = [ospath_realpath(path) for path in paths]

    if posix:
      paths = [path.replace('\\', '/') for path in paths]
  else:
    paths = env_subst(paths)
    paths = ospath_realpath(paths)

    if posix:
      paths = paths.replace('\\', '/')

  return paths

def path_exists(env, path):
   path = env.RealPath(path)
   return os.path.exists(path)

def get_builder_action (env, action, action_text = 'Generating ', action_target="TARGET", action_source="SOURCE"):
  action_target_name_str = "None"

  if action_target is not None:
    action_target_name_str = "${%s.name}" % action_target

  verbose_str = "=== %s ${BUILDPATH}/%s" % (action_text, action_target_name_str)

  act = SCons.Action.Action(action, verbose_str)
  return act

def load_tool_script(env, tool, toolpath=None):
  if toolpath is None:
    toolpath = []
  if not isinstance(toolpath,list):
    toolpath = [toolpath]

  if env.IsFile(tool):
    toolpath.append(os.path.dirname(tool))
    tool = os.path.splitext(os.path.basename(tool))[0]

  env.Tool(tool, toolpath=toolpath)

  return None

def is_dir(env, path):
  path = env.RealPath(path)
  return os.path.isdir(path)

def is_file(env, path):
  path = env.RealPath(path)
  return os.path.isfile(path)

def print_info(env, message, raw=True):
  print('** INFO: ' + env.subst(str(message), raw))

def print_debug_info(env, debuginfo_type, message, raw=True):
  debuginfo_type = str(debuginfo_type)
  debuginfo_opt = env.get('DEBUGINFO_OPT',[])

  if debuginfo_type in debuginfo_opt:
    print ('** DBGINF: ' + env.subst(str(message), raw))

def print_warning(env, message, width=80, raw=True):
   if not env.get('DISABLE_BUILD_WARNINGS', False):
      print ('!! WARNING: ' + env.subst(str(message), raw))

# This method extracts the version number from sdk_version
# version number is considered to be the number after the last delimeter "."
# append it to the string "1_"
# if the input sdk_version is not of valid format (examples of valid format "TZ.XF.5.1.14" or "1.14") exits the execution
def extract_sdk_version_number(sdk_version):
  if not (re.match(r"(TZ.XF.[0-9]+.[0-9]+)",sdk_version) or re.match(r"[0-9]+.[0-9]+",sdk_version)):
    print("Error: SDK Version found had unexpected format!")
    exit(1)
  file_format="1_"+(sdk_version.split(".")[-1])
  return file_format

#For setting build enviornment with the values in sdk_config_<version>.cfg
def set_build_env_cfg(env):
  env['BUILD_FEATURES'] = json.loads(env['BUILD_FEATURES'])
  for feature in env['BUILD_FEATURES']:
    env[feature] = env['BUILD_FEATURES'][feature]
  env['OVERRIDE_TA_METADATA'] = json.loads(env['OVERRIDE_TA_METADATA'])
  env['CMNLIB_SUPPORTED_APIS'] = json.loads(env['CMNLIB_SUPPORTED_APIS'])
  env['MINKIDL'] = json.loads(env['MINKIDL'])

def generate(env):
  env.AddMethod(real_path,"RealPath")
  env.AddMethod(is_file,'IsFile')
  env.AddMethod(is_dir,'IsDir')
  if env.StandaloneSdk():
    # Check if any CHIPSET value is passed on commandline or set in the environment.
    if SCons.Script.ARGUMENTS.get('CHIPSET'):
      chipset= SCons.Script.ARGUMENTS['CHIPSET']
    elif env.get('CHIPSET'):
      chipset = env['CHIPSET']
    else:
      chipset = ''
    os_name = 'lnx' if platform.system().startswith('Linux') else 'win'
    if env['CHIPSET']:
      if chipset in ['nordau']:
        cfg_files = ['$SDK_ROOT/config/sdk_config_{}.cfg'.format(os_name),'$SDK_ROOT/config/sdk_config_nord.cfg']
      else:
        cfg_files = ['$SDK_ROOT/config/sdk_config_{}.cfg'.format(os_name),'$SDK_ROOT/config/sdk_config_{}.cfg'.format(chipset)]
    else:
      cfg_files = ['$SDK_ROOT/config/sdk_config_{}.cfg'.format(os_name),'$SDK_ROOT/config/sdk_config_unsigned.cfg']
    
    if SCons.Script.ARGUMENTS.get('sdk_version') and SCons.Script.ARGUMENTS.get('sdk_version') not in ["TZ.XF.5.0", "latest"]:
      env['SDK_VER_NUM'] = extract_sdk_version_number(SCons.Script.ARGUMENTS['sdk_version'])
      sdk_version_cfg_file='$SDK_ROOT/config/sdk_config_{}.cfg'.format(env['SDK_VER_NUM'])
      cfg_files.append(sdk_version_cfg_file)

    BAD_VALUE = 'BAD_VALUE_REPLACE_ME_IN_CONFIG_FILE'
    BAD_PATH = '/BAD/PATH/REPLACE/ME/IN/CONFIG/FILE'
    EMPTY_JSON='{}'
    env.AddConfigVars(cfg_files,
    [
      ('CLANG_VERSION',          'clang version',                BAD_VALUE),
      ('GNUPATH',                'gnu path',                     BAD_PATH),
      ('LLVM32INC',              'llvm32inc path',               BAD_PATH),
      ('LLVMBIN',                'llvm bin path',                BAD_PATH),
      ('LLVMCLANG',              'clang path',                   BAD_PATH),
      ('LLVMINC',                'llvm inc path',                BAD_PATH),
      ('LLVMLIB',                'llvm lib path',                BAD_PATH),
      ('LLVMLIBPROP',            'linux-propri_rt path',         BAD_PATH),
      ('LLVMROOT',               'llvm root path',               BAD_PATH),
      ('LLVMTOOLPATH',           'llvm tools path',              BAD_PATH),
      ('LLVM_VERSION',           'llvm version',                 BAD_VALUE),
      ('MAKE_PATH',              'make path',                    BAD_PATH),
      ('MUSL32PATH',             'musl32 path',                  BAD_PATH),
      ('MUSLPATH',               'musl path',                    BAD_PATH),
      ('BUILD_ASIC',             'build asic',                   BAD_VALUE),
      ('BUILD_ID',               'build id',                     BAD_VALUE),
      ('BUILD_VER',              'build version',                BAD_VALUE),
      ('CHIPSET',                'chipset',                      BAD_VALUE),
      ('HAL_PLATFORM',           'hal platform',                 BAD_VALUE),
      ('MAX_NUM_ROOT_CERTS',     'max num root certs',           BAD_VALUE),
      ('MSM_ID',                 'msm id',                       BAD_VALUE),
      ('RAW_CHIPSET',            'raw chipset',                  BAD_VALUE),
      ('SECIMAGE_CONFIG',        'secimage config',              BAD_VALUE),
      ('SOC_HW_VERSION',         'soc hw version',               BAD_VALUE),
      ('SOC_VERS',               'soc vers',                     BAD_VALUE),
      ('SOFTWARE_IMAGE',         'software image',               BAD_VALUE),
      ('TARGET_FAMILY',          'target family',                BAD_VALUE),
      ('TZ_EXEC_MODE',           'tz exec mode',                 BAD_VALUE),
      ('SECIMAGE_CONFIG_QTI',    'SECIMAGE_CONFIG_QTI',          '${SECIMAGE_CONFIG}'),
      ('EL0_ARCH_SET',           'EL0 supported architectures',  ['aarch32','aarch64']),
      ('ARM_AARCH',              'Supported ARM architecture',   ['']),
      ('MINKIDL',                'mink idl',                     EMPTY_JSON), 
      ('CMNLIB_SUPPORTED_APIS',  'cmnlib supported apis',        EMPTY_JSON),
      ('OVERRIDE_TA_METADATA',   'override ta metadata',         EMPTY_JSON),
      ('BUILD_FEATURES',         'build features',               EMPTY_JSON),
    ])
    set_build_env_cfg(env)

  else:
    for k,v in list(SCons.Script.ARGUMENTS.items()):
      env[k] = v
    env.Replace(SECIMAGE_CONFIG_QTI=SCons.Script.ARGUMENTS.get("SECIMAGE_CONFIG_QTI", env["SECIMAGE_CONFIG"]))

  env['SHORT_BUILDPATH'] = env['BUILD_ID']
  env['BUILD_ID'] = env['BUILD_ID'] + 'A'
  env['CUST_H'] = env.subst('cust${BUILD_ID}.h').lower()
  env['USES_PLATFORM_IMAGE_INTEGRITY_CHECK'] = True
  env["PYTHON_PATH"] = sys.executable

  env['MBN_ROOT'] = '${BUILD_ROOT}/build/ms/bin/${SHORT_BUILDPATH}/'

  env.Append(CPPPATH=['$BUILD_ROOT/build/cust','$BUILD_ROOT/build/ms'])

  #NOTE: None of these options actually do anything for apps builds. They're
  #here only for qcscons compatibility and are just stubs
  try:
    SCons.Script.AddOption('--callgraph',
      dest='callgraph',
      action="store_true",
      default=False,
      help='Create call graphs on supported linkers only.')

    SCons.Script.AddOption('--verbose',
      dest='verbose',
      type='int',
      nargs=1,
      default='0',
      help='Verbose level 3=raw (no formatting), 2=detailed, 1=limited, 0=off')

    SCons.Script.AddOption('--preprocess',
      dest='preprocess',
      action="store_true",
      default=False,
      help='Generate C-preprocess file *.o.pp for any compiled file')

    SCons.Script.AddOption('--mixasm',
      dest='mixasm',
      action="store_true",
      default=False,
      help='Generate mixed assembly/source file *.o.mix for any compiled file')

    SCons.Script.AddOption('--hdeptree',
      dest='hdeptree',
      action="store_true",
      default=False,
      help='Generate header dependency tree file *.dep for any compiled file')

    SCons.Script.AddOption('--cleanpack',
      dest='cleanpack',
      action="store_true",
      default=False,
      help='Perform special clean processing for Pack build')
  except:
    pass
  env.Replace(USES_MBNTOOLS = True)
  env.Replace(GLOBAL_DICT = {1:1})
  env.Replace(USES_SEC_POLICY_MULTIPLE_DEFAULT_SIGN=True)
  env.Replace(USES_SEC_POLICY_STEP1_QC_SIGN=True)
  env.Replace(USES_SEC_POLICY_STEP2_OEM_SIGN=True)

  env.Replace(INC_PATH=env['BUILD_ROOT'])
  env.Replace(INC_ROOT=env['BUILD_ROOT'])
  if 'ROOT_GLOBAL_ENV' not in env:
    env['ROOT_GLOBAL_ENV'] = env
  env['INSTALL_LIBPATH'] = ''


  if env.StandaloneSdk():
    env['BUILD_SCRIPTS_ROOT'] = '${SDK_ROOT}/scripts/'
    env.Tool('formatting',toolpath=['${SDK_ROOT}/scripts'])
  else:
    env['BUILD_SCRIPTS_ROOT'] = '${BUILD_ROOT}/tools/build/scons/scripts/'
    env.Tool('formatting',toolpath=['${BUILD_ROOT}/ssg/bsp/build/scripts'])

  env['PYTHONCMD'] = sys.executable
  env['PYTHON_PATH'] = env.get('PYTHON_PATH',os.environ.get('PYTHON_PATH'))

  env.AddMethod(load_tool_script,'LoadToolScript')

  env.AddMethod(print_info,"PrintInfo")
  env.AddMethod(print_warning,"PrintWarning")
  env.AddMethod(print_debug_info,"PrintDebugInfo")
  env.AddMethod(path_exists,"PathExists")

  env.AddMethod(real_path,"SubstRealPath")
  env.AddMethod(get_builder_action,"GetBuilderAction")
  env.AddMethod(dummy,'CleanPack')
  env.AddMethod(dummy, 'RequirePublicApi')
  env.AddMethod(dummy, 'RequirePrivateApi')
  env.AddMethod(dummy, 'RequireProtectedApi')
  env.AddMethod(dummy, 'RequireRestrictedApi')
  env.AddMethod(dummy, 'PublishPrivateApi')
  env.AddMethod(dummy, 'AddBinaryLibrary')
  env.AddMethod(dummy, 'AddBinaryObject')
  env.AddMethod(dummy, 'LoadSoftwareUnits')


  env.AddMethod(is_key_enable, 'IsKeyEnable')

  env.AddMethod(init_image_vars, 'InitImageVars')
  env.AddMethod(init_build_config, 'InitBuildConfig')
  env.AddMethod(check_alias,'CheckAlias')

  if sys.platform.startswith('win'):
    class ourSpawn:
      def ourspawn(self, sh, escape, cmd, args, env):
        import subprocess
        args = ' '.join(args[1:])
        cmdline = ' '.join([cmd, args])
        startupinfo = subprocess.STARTUPINFO()
        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
        proc = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
            stderr=subprocess.PIPE, startupinfo=startupinfo, shell = False, env = env)
        data, err = proc.communicate()
        rv = proc.wait()
        if rv:
          print(err)
        return rv

    buf = ourSpawn()
    buf.ourenv = env
    env['SPAWN'] = buf.ourspawn

