#=========================== begin_copyright_notice ============================
#
# Copyright (c) 2020-2021 Intel Corporation
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom
# the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#
#============================ end_copyright_notice =============================

#=========================== Host / Target Architecture ========================

include_guard(DIRECTORY)

# Detects host and target architecture.
#
# Currently supports: Windows32, Windows64, WindowsARM, Android32, Android64, AndroidMIPS, AndroidARM,
#                     Linux32, Linux64, LinuxMIPS, LinuxARM
#
# @param targetArchVarName Name of variable placeholder for target architecture.
# @param hostArchVarName   Name of variable placeholder for host architecture.
function(igc_arch_detect targetArchVarName hostArchVarName)
  string(TOLOWER "${CMAKE_GENERATOR}" _cmakeGenerator)
  string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" _cmakeTargetProcessor)

  # Target architecture:
  # Detect target architecture on Windows using suffix from generator.
  if(CMAKE_SYSTEM_NAME MATCHES "Windows")
    if(_cmakeGenerator MATCHES " (arm|aarch64)$")
      set(_targetArchitecture "WindowsARM")
    elseif(_cmakeGenerator MATCHES " (x64|x86_64|win64|amd64|64[ ]*\\-[ ]*bit)$")
      set(_targetArchitecture "Windows64")
    else()
      set(_targetArchitecture "Windows32")
    endif()
  # Use system processor set by toolchain or CMake.
  elseif(ANDROID OR (CMAKE_SYSTEM_NAME MATCHES "Linux"))
    if(ANDROID)
      set(_targetArchOS "Android")
    else()
      set(_targetArchOS "Linux")
    endif()

    if(_cmakeTargetProcessor MATCHES "(x64|x86_64|amd64|64[ ]*\\-[ ]*bit)")
      set(_targetArchitecture "${_targetArchOS}64")
    elseif(_cmakeTargetProcessor MATCHES "(x32|x86|i[0-9]+86|32[ ]*\\-[ ]*bit)")
      set(_targetArchitecture "${_targetArchOS}32")
    elseif(_cmakeTargetProcessor MATCHES "mips")
      set(_targetArchitecture "${_targetArchOS}MIPS")
    else()
      set(_targetArchitecture "${_targetArchOS}ARM")
    endif()
  else()
    set(_targetArchitecture "Unknown-NOTFOUND")
  endif()

  # Host architecture:
  # Detect system architecture using WMI.
  if(CMAKE_HOST_SYSTEM_NAME MATCHES "Windows")
    if (("$ENV{PROCESSOR_ARCHITECTURE}" MATCHES "AMD64") OR
        ("$ENV{PROCESSOR_ARCHITEW6432}" MATCHES "AMD64"))
      set(_hostArchitecture "Windows64")
    else()
      set(_hostArchitecture "Windows32")
    endif()
  # Use 'uname -m' to detect kernel architecture.
  elseif(CMAKE_HOST_SYSTEM_NAME MATCHES "Linux")
    set(_hostArchOS "Linux")
    set(_osArchitecture "x86_64")
    execute_process(
        COMMAND uname -m
        TIMEOUT 10
        OUTPUT_VARIABLE _osArchitecture
        ERROR_QUIET
        OUTPUT_STRIP_TRAILING_WHITESPACE
      )
    string(TOLOWER "${_osArchitecture}" _osArchitecture)

    if(_osArchitecture MATCHES "(x64|x86_64|amd64|64[ ]*\\-[ ]*bit)")
      set(_hostArchitecture "${_hostArchOS}64")
    elseif(_osArchitecture MATCHES "(x32|x86|i[0-9]+86|32[ ]*\\-[ ]*bit)")
      set(_hostArchitecture "${_hostArchOS}32")
    elseif(_osArchitecture MATCHES "mips")
      set(_hostArchitecture "${_hostArchOS}MIPS")
    else()
      set(_hostArchitecture "${_hostArchOS}ARM")
    endif()
  else()
    set(_hostArchitecture "Unknown-NOTFOUND")
  endif()

  set("${targetArchVarName}" "${_targetArchitecture}" PARENT_SCOPE)
  set("${hostArchVarName}"   "${_hostArchitecture}" PARENT_SCOPE)
endfunction()


# Determines whether architecture is valid (accepts only normalized).
#
# @param retValName Name of variable placeholder where result will be returned.
# @param arch       Architecture name to validate. Architecture should be normalized.
function(igc_arch_validate retVarName arch)
  # Allowed architectures (list).
  set(__allowedArchs
      "Windows32" "Windows64"               "WindowsARM"
      "Android32" "Android64" "AndroidMIPS" "AndroidARM"
      "Linux32"   "Linux64"   "LinuxMIPS"   "LinuxARM"
    )

  list(FIND __allowedArchs "${arch}" _allowedArchIdx)
  if(_allowedArchIdx LESS 0)
    set("${retVarName}" NO  PARENT_SCOPE)
  else()
    set("${retVarName}" YES PARENT_SCOPE)
  endif()
endfunction()


# Normalizes architecture name. If architecture is not supported by helper functions
# the "Unknown-NOTFOUND" will be returned.
#
# Currently supports: Windows32, Windows64, WindowsARM, Android32, Android64, AndroidMIPS, AndroidARM,
#                     Linux32, Linux64, LinuxMIPS, LinuxARM
#
# @param retValName Name of variable placeholder where result will be returned.
# @param arch       Architecture name to normalize / filter.
function(igc_arch_normalize retVarName arch)
  string(TOLOWER "${arch}" _arch)
  string(STRIP "${_arch}" _arch)

  if(_arch MATCHES "^win")
    set(_osPart "Windows")
  elseif(_arch MATCHES "^and")
    set(_osPart "Android")
  elseif(_arch MATCHES "^lin")
    set(_osPart "Linux")
  else()
    set(_osPart "Windows")
  endif()

  if(_arch MATCHES "64$")
    set(_cpuPart "64")
  elseif(_arch MATCHES "(32|86)$")
    set(_cpuPart "32")
  elseif(_arch MATCHES "mips^")
    set(_cpuPart "MIPS")
  elseif(_arch MATCHES "arm|aarch64")
    set(_cpuPart "ARM")
  else()
    set("${retVarName}" "Unknown-NOTFOUND" PARENT_SCOPE)
    return()
  endif()

  set(_normalizedArch "${_osPart}${_cpuPart}")
  igc_arch_validate(_archValid "${_normalizedArch}")
  if(_archValid)
    set("${retVarName}" "${_normalizedArch}" PARENT_SCOPE)
  else()
    set("${retVarName}" "Unknown-NOTFOUND" PARENT_SCOPE)
  endif()
endfunction()

# Gets OS platform used in specified architecture. If it cannot be determined
# the "Unknown-NOTFOUND" will be returned.
#
# @param retValName Name of variable placeholder where result will be returned.
# @param arch       Architecture name to get info from.
function(igc_arch_get_os retVarName arch)
  set(_os_detect_list "^(Windows|Android|Linux)(.*)$")

  if(arch MATCHES "${_os_detect_list}")
    set("${retVarName}" "${CMAKE_MATCH_1}" PARENT_SCOPE)
  else()
    set("${retVarName}" "Unknown-NOTFOUND" PARENT_SCOPE)
  endif()
endfunction()

function(igc_arch_get_cpu retVarName)
  if(CMAKE_SIZEOF_VOID_P EQUAL 8)
    set("${retVarName}" "64" PARENT_SCOPE)
  else()
    set("${retVarName}" "32" PARENT_SCOPE)
  endif()
endfunction()

# Determines whether cross-compilation is needed.
#
# @param retValName Name of variable placeholder where result will be returned.
# @param targetArch Target architecture (it will be normalized).
# @param hostArch   Host architecture (it will be normalized).
function(igc_arch_crosscompile_needed retVarName targetArch hostArch)
  # Allowed cross-executions (keys list, lists for each key).
  # Key:    host architecture.
  # Values: target architecture that can be run on host (different than host).
  set(__allowedCrossExecution "Windows64")
  set(__allowedCrossExecution_Windows64 "Windows32")

  igc_arch_normalize(_targetArch "${targetArch}")
  igc_arch_normalize(_hostArch   "${hostArch}")

  if(_targetArch STREQUAL _hostArch)
    set("${retVarName}" NO PARENT_SCOPE)
  else()
    list(FIND __allowedCrossExecution "${_hostArch}" _keyIdx)
    if(_keyIdx LESS 0)
      set("${retVarName}" YES PARENT_SCOPE)
    else()
      list(FIND "__allowedCrossExecution_${_hostArch}" "${_targetArch}" _valIdx)
      if(_valIdx LESS 0)
        set("${retVarName}" YES PARENT_SCOPE)
      else()
        set("${retVarName}" NO PARENT_SCOPE)
      endif()
    endif()
  endif()
endfunction()
