# Macros to build the mitsuba targets. They are to be used by the CMake scripts
# only, otherwise they don't make any sense at all.

include (CMakeParseArguments)
include (CMakeDependentOption)
include (PCHTargets)

# Function to check that the assumed configurations exist
function (mts_check_configurations)
  if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
    message(WARNING "The build type is not set. Set the value of CMAKE_BUILD_TYPE.")
    return ()
  endif ()
  if (CMAKE_BUILD_TYPE)
    set (configs ${ARGN})
    list (FIND configs ${CMAKE_BUILD_TYPE} idx)
    if (idx LESS 0)
      message (AUTHOR_WARNING "Unexpected configuration '${CMAKE_BUILD_TYPE}' Check the value of CMAKE_BUILD_TYPE.")
    endif ()
    return ()
  endif ()
  set (configs "")
  foreach (cfg ${CMAKE_CONFIGURATION_TYPES})
    set (configs ${configs} ${cfg})
  endforeach ()
  set (myconfigs "")
  foreach (cfg ${ARGN})
    set (myconfigs ${myconfigs} ${cfg})
    list (FIND configs ${cfg} idx)
    if (idx LESS 0)
      message (AUTHOR_WARNING "The assumed configuration '${cfg}' is not available.")
    endif ()
  endforeach ()
  foreach (cfg ${configs})
    list (FIND myconfigs ${cfg} idx)
    if (idx LESS 0)
      message (AUTHOR_WARNING "Unexpected configuration '${cfg}' found.")
    endif ()
  endforeach ()
endfunction()

# Check the standard configurations
mts_check_configurations (Debug Release MinSizeRel RelWithDebInfo)


# Option to enable interprocedural optimizations
option(MTS_LTCG "Enable interprocedural optimizations on Release targets" ON)
mark_as_advanced (MTS_LTCG)

# Macro to enable interprocedural optimizations on a target
macro (mts_target_ltcg target)
  if (MTS_LTCG)
    set_target_properties (${target} PROPERTIES
      INTERPROCEDURAL_OPTIMIZATION_RELEASE 1
    )
  endif()
endmacro()


# Macro to enable parallel compilation on MSVC
macro (mts_msvc_mp target)
  if (MSVC_IDE AND MSVC_VERSION GREATER 1400)
    set_property (TARGET ${target} APPEND
      PROPERTY COMPILE_FLAGS " /MP")
  endif ()
endmacro()



# Option for precompiled headers
if (PCH_MSVC OR PCH_GCC)
  set (MTS_PCH_DEFAULT ON)
else ()
  set (MTS_PCH_DEFAULT OFF)
endif ()
CMAKE_DEPENDENT_OPTION (MTS_USE_PCH "Use precompiled headers (PCH)."
  ${MTS_PCH_DEFAULT} "PCH_SUPPORTED" OFF)
CMAKE_DEPENDENT_OPTION (MTS_USE_PCH_ALL_PLUGINS
  "Use PCH for all plugins irrespective of their number of source files."
  OFF "MTS_USE_PCH" OFF)
  
# Default project-wide header to be precompiled
set (MTS_DEFAULT_PCH "${PROJECT_SOURCE_DIR}/data/pch/mitsuba_precompiled.hpp")



# Function to configure the output path according to the configurations
# The output path configure expression, contained in "path_cfgstr" should
# have the placeholder @CFGNAME@ for proper substitution
function (SET_OUTPATH_CFG target_name property_suffix path_cfgstr)
  if (CMAKE_CONFIGURATION_TYPES)
    foreach (CFGNAME ${CMAKE_CONFIGURATION_TYPES})
      string (TOUPPER ${CFGNAME} cfg_upper)
      string (CONFIGURE "${path_cfgstr}" outpath @ONLY)
      set_target_properties (${target_name} PROPERTIES
        ${property_suffix}_${cfg_upper} "${outpath}")
    endforeach ()
  else ()
    set (CFGNAME ".")
    string (CONFIGURE "${path_cfgstr}" outpath @ONLY)
    set_target_properties (${target_name} PROPERTIES
        ${property_suffix} "${outpath}")
  endif ()
endfunction ()



# Function to set up the default windows resource file:
#  target_filename - where to write the configured file
#  name        - base name WITHOUT extension, eg "mitsuba"
#  extension   - file extension including the . ".exe"
#  description - File description to be presented to users
#  ICON <iconfile> - icon to be shown for executables
function(mts_win_resource target_filename name ext description)
  CMAKE_PARSE_ARGUMENTS(_res "" "ICON" "" ${ARGN})
  if (NOT WIN32)
    message(AUTHOR_WARNING "This is not a Windows build!")
  elseif(NOT MTS_VERSION)
    message(AUTHOR_WARNING "The mitsuba version variable is not set")
  endif()
  set(RC_FILE "${PROJECT_SOURCE_DIR}/data/windows/mitsuba_res.rc.in")
  
  #TODO Set up the VS_FF_PRERELEASE and VS_FF_PRIVATEBUILD flags adequately
  string(TOLOWER "${ext}" ext_lower)
  if(ext_lower STREQUAL ".dll")
    set(RC_FILETYPE "VFT_DLL")
  elseif(ext_lower STREQUAL ".exe")
    set(RC_FILETYPE "VFT_APP")
  elseif(ext_lower STREQUAL ".lib")
    set(RC_FILETYPE "VFT_STATIC_LIB")
  else()
    message(AUTHOR_WARNING "Unknown windows file type: ${ext_lower}")
    set(RC_FILETYPE "VFT_UNKNOWN")
  endif()
  
  if(_res_ICON)
    get_filename_component(_res_dir "${target_filename}" PATH)
    file(RELATIVE_PATH RC_ICON "${_res_dir}" "${_res_ICON}")
  else()
    set(RC_ICON "")
  endif()
  
  set(RC_DESCRIPTION "${description}")
  if (MTS_HAS_VALID_REV)
    set(RC_VERSION "${MTS_VERSION}-${MTS_VERSION_BUILD}hg${MTS_REV_ID}")
  else()
    set(RC_VERSION "${MTS_VERSION}")
  endif()
  set(RC_VERSION_COMMA "${MTS_VERSION_MAJOR},${MTS_VERSION_MINOR},${MTS_VERSION_PATCH},0")
  set(RC_FILENAME "${name}${ext}")
  set(RC_NAME "${name}")
  # MTS_DATE has the format YYYY.MM.DD
  string(SUBSTRING "${MTS_DATE}" 0 4 RC_YEAR)
  
  configure_file("${RC_FILE}" "${target_filename}" ESCAPE_QUOTES @ONLY)
endfunction()



# Constant with the bundle name for Mitsuba
set(MTS_BUNDLE_NAME "Mitsuba.app")
set(MTS_BUNDLE_RESOURCES "${MTS_BUNDLE_NAME}/Contents/Resources")

# Flag to use either simple or traditional Unix paths (e.g. share/mitsuba/...)
CMAKE_DEPENDENT_OPTION(MTS_SIMPLE_PATHS
  "Use a simple, Windows-like dir structure instead of the typical Unix one."
  ON "NOT WIN32; NOT APPLE" OFF)


# Constant with the destination for the Python bindings
if (WIN32 OR MTS_SIMPLE_PATHS)
  set(MTS_PYTHON_DEST "python")
elseif (APPLE)
  set(MTS_PYTHON_DEST "${MTS_BUNDLE_NAME}/python")
else()
  set(MTS_PYTHON_DEST "share/mitsuba/python")
endif()


# Constant with the destination for the core libraries
if (WIN32 OR MTS_SIMPLE_PATHS)
  set(MTS_LIB_DEST ".")
elseif (APPLE)
  set(MTS_LIB_DEST "${MTS_BUNDLE_NAME}/Contents/Frameworks")
else ()
  set(MTS_LIB_DEST "lib")
endif ()

# Macro to add a build target for a mitsuba core library.
#
# Usage:
#
# add_mts_corelib (name source1 [source2 ...]
#                   [LINK_LIBRARIES external_lib1 ...] )
#
# The plugin name is taken from the first argument. Additional libraries
# (for example, libpng) may be specified after the optionl LINK_LIBRARIES
# keyword.
#
# Each time this macro adds a target, it adds a new element to the
# variable "MTS_CORELIBS", which will contain all the generated core libs
#
macro (add_mts_corelib _corelib_name)
  CMAKE_PARSE_ARGUMENTS(_corelib "" "" "LINK_LIBRARIES" ${ARGN})
  set (_corelib_srcs ${_corelib_UNPARSED_ARGUMENTS})
  set(MTS_CORELIBS ${MTS_CORELIBS} ${_corelib_name} PARENT_SCOPE)
  
  if (WIN32)
    set(_corelib_res "${CMAKE_CURRENT_BINARY_DIR}/${_corelib_name}_res.rc")
    set(_corelib_description "Mitsuba core library: ${_corelib_name}")
    mts_win_resource("${_corelib_res}"
      "lib${_corelib_name}" ".dll" "${_corelib_description}")
    list(APPEND _corelib_srcs "${_corelib_res}")
  endif()
  
  if (MTS_USE_PCH)
    pch_add_library (${_corelib_name} SHARED
      PCH_HEADER "${MTS_DEFAULT_PCH}" ${_corelib_srcs})
  else ()
    add_library (${_corelib_name} SHARED ${_corelib_srcs})
  endif ()
  target_link_libraries (${_corelib_name} ${_corelib_LINK_LIBRARIES})
  if (WIN32)
    set_target_properties (${_corelib_name} PROPERTIES 
      PREFIX "lib"
      VERSION "${MTS_VERSION}")
  endif()
  if (WIN32)
    set (_corelib_property_suffix "RUNTIME_OUTPUT_DIRECTORY")
  else ()
    set (_corelib_property_suffix "LIBRARY_OUTPUT_DIRECTORY")
  endif ()
  SET_OUTPATH_CFG (${_corelib_name} ${_corelib_property_suffix}
    "${PROJECT_BINARY_DIR}/binaries/@CFGNAME@/${MTS_LIB_DEST}"
  )
  mts_target_ltcg (${_corelib_name})
  mts_msvc_mp (${_corelib_name})
  install(TARGETS ${_corelib_name}
    RUNTIME DESTINATION "${MTS_LIB_DEST}" COMPONENT Runtime
    LIBRARY DESTINATION "${MTS_LIB_DEST}" COMPONENT Runtime
    ARCHIVE DESTINATION "sdk/lib" COMPONENT Developer
  )
endmacro()



# Constant with the destination for the plugins
if (WIN32 OR MTS_SIMPLE_PATHS)
  set(MTS_PLUGIN_DEST "plugins")
elseif (APPLE)
  set(MTS_PLUGIN_DEST "${MTS_BUNDLE_NAME}/plugins")
else()
  set(MTS_PLUGIN_DEST "share/mitsuba/plugins")
endif()
  
# Macro to add a build target for a mitsuba plugin (based on the OIIO one).
#
# Usage:
#
# add_mts_plugin (name source1 [source2 ...]
#                 [LINK_LIBRARIES external_lib1 ...]
#                 [MTS_HW] [MTS_BIDIR]
#                 [NO_MTS_PCH]
#                 [TYPE plugin_type] )
#
# The plugin name is taken from the first argument and the
# source is automatically linked against mitsuba libraries.  Additional
# libraries (for example, libpng) may be specified after the optional
# LINK_LIBRARIES keyword.  NO_MTS_PCH makes the target not to use the 
# default mitsuba precompiled header (on supported platforms).
#
# By default the plugins are linked against mitsuba-core and mitsuba-render.
# When MTS_HW is set, the plugin will be linked against with mitsuba-hw. When
# MTS_BIDIR is specified, the plugin will also be linked against mitsuba-bidir.
# 
# The plugin type (i.e. camera, bsdf, luminaire) may be specified
# after the TYPE keyword. Currently doing this modifies the IDE project name
# in order to have a nicer organization.
#
macro (add_mts_plugin _plugin_name)
  CMAKE_PARSE_ARGUMENTS(_plugin "MTS_HW;MTS_BIDIR;NO_MTS_PCH"
    "TYPE" "LINK_LIBRARIES" ${ARGN})
  set (_plugin_srcs ${_plugin_UNPARSED_ARGUMENTS})
  
  if (WIN32)
    set(_plugin_res "${CMAKE_CURRENT_BINARY_DIR}/${_plugin_name}_res.rc")
    if (_plugin_TYPE)
      set(_plugin_description "Mitsuba ${_plugin_TYPE} plugin: ${_plugin_name}")
    else()
      set(_plugin_description "Mitsuba plugin: ${_plugin_name}")
    endif()
    mts_win_resource("${_plugin_res}"
      "${_plugin_name}" ".dll" "${_plugin_description}")
    list(APPEND _plugin_srcs "${_plugin_res}")
  endif()
  
  # Use the PCH only with the plugins with 2 or more source files
  set(_plugin_cxx_count "0")
  foreach(_src ${_plugin_srcs})
    if(_src MATCHES ".+\\.[cC][^.]*$")
      math(EXPR _plugin_cxx_count "${_plugin_cxx_count} + 1")
    endif()
  endforeach()
  
  if (NOT _plugin_NO_MTS_PCH AND MTS_USE_PCH AND
      (MTS_USE_PCH_ALL_PLUGINS OR _plugin_cxx_count GREATER 1))
    pch_add_library (${_plugin_name} MODULE
      PCH_HEADER "${MTS_DEFAULT_PCH}" ${_plugin_srcs})
  else ()
    add_library (${_plugin_name} MODULE ${_plugin_srcs})
  endif ()
  
  set(_plugin_core_libraries "mitsuba-core" "mitsuba-render")
  if (_plugin_MTS_HW)
    list(APPEND _plugin_core_libraries "mitsuba-hw")
  endif()
  if (_plugin_MTS_BIDIR)
    list(APPEND _plugin_core_libraries "mitsuba-bidir")
  endif()
  target_link_libraries (${_plugin_name} 
    ${_plugin_core_libraries} ${_plugin_LINK_LIBRARIES})
  
  set_target_properties (${_plugin_name} PROPERTIES PREFIX "")
  if (APPLE)
    set_target_properties (${_plugin_name} PROPERTIES SUFFIX ".dylib")
  endif ()
  if (WIN32)
    set_target_properties (${_plugin_name} PROPERTIES VERSION "${MTS_VERSION}")
  endif()
  set (_plugin_FOLDER "plugins")
  if (_plugin_TYPE)
    if (CMAKE_GENERATOR MATCHES "Visual Studio")
      set (_plugin_FOLDER "plugins/${_plugin_TYPE}")
    else()
      set_target_properties (${_plugin_name} PROPERTIES
        PROJECT_LABEL "${_plugin_TYPE}-${_plugin_name}")
    endif()
  endif()
  set_target_properties (${_plugin_name} PROPERTIES
    FOLDER ${_plugin_FOLDER})
  unset (_plugin_FOLDER)
  SET_OUTPATH_CFG (${_plugin_name} LIBRARY_OUTPUT_DIRECTORY
    "${PROJECT_BINARY_DIR}/binaries/@CFGNAME@/${MTS_PLUGIN_DEST}"
  )
  mts_target_ltcg (${_plugin_name})
  mts_msvc_mp (${_plugin_name})
  install(TARGETS ${_plugin_name}
    RUNTIME DESTINATION ${MTS_PLUGIN_DEST} COMPONENT Runtime
    LIBRARY DESTINATION ${MTS_PLUGIN_DEST} COMPONENT Runtime)
endmacro ()



# Constant with the executables destination
if (WIN32 OR MTS_SIMPLE_PATHS)
  set (MTS_EXE_DEST ".")
elseif (APPLE)
  set (MTS_EXE_DEST "${MTS_BUNDLE_NAME}/Contents/MacOS")
else()
  set (MTS_EXE_DEST "bin")
endif()

# Macro to add a build target for a mitsuba application.
#
# Usage:
#
# add_mts_exe (name [WIN32] source1 [source2 ...]
#                   [LINK_LIBRARIES external_lib1 ...]
#                   [RES_ICON filename]
#                   [RES_DESCRIPTION "Description string"]
#                   [NO_INSTALL]
#                   [MTS_HW] [MTS_BIDIR]
#                   [NO_MTS_PCH | PCH pch_header] )
#
# The executable name is taken from the first argument. The target gets
# automatically linked against mitusba's core libraries, as defined
# in the "MTS_CORELIBS" variable Additional libraries
# (for example, libpng) may be specified after the optionl LINK_LIBRARIES
# keyword.
#
# By default the executables are linked against mitsuba-core and mitsuba-render.
# When MTS_HW is set, the executable will be linked against with mitsuba-hw.
# When MTS_BIDIR is specified, the executable will also be linked against 
# mitsuba-bidir.
#
# The optional keyword WIN32, if presents, gets passed to add_executable(...)
# to produce a Windows executable using winmain, thus it won't have a
# console. The NO_INSTALL keyword causes the target not to be installed.
# NO_MTS_PCH makes the target not to use the default mitsuba precompiled header
# (on supported platforms). PCH specifies a custom precompiler header to use
# which is more suitable for the application.
#
# The optional RES_ICON parameter specified an icon to be bundled into the
# executable. This only works on Windows builds. The optional RES_DESCRIPTION
# parameters sets a specific executable description to be used in the Windows
# builds; other platforms simply ignore this value as with RES_ICON.
#
macro (add_mts_exe _exe_name)
  CMAKE_PARSE_ARGUMENTS(_exe "WIN32;NO_INSTALL;MTS_HW;MTS_BIDIR;NO_MTS_PCH"
    "PCH;RES_ICON;RES_DESCRIPTION" "LINK_LIBRARIES" ${ARGN})
  set (_exe_srcs ${_exe_UNPARSED_ARGUMENTS})
  if (_exe_WIN32)
    set(_exe_TYPE WIN32)
  endif()
  
  if (WIN32)
    set(_exe_res "${CMAKE_CURRENT_BINARY_DIR}/${_exe_name}_res.rc")
    set(_exe_res_args "${_exe_res}" "${_exe_name}" ".exe")
    if (_exe_RES_DESCRIPTION)
      set(_exe_description "${_exe_RES_DESCRIPTION}")
    else()
      set(_exe_description "Mitsuba application: ${_exe_name}")
    endif()
    list(APPEND _exe_res_args "${_exe_description}")
    if (_exe_RES_ICON)
      list(APPEND _exe_res_args "ICON" "${_exe_RES_ICON}")
    endif()
    mts_win_resource(${_exe_res_args})
    list(APPEND _exe_srcs "${_exe_res}")
  endif()
  
  if (MTS_USE_PCH AND (NOT _exe_NO_MTS_PCH OR _exe_PCH))
    set (_exe_pch_header "${MTS_DEFAULT_PCH}")
    if (_exe_PCH)
      set (_exe_pch_header "${_exe_PCH}")
      if (_exe_NO_MTS_PCH)
        message (AUTHOR_WARNING
	  "'NO_MTS_PCH' ignored due to 'PCH ${_exe_PCH}'.")
      endif ()
    endif ()
    pch_add_executable (${_exe_name} ${_exe_TYPE}
      PCH_HEADER "${_exe_pch_header}" ${_exe_srcs})
  else ()
    add_executable (${_exe_name} ${_exe_TYPE} ${_exe_srcs})
  endif ()

  set(_exe_core_libraries "mitsuba-core" "mitsuba-render")
  if (_exe_MTS_HW)
    list(APPEND _exe_core_libraries "mitsuba-hw")
  endif()
  if (_exe_MTS_BIDIR)
    list(APPEND _exe_core_libraries "mitsuba-bidir")
  endif()
  target_link_libraries (${_exe_name} ${_exe_core_libraries} ${_exe_LINK_LIBRARIES})
  if (WIN32)
    set_target_properties (${_exe_name} PROPERTIES VERSION "${MTS_VERSION}")
  endif()
  set_target_properties (${_exe_name} PROPERTIES FOLDER "apps")
  SET_OUTPATH_CFG (${_exe_name} RUNTIME_OUTPUT_DIRECTORY
    "${PROJECT_BINARY_DIR}/binaries/@CFGNAME@/${MTS_EXE_DEST}"
  )
  mts_target_ltcg (${_exe_name})
  mts_msvc_mp (${_exe_name})
  if (NOT _exe_NO_INSTALL)
    install(TARGETS ${_exe_name}
      RUNTIME DESTINATION ${MTS_EXE_DEST} COMPONENT Runtime)
  endif()
endmacro()



# Constant with the headers destination
if (WIN32 OR MTS_SIMPLE_PATHS)
  set (MTS_HEADER_DEST "sdk/include")
elseif (APPLE)
  set (MTS_HEADER_DEST "${MTS_BUNDLE_NAME}/Headers")
else()
  set (MTS_HEADER_DEST "include")
endif()

# Macro to install header files. The FOLDER option specifies a subdirectory
# on which the given headers will be installed. Usage:
#  mts_install_headers(header1 header2 ... [FOLDER subdir])
macro (mts_install_headers)
  CMAKE_PARSE_ARGUMENTS(_hdrs "" "FOLDER" "" ${ARGN})
  set (_hdrs_files ${_hdrs_UNPARSED_ARGUMENTS})
  if (NOT _hdrs_FOLDER)
    set (_hdrs_FOLDER ".")
  endif ()
  install (FILES ${_hdrs_files}
    PERMISSIONS "OWNER_READ" "GROUP_READ" "WORLD_READ"
    DESTINATION "${MTS_HEADER_DEST}/${_hdrs_FOLDER}"
    COMPONENT Developer)
endmacro ()



# Function to get a list of paths contained in variables which match the regex
# "LIBRAR(Y|IES)$". This is intended to be used with the FIXUP_BUNDLE macro.
# Example usage:
#  mts_library_paths (paths)
#  message (${paths})
function (mts_library_paths outvar_name)
  set (library_paths "")
  get_cmake_property (variables VARIABLES)
  foreach (var ${variables})
    if (var MATCHES "LIBRAR(Y|IES)$")
      foreach (path ${${var}})
        get_filename_component (libpath "${path}" PATH)
        if (libpath AND EXISTS "${libpath}")
          get_filename_component (libpath "${libpath}" REALPATH)
          set (library_paths ${library_paths} ${libpath})
        endif ()
      endforeach ()
    endif ()
  endforeach ()
  list (REMOVE_DUPLICATES library_paths)

  # Try to add the "bin" directories that might exist as well
  set (bin_paths "")
  if (WIN32)
    foreach (libpath ${library_paths})
      if (EXISTS "${libpath}/../bin")
        get_filename_component (binpath "${libpath}/../bin" REALPATH)
        set (bin_paths ${bin_paths} ${binpath})
      endif ()
      if (EXISTS "${libpath}/bin")
        get_filename_component (binpath "${libpath}/bin" REALPATH)
        set (bin_paths ${bin_paths} ${binpath})
      endif ()
    endforeach ()
  endif ()
  list (REMOVE_DUPLICATES bin_paths)
  set (${outvar_name} ${bin_paths} ${library_paths} PARENT_SCOPE)
endfunction ()