if(__opus_functions)
  return()
endif()
set(__opus_functions INCLUDED)

function(get_library_version OPUS_LIBRARY_VERSION OPUS_LIBRARY_VERSION_MAJOR)
  file(STRINGS configure.ac opus_lt_current_string
       LIMIT_COUNT 1
       REGEX "OPUS_LT_CURRENT=")
  string(REGEX MATCH
               "OPUS_LT_CURRENT=([0-9]*)"
               _
               ${opus_lt_current_string})
  set(OPUS_LT_CURRENT ${CMAKE_MATCH_1})

  file(STRINGS configure.ac opus_lt_revision_string
       LIMIT_COUNT 1
       REGEX "OPUS_LT_REVISION=")
  string(REGEX MATCH
               "OPUS_LT_REVISION=([0-9]*)"
               _
               ${opus_lt_revision_string})
  set(OPUS_LT_REVISION ${CMAKE_MATCH_1})

  file(STRINGS configure.ac opus_lt_age_string
       LIMIT_COUNT 1
       REGEX "OPUS_LT_AGE=")
  string(REGEX MATCH
               "OPUS_LT_AGE=([0-9]*)"
               _
               ${opus_lt_age_string})
  set(OPUS_LT_AGE ${CMAKE_MATCH_1})

  math(EXPR OPUS_LIBRARY_VERSION_MAJOR "${OPUS_LT_CURRENT} - ${OPUS_LT_AGE}")
  set(OPUS_LIBRARY_VERSION_MINOR ${OPUS_LT_AGE})
  set(OPUS_LIBRARY_VERSION_PATCH ${OPUS_LT_REVISION})
  set(
    OPUS_LIBRARY_VERSION
    "${OPUS_LIBRARY_VERSION_MAJOR}.${OPUS_LIBRARY_VERSION_MINOR}.${OPUS_LIBRARY_VERSION_PATCH}"
    PARENT_SCOPE)
  set(OPUS_LIBRARY_VERSION_MAJOR ${OPUS_LIBRARY_VERSION_MAJOR} PARENT_SCOPE)
endfunction()

function(get_package_version PACKAGE_VERSION)
  find_package(Git)
  if(Git_FOUND AND EXISTS "${CMAKE_CURRENT_LIST_DIR}/.git")
    execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --match "v*"
                    OUTPUT_VARIABLE OPUS_PACKAGE_VERSION)
    if(OPUS_PACKAGE_VERSION)
      string(STRIP ${OPUS_PACKAGE_VERSION}, OPUS_PACKAGE_VERSION)
      string(REPLACE \n
                     ""
                     OPUS_PACKAGE_VERSION
                     ${OPUS_PACKAGE_VERSION})
      string(REPLACE ,
                     ""
                     OPUS_PACKAGE_VERSION
                     ${OPUS_PACKAGE_VERSION})

      string(SUBSTRING ${OPUS_PACKAGE_VERSION}
                       1
                       -1
                       OPUS_PACKAGE_VERSION)
      set(PACKAGE_VERSION ${OPUS_PACKAGE_VERSION} PARENT_SCOPE)
      return()
    endif()
  endif()

  if(EXISTS "${CMAKE_SOURCE_DIR}/package_version")
    # Not a git repo, lets' try to parse it from package_version file if exists
    file(STRINGS package_version opus_package_version_string
         LIMIT_COUNT 1
         REGEX "PACKAGE_VERSION=")
    string(REPLACE "PACKAGE_VERSION="
                   ""
                   opus_package_version_string
                   ${opus_package_version_string})
    string(REPLACE "\""
                   ""
                   opus_package_version_string
                   ${opus_package_version_string})
    # In case we have a unknown dist here we just replace it with 0
    string(REPLACE "unknown"
                   "0"
                   opus_package_version_string
                   ${opus_package_version_string})
    set(PACKAGE_VERSION ${opus_package_version_string} PARENT_SCOPE)
    return()
  endif()

  # if all else fails set to 0
  set(PACKAGE_VERSION 0 PARENT_SCOPE)
endfunction()

function(check_flag NAME FLAG)
  include(CheckCCompilerFlag)
  check_c_compiler_flag(${FLAG} ${NAME}_SUPPORTED)
endfunction()

include(CheckIncludeFile)
# function to check if compiler supports SSE, SSE2, SSE4.1 and AVX if target
# systems may not have SSE support then use OPUS_MAY_HAVE_SSE option if target
# system is guaranteed to have SSE support then OPUS_PRESUME_SSE can be used to
# skip SSE runtime check
function(opus_detect_sse COMPILER_SUPPORT_SIMD)
  message(STATUS "Check SIMD support by compiler")
  check_include_file(xmmintrin.h HAVE_XMMINTRIN_H) # SSE1
  if(HAVE_XMMINTRIN_H)
    if(MSVC)
      # different arch options for 32 and 64 bit target for MSVC
      if(CMAKE_SIZEOF_VOID_P EQUAL 4)
        check_flag(SSE1 /arch:SSE)
      else()
        set(SSE1_SUPPORTED
            1
            PARENT_SCOPE)
      endif()
    else()
      check_flag(SSE1 -msse)
    endif()
  else()
    set(SSE1_SUPPORTED
        0
        PARENT_SCOPE)
  endif()

  check_include_file(emmintrin.h HAVE_EMMINTRIN_H) # SSE2
  if(HAVE_EMMINTRIN_H)
    if(MSVC)
      if(CMAKE_SIZEOF_VOID_P EQUAL 4)
        check_flag(SSE2 /arch:SSE2)
      else()
        set(SSE2_SUPPORTED
            1
            PARENT_SCOPE)
      endif()
    else()
      check_flag(SSE2 -msse2)
    endif()
  else()
    set(SSE2_SUPPORTED
        0
        PARENT_SCOPE)
  endif()

  check_include_file(smmintrin.h HAVE_SMMINTRIN_H) # SSE4.1
  if(HAVE_SMMINTRIN_H)
    if(MSVC)
      if(CMAKE_SIZEOF_VOID_P EQUAL 4)
        check_flag(SSE4_1 /arch:SSE2) # SSE2 and above
      else()
        set(SSE4_1_SUPPORTED
            1
            PARENT_SCOPE)
      endif()
    else()
      check_flag(SSE4_1 -msse4.1)
    endif()
  else()
    set(SSE4_1_SUPPORTED
        0
        PARENT_SCOPE)
  endif()

  check_include_file(immintrin.h HAVE_IMMINTRIN_H) # AVX
  if(HAVE_IMMINTRIN_H)
    if(MSVC)
      check_flag(AVX /arch:AVX)
    else()
      check_flag(AVX -mavx)
    endif()
  else()
    set(AVX_SUPPORTED
        0
        PARENT_SCOPE)
  endif()

  if(SSE1_SUPPORTED OR SSE2_SUPPORTED OR SSE4_1_SUPPORTED OR AVX_SUPPORTED)
    set(COMPILER_SUPPORT_SIMD 1 PARENT_SCOPE)
  else()
    message(STATUS "No SIMD support in compiler")
  endif()
endfunction()

function(opus_detect_neon COMPILER_SUPPORT_NEON)
  if(CMAKE_SYSTEM_PROCESSOR MATCHES "(arm|aarch64)")
    message(STATUS "Check NEON support by compiler")
    check_include_file(arm_neon.h HAVE_ARM_NEON_H)
    if(HAVE_ARM_NEON_H)
      set(COMPILER_SUPPORT_NEON ${HAVE_ARM_NEON_H} PARENT_SCOPE)
    endif()
  endif()
endfunction()

function(opus_supports_cpu_detection RUNTIME_CPU_CAPABILITY_DETECTION)
  if(MSVC)
    check_include_file(intrin.h HAVE_INTRIN_H)
  else()
    check_include_file(cpuid.h HAVE_CPUID_H)
  endif()
  if(HAVE_INTRIN_H OR HAVE_CPUID_H)
    set(RUNTIME_CPU_CAPABILITY_DETECTION 1 PARENT_SCOPE)
  elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "(arm|aarch64)")
    # ARM cpu detection is implemented for Windows and anything
    # using a Linux kernel (such as Android).
    if (CMAKE_SYSTEM_NAME MATCHES "(Windows|Linux|Android)")
      set(RUNTIME_CPU_CAPABILITY_DETECTION 1 PARENT_SCOPE)
    endif ()
  else()
    set(RUNTIME_CPU_CAPABILITY_DETECTION 0 PARENT_SCOPE)
  endif()
endfunction()

function(add_sources_group target group)
  target_sources(${target} PRIVATE ${ARGN})
  source_group(${group} FILES ${ARGN})
endfunction()

function(get_opus_sources SOURCE_GROUP MAKE_FILE SOURCES)
  # read file, each item in list is one group
  file(STRINGS ${MAKE_FILE} opus_sources)

  # add wildcard for regex match
  string(CONCAT SOURCE_GROUP ${SOURCE_GROUP} ".*$")

  # find group
  foreach(val IN LISTS opus_sources)
    if(val MATCHES ${SOURCE_GROUP})
      list(LENGTH val list_length)
      if(${list_length} EQUAL 1)
        # for tests split by '=' and clean up the rest into a list
        string(FIND ${val} "=" index)
        math(EXPR index "${index} + 1")
        string(SUBSTRING ${val}
                         ${index}
                         -1
                         sources)
        string(REPLACE " "
                       ";"
                       sources
                       ${sources})
      else()
        # discard the group
        list(REMOVE_AT val 0)
        set(sources ${val})
      endif()
      break()
    endif()
  endforeach()

  list(LENGTH sources list_length)
  if(${list_length} LESS 1)
    message(
      FATAL_ERROR
        "No files parsed succesfully from ${SOURCE_GROUP} in ${MAKE_FILE}")
  endif()

  # remove trailing whitespaces
  set(list_var "")
  foreach(source ${sources})
    string(STRIP "${source}" source)
    list(APPEND list_var "${source}")
  endforeach()

  set(${SOURCES} ${list_var} PARENT_SCOPE)
endfunction()
