# Copyright 2020 Google LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

cmake_minimum_required(VERSION 3.22...4.1)

# Prefer GLVND libraries by default.
cmake_policy(SET CMP0072 NEW)

project(sjpeg
        LANGUAGES C CXX)
set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 11)

# Options for coder / decoder executables.
option(SJPEG_ENABLE_SIMD "Enable any SIMD optimization." ON)
option(SJPEG_BUILD_EXAMPLES "Build the sjpeg / vjpeg command line tools." ON)
option(SJPEG_BUILD_TESTS "Build the unit tests." ON)
option(SJPEG_ENABLE_PROGRESSIVE "Build with progressive JPEG encoding support." ON)
option(SJPEG_ENABLE_MULTITHREADING "Build with multi-threaded encoding support." ON)
# Trimming: each feature turned OFF is stripped out (-DSJPEG_NO_<feature>).
option(SJPEG_ENABLE_RISKINESS "Build the SjpegRiskiness() analysis (else the YUV_AUTO mode is plain 4:2:0)." ON)
option(SJPEG_ENABLE_SHARP_YUV "Build the sharp-YUV 4:2:0 conversion (else plain 4:2:0)." ON)
option(SJPEG_ENABLE_SEARCH "Build the target size / PSNR search (else target_mode is ignored)." ON)
option(SJPEG_ENABLE_ADAPTIVE_QUANT "Build the adaptive quantization (else the matrices are used as is)." ON)
option(SJPEG_ENABLE_METADATA "Build the EXIF / ICC / XMP / APP markers (else not written)." ON)
option(SJPEG_ENABLE_C_FALLBACK "Build the plain-C variants of the SSE2 / NEON functions." ON)
# Opt-in, off by default: unlike SSE2 (guaranteed on all x86-64) or NEON,
# AVX2 isn't a safe baseline assumption, so it isn't part of the
# SJPEG_SIMD_DEFINES loop in cmake/cpu.cmake (which applies its flag to the
# whole target). Instead just src/*_avx2.cc gets -mavx2, and support is
# picked at runtime (SupportsAVX2() in src/enc.cc), so a binary built with
# this ON still runs on pre-Haswell CPUs.
option(SJPEG_HAVE_AVX2 "Build the AVX2 kernel variants (x86 only)." OFF)

set(SJPEG_DEP_LIBRARIES)
set(SJPEG_DEP_INCLUDE_DIRS)

if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE "Release" CACHE STRING
    "Build type: Release, Debug or RelWithDebInfo" FORCE
  )
endif()

include(GNUInstallDirs)

# Single source of truth: SJPEG_VERSION in src/sjpeg.h (0xMMmmpp).
file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/src/sjpeg.h SJPEG_VERSION_LINE
     REGEX "^#define SJPEG_VERSION 0x")
string(REGEX MATCH "0x([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])"
       _ "${SJPEG_VERSION_LINE}")
math(EXPR SJPEG_VERSION_MAJOR "0x${CMAKE_MATCH_1}")
math(EXPR SJPEG_VERSION_MINOR "0x${CMAKE_MATCH_2}")
math(EXPR SJPEG_VERSION_PATCH "0x${CMAKE_MATCH_3}")
set(PROJECT_VERSION
    ${SJPEG_VERSION_MAJOR}.${SJPEG_VERSION_MINOR}.${SJPEG_VERSION_PATCH})

################################################################################
# Android only.

if(ANDROID)
  include_directories(${SJPEG_ANDROID_NDK_PATH}/sources/android/cpufeatures)
  add_library(cpufeatures-sjpeg
    STATIC ${SJPEG_ANDROID_NDK_PATH}/sources/android/cpufeatures/cpu-features.c
  )
  target_link_libraries(cpufeatures-sjpeg dl)
  set(SJPEG_DEP_LIBRARIES ${SJPEG_DEP_LIBRARIES} cpufeatures-sjpeg)
  set(SJPEG_DEP_INCLUDE_DIRS ${SJPEG_DEP_INCLUDE_DIRS}
      ${SJPEG_ANDROID_NDK_PATH}/sources/android/cpufeatures
  )
endif()

## Check for SIMD extensions.
include(${CMAKE_CURRENT_LIST_DIR}/cmake/cpu.cmake)

################################################################################
# sjpeg source files.

# Build the sjpeg library.
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/ ${SJPEG_DEP_INCLUDE_DIRS})
set(SJPEG_SRCS
  ${CMAKE_CURRENT_SOURCE_DIR}/src/bit_writer.cc
  ${CMAKE_CURRENT_SOURCE_DIR}/src/bit_writer.h
  ${CMAKE_CURRENT_SOURCE_DIR}/src/colors_rgb.cc
  ${CMAKE_CURRENT_SOURCE_DIR}/src/api.cc
  ${CMAKE_CURRENT_SOURCE_DIR}/src/dichotomy.cc
  ${CMAKE_CURRENT_SOURCE_DIR}/src/enc.cc
  ${CMAKE_CURRENT_SOURCE_DIR}/src/encoders.cc
  ${CMAKE_CURRENT_SOURCE_DIR}/src/entropy.cc
  ${CMAKE_CURRENT_SOURCE_DIR}/src/entropy.h
  ${CMAKE_CURRENT_SOURCE_DIR}/src/fdct.cc
  ${CMAKE_CURRENT_SOURCE_DIR}/src/headers.cc
  ${CMAKE_CURRENT_SOURCE_DIR}/src/headers.h
  ${CMAKE_CURRENT_SOURCE_DIR}/src/dsp.h
  ${CMAKE_CURRENT_SOURCE_DIR}/src/blocks.h
  ${CMAKE_CURRENT_SOURCE_DIR}/src/common.h
  ${CMAKE_CURRENT_SOURCE_DIR}/src/slices.h
  ${CMAKE_CURRENT_SOURCE_DIR}/src/histogram.cc
  ${CMAKE_CURRENT_SOURCE_DIR}/src/md5sum.h
  ${CMAKE_CURRENT_SOURCE_DIR}/src/jpeg_tools.cc
  ${CMAKE_CURRENT_SOURCE_DIR}/src/quantize.cc
  ${CMAKE_CURRENT_SOURCE_DIR}/src/score_175.cc
  ${CMAKE_CURRENT_SOURCE_DIR}/src/simd.h
  ${CMAKE_CURRENT_SOURCE_DIR}/src/sjpeg.h
  ${CMAKE_CURRENT_SOURCE_DIR}/src/sjpegi.h
  ${CMAKE_CURRENT_SOURCE_DIR}/src/yuv_convert.cc
)
if(SJPEG_ENABLE_PROGRESSIVE)
  list(APPEND SJPEG_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/src/prog.cc)
endif()
# enc_parallel.cc: the thread pool, or its serial stand-in
list(APPEND SJPEG_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/src/enc_parallel.cc)
if(SJPEG_ENABLE_MULTITHREADING)
  find_package(Threads REQUIRED)
endif()
add_library(sjpeg ${SJPEG_SRCS})
if(NOT SJPEG_ENABLE_PROGRESSIVE)
  target_compile_definitions(sjpeg PUBLIC SJPEG_NO_PROGRESSIVE)
endif()
if(SJPEG_ENABLE_MULTITHREADING)
  target_link_libraries(sjpeg Threads::Threads)
else()
  target_compile_definitions(sjpeg PUBLIC SJPEG_NO_MULTITHREADING)
endif()
foreach(FEATURE RISKINESS SHARP_YUV SEARCH ADAPTIVE_QUANT METADATA C_FALLBACK)
  if(NOT SJPEG_ENABLE_${FEATURE})
    target_compile_definitions(sjpeg PUBLIC SJPEG_NO_${FEATURE})
  endif()
endforeach()
if(SJPEG_DEP_LIBRARIES)
  target_link_libraries(sjpeg ${SJPEG_DEP_LIBRARIES})
endif()

if(SJPEG_HAVE_AVX2)
  target_sources(sjpeg PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/src/colors_rgb_avx2.cc
    ${CMAKE_CURRENT_SOURCE_DIR}/src/fdct_avx2.cc
    ${CMAKE_CURRENT_SOURCE_DIR}/src/histogram_avx2.cc
    ${CMAKE_CURRENT_SOURCE_DIR}/src/quantize_avx2.cc
    ${CMAKE_CURRENT_SOURCE_DIR}/src/riskiness_avx2.cc
    ${CMAKE_CURRENT_SOURCE_DIR}/src/yuv_convert_avx2.cc)
  if(MSVC)
    set(SJPEG_AVX2_FLAG "/arch:AVX2")
  else()
    set(SJPEG_AVX2_FLAG "-mavx2")
  endif()
  set_source_files_properties(
    ${CMAKE_CURRENT_SOURCE_DIR}/src/colors_rgb_avx2.cc
    ${CMAKE_CURRENT_SOURCE_DIR}/src/fdct_avx2.cc
    ${CMAKE_CURRENT_SOURCE_DIR}/src/histogram_avx2.cc
    ${CMAKE_CURRENT_SOURCE_DIR}/src/quantize_avx2.cc
    ${CMAKE_CURRENT_SOURCE_DIR}/src/riskiness_avx2.cc
    ${CMAKE_CURRENT_SOURCE_DIR}/src/yuv_convert_avx2.cc
    PROPERTIES COMPILE_OPTIONS "${SJPEG_AVX2_FLAG}"
  )
  # PUBLIC: unit_test / sjpeg-bin link against 'sjpeg' and need to see the
  # same define (matches how SJPEG_HAVE_PNG/JPEG propagate below).
  target_compile_definitions(sjpeg PUBLIC SJPEG_HAVE_AVX2)
endif()

# Make sure the OBJECT libraries are built with position independent code
# (it is not ON by default).
set_target_properties(sjpeg PROPERTIES POSITION_INDEPENDENT_CODE ON)

# Set the version numbers.
set_target_properties(sjpeg PROPERTIES VERSION ${PROJECT_VERSION}
                      SOVERSION ${PROJECT_VERSION})

# Build the executables if asked for.
if(SJPEG_BUILD_EXAMPLES)
  # Find the standard image libraries.
  set(SJPEG_DEP_IMG_LIBRARIES)
  set(SJPEG_DEP_IMG_INCLUDE_DIRS)
  foreach(I_LIB PNG JPEG)
    find_package(${I_LIB})
    set(SJPEG_HAVE_${I_LIB} ${${I_LIB}_FOUND})
    if(${I_LIB}_FOUND)
      set(SJPEG_DEP_IMG_LIBRARIES ${SJPEG_DEP_IMG_LIBRARIES}
          ${${I_LIB}_LIBRARIES})
      set(SJPEG_DEP_IMG_INCLUDE_DIRS ${SJPEG_DEP_IMG_INCLUDE_DIRS}
          ${${I_LIB}_INCLUDE_DIRS})
    endif()
  endforeach()

  # Find the OpenGL/GLUT libraries.
  set(SJPEG_DEP_GL_LIBRARIES)
  set(SJPEG_DEP_GL_INCLUDE_DIRS)
  find_package(OpenGL)
  if(OPENGL_FOUND)
    set(SJPEG_DEP_GL_LIBRARIES
        ${SJPEG_DEP_GL_LIBRARIES} ${OPENGL_LIBRARIES})
    set(SJPEG_DEP_GL_INCLUDE_DIRS
        ${SJPEG_DEP_GL_INCLUDE_DIRS} ${OPENGL_INCLUDE_DIR})
    set(SJPEG_HAVE_OPENGL TRUE)
  endif()
  find_package(GLUT)
  if(GLUT_FOUND)
    set(SJPEG_DEP_GL_LIBRARIES
        ${SJPEG_DEP_GL_LIBRARIES} ${GLUT_glut_LIBRARY})
    set(SJPEG_DEP_GL_INCLUDE_DIRS
        ${SJPEG_DEP_GL_INCLUDE_DIRS} ${GLUT_INCLUDE_DIR})
    set(SJPEG_HAVE_GLUT TRUE)
  endif()

  # build the utils library
  include_directories(${SJPEG_DEP_IMG_INCLUDE_DIRS})
  add_library(sjpeg-utils
              ${CMAKE_CURRENT_SOURCE_DIR}/examples/utils.cc
              ${CMAKE_CURRENT_SOURCE_DIR}/examples/utils.h
  )
  target_link_libraries(sjpeg-utils sjpeg)

  if(WIN32)
    # quiet warnings related to fopen, sscanf
    target_compile_definitions(sjpeg-utils PRIVATE _CRT_SECURE_NO_WARNINGS)
  endif()
  if(APPLE)
    # OpenGL/GLUT are deprecated on macOS; keep the build log readable.
    target_compile_definitions(sjpeg-utils PUBLIC GL_SILENCE_DEPRECATION)
  endif()
  if(SJPEG_HAVE_OPENGL)
    # check pthread for GL libraries
    set(THREADS_PREFER_PTHREAD_FLAG ON)
    find_package(Threads)
    if(Threads_FOUND)
      if(CMAKE_USE_PTHREADS_INIT)
        list(APPEND SJPEG_DEP_GL_LIBRARIES ${CMAKE_THREAD_LIBS_INIT})
      endif()
    endif()
    target_compile_definitions(sjpeg-utils PUBLIC SJPEG_HAVE_OPENGL)
    add_definitions(${OPENGL_DEFINITIONS})
  endif()
  if(SJPEG_HAVE_GLUT)
    add_definitions(${GLUT_DEFINITIONS})
  endif()
  if(SJPEG_HAVE_JPEG)
    target_compile_definitions(sjpeg-utils PUBLIC SJPEG_HAVE_JPEG)
  endif()
  if(SJPEG_HAVE_PNG)
    target_compile_definitions(sjpeg-utils PUBLIC SJPEG_HAVE_PNG)
  endif()
  if(SJPEG_DEP_IMG_LIBRARIES)
    # check pthread for GL libraries
    target_link_libraries(sjpeg-utils ${SJPEG_DEP_IMG_LIBRARIES}
                          ${SJPEG_DEP_GL_LIBRARIES})
  endif()
  # set_target_properties(sjpeg-utils PROPERTIES POSITION_INDEPENDENT_CODE ON)

  # sjpeg
  add_executable(sjpeg-bin ${CMAKE_CURRENT_SOURCE_DIR}/examples/sjpeg.cc)
  target_link_libraries(sjpeg-bin sjpeg sjpeg-utils)
  set_target_properties(sjpeg-bin PROPERTIES OUTPUT_NAME sjpeg)

  # vjpeg
  include_directories(${SJPEG_DEP_GL_INCLUDE_DIRS})
  add_executable(vjpeg ${CMAKE_CURRENT_SOURCE_DIR}/examples/vjpeg.cc)
  # Force to link against pthread.
  include(CheckCXXSourceCompiles)
  set(CMAKE_REQUIRED_FLAGS_INI ${CMAKE_REQUIRED_FLAGS})
  set(CMAKE_REQUIRED_FLAGS "-Wl,--no-as-needed")
  check_cxx_source_compiles("int main(void){return 0;}" FLAG_NO_AS_NEEDED)
  set(CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS_INI})
  if(FLAG_NO_AS_NEEDED)
    target_link_libraries(vjpeg "-Wl,--no-as-needed")
  endif()

  # check whether we need to include GLUT/glut.h or GL/glut.h
  include(CheckIncludeFileCXX)
  check_include_file_cxx(GLUT/glut.h HAVE_GLUT_GLUT_H)
  check_include_file_cxx(GL/glut.h HAVE_GL_GLUT_H)
  if(HAVE_GLUT_GLUT_H)
    add_definitions(-DHAVE_GLUT_GLUT_H)
  elseif(HAVE_GL_GLUT_H)
    add_definitions(-DHAVE_GL_GLUT_H)
  endif()
  target_link_libraries(vjpeg ${SJPEG_DEP_GL_LIBRARIES} sjpeg sjpeg-utils)

  install(TARGETS sjpeg-bin vjpeg RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()

# Build the unit tests if asked for.
if(SJPEG_BUILD_TESTS)
  enable_testing()
  add_executable(unit_test ${CMAKE_CURRENT_SOURCE_DIR}/tests/unit_test.cc)
  # the tests encode from several threads at once
  set(THREADS_PREFER_PTHREAD_FLAG ON)
  find_package(Threads REQUIRED)
  target_link_libraries(unit_test sjpeg Threads::Threads)
  add_test(NAME unit_test COMMAND unit_test)

  set(CHECK_DEPENDS unit_test)
  if(SJPEG_BUILD_EXAMPLES)
    add_test(NAME test_cmd
             COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_cmd.sh $<TARGET_FILE:sjpeg-bin>)
    add_test(NAME test_png_jpg
             COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_png_jpg.sh $<TARGET_FILE:sjpeg-bin>)
    list(APPEND CHECK_DEPENDS sjpeg-bin)
  endif()

  # `ctest` alone never builds its targets first (unlike `make test` in the
  # plain Makefile); this mirrors that with a target that does.
  add_custom_target(check
    COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure
    DEPENDS ${CHECK_DEPENDS}
  )
endif()

# Install the different headers and libraries.
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/sjpeg.h
        DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(TARGETS sjpeg
        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
        ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})

# Create the CMake version file.
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
  "${CMAKE_CURRENT_BINARY_DIR}/sjpegConfigVersion.cmake"
  VERSION ${PROJECT_VERSION}
  COMPATIBILITY AnyNewerVersion
)

# Create the Config file.
set(ConfigPackageLocation ${CMAKE_INSTALL_DATADIR}/sjpeg/cmake/)
configure_package_config_file(
  ${CMAKE_CURRENT_SOURCE_DIR}/cmake/sjpegConfig.cmake.in
  ${CMAKE_CURRENT_BINARY_DIR}/sjpegConfig.cmake
  INSTALL_DESTINATION ${ConfigPackageLocation}
)

# Install the generated CMake files.
install(
  FILES "${CMAKE_CURRENT_BINARY_DIR}/sjpegConfigVersion.cmake"
        "${CMAKE_CURRENT_BINARY_DIR}/sjpegConfig.cmake"
  DESTINATION ${ConfigPackageLocation}
)

# Generate and install the pkg-config file. libsjpeg itself has no external
# library dependencies, so this only ever needs -lsjpeg: libjpeg/libpng are
# examples-only (see SJPEG_BUILD_EXAMPLES above).
configure_file(
  ${CMAKE_CURRENT_SOURCE_DIR}/cmake/sjpeg.pc.in
  ${CMAKE_CURRENT_BINARY_DIR}/sjpeg.pc
  @ONLY
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/sjpeg.pc
        DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)

################################################################################
# Man page.
if(SJPEG_BUILD_EXAMPLES)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/man/sjpeg.1
              ${CMAKE_CURRENT_SOURCE_DIR}/man/vjpeg.1
  DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
endif()
