#- Use module for Vala
# Provides functions to compile Vala and Genie sources. It is assumed that
# FindVala.cmake has already been loaded. Refer to FindVala.cmake for
# information on how to use Vala in your CMake project.

#=============================================================================
# Copyright 2009-2010 Michael Wild, Kitware Inc.
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# (To distributed this file outside of CMake, substitute the full
#  License text for the above reference.)

include(ParseArguments)
macro(ensure_vala_version version)
    parse_arguments(ARGS "" "MINIMUM;MAXIMUM;EXACT" ${ARGN})
	set(compare_message "")
	set(error_message "")
	if(ARGS_MINIMUM)
		set(compare_message "a minimum ")
		set(error_message "or greater ")
	elseif(ARGS_MAXIMUM)
		set(compare_message "a maximum ")
		set(error_message "or less ")
	endif(ARGS_MINIMUM)

	message(STATUS
		"checking for ${compare_message}Vala version of ${version}"
	)

	unset(version_accepted)

	# MINIMUM is the default if no option is specified
	if(ARGS_EXACT)
		if(${VALA_VERSION} VERSION_EQUAL ${version} )
			set(version_accepted TRUE)
		endif(${VALA_VERSION} VERSION_EQUAL ${version})
	elseif(ARGS_MAXIMUM)
		if(${VALA_VERSION} VERSION_LESS ${version} OR ${VALA_VERSION} VERSION_EQUAL ${version})
			set(version_accepted TRUE)
		endif(${VALA_VERSION} VERSION_LESS ${version} OR ${VALA_VERSION} VERSION_EQUAL ${version})
	else(ARGS_MAXIMUM)
		if(${VALA_VERSION} VERSION_GREATER ${version} OR ${VALA_VERSION} VERSION_EQUAL ${version})
			set(version_accepted TRUE)
		endif(${VALA_VERSION} VERSION_GREATER ${version} OR ${VALA_VERSION} VERSION_EQUAL ${version})
	endif(ARGS_EXACT)

	if (NOT version_accepted)
		message(FATAL_ERROR
			"Vala version ${version} ${error_message}is required."
		)
	endif(NOT version_accepted)
endmacro(ensure_vala_version)

# target properties
define_property(TARGET PROPERTY PUBLIC_VAPI_FILE
  BRIEF_DOCS ".vapi file generated by either GENERATE_VAPI or LIBRARY option."
  FULL_DOCS "If a Vala target is created with GENERATE_VAPI or the LIBRARY "
  "option, the location of the generated public .vapi file is stored in this "
  "property.")

define_property(TARGET PROPERTY INTERNAL_VAPI_FILE
  BRIEF_DOCS ".vapi file generated by the GENERATE_INTERNAL_VAPI option."
  FULL_DOCS "If a Vala target is created with GENERATE_INTERNAL_VAPI option, "
  "the location of the generated internal .vapi file is stored in this "
  "property.")

define_property(TARGET PROPERTY VAPI_FILES
  BRIEF_DOCS ".vapi files required to link against this library."
  FULL_DOCS "Names the .vapi file describing the C GLib/GObject interface "
  "defined by a Vala library in the Vala language such that it can be used "
  "by other Vala code and all other .vapi files this library depends on.")

define_property(TARGET PROPERTY VALA_PACKAGE_DEPENDENCIES
  BRIEF_DOCS "Dependencies from Vala targets used with PACKAGES"
  FULL_DOCS "Target names of Vala libraries that have been passed using the "
  "PACKAGES option of vala_precompile, vala_add_executable or "
  "vala_add_library on which this target depends. This property is "
  "inherited by other targets that list this target in their "
  "PACKAGES option.")

define_property(TARGET PROPERTY VALA_C_SOURCES
  BRIEF_DOCS "The generated C-sources"
  FULL_DOCS "The functions vala_add_executable and vala_add_library store in "
  "this property the paths of the generated C-sources as returned by "
  "vala_precompile.")

# include directories
include_directories(${VALA_INCLUDE_DIRS})

# compiler flags
set(VALA_COMPILE_FLAGS "" CACHE STRING "Flags used by the Vala compiler")
set(VALA_COMPILE_FLAGS_DEBUG "-g" CACHE STRING
  "Flags used by the Vala compiler during debug builds")
set(VALA_COMPILE_FLAGS_MINSIZEREL "" CACHE STRING
  "Flags used by the Vala compiler during release minsize builds")
set(VALA_COMPILE_FLAGS_RELEASE "" CACHE STRING
  "Flags used by the Vala compiler during release builds")
set(VALA_COMPILE_FLAGS_RELWITHDEBINFO "-g" CACHE STRING
  "Flags used by the Vala compiler during release with Debug Info builds")
mark_as_advanced(VALA_COMPILE_FLAGS VALA_COMPILE_FLAGS_DEBUG
  VALA_COMPILE_FLAGS_MINSIZEREL VALA_COMPILE_FLAGS_RELEASE
  VALA_COMPILE_FLAGS_RELWITHDEBINFO)

function(vala_precompile target outvar)
  set(in_files)
  set(out_files)
  set(result)
  set(vala_pkg_opts)
  set(depends)
  set(depends_vapis)
  set(vapi_arguments)
  set(header_arguments)
  set(valid_args
    PACKAGES LIBRARY COMPILE_FLAGS VAPI_DIRS GENERATE_HEADER
    GENERATE_INTERNAL_HEADER GENERATE_VAPI GENERATE_INTERNAL_VAPI CUSTOM_VAPIS
    BASE_DIR OUTPUT_DIR COMMENT
    )
  _vala_parse_arguments(ARGS "${valid_args}" "" ${ARGN})

  # deal with PACKAGES
  foreach(pkg ${ARGS_PACKAGES})
    # if it is a target, the user wants us to do things automagically
    if(TARGET "${pkg}")
      # add the target and its own dependencies to the list depends
      list(APPEND depends ${pkg})
      get_target_property(pkg_depends ${pkg} VALA_PACKAGE_DEPENDENCIES)
      if(pkg_depends)
        list(APPEND depends ${pkg_depends})
      endif()
    else()
      # otherwise just put --pkg
      list(APPEND vala_pkg_opts "--pkg=${pkg}")
    endif()
  endforeach()
  # get the vapis of the dependencies
  foreach(d ${depends})
    get_target_property(vapi_file ${d} VAPI_FILES)
    if(vapi_file)
      list(APPEND depends_vapis "${vapi_file}")
    endif()
  endforeach()

  # deal with OUTPUT_DIR (make it absolute if its not) and ensure it exists
  if(NOT ARGS_OUTPUT_DIR)
    set(${ARGS_OUTPUT_DIR} "${CMAKE_CURRENT_BINARY_DIR}")
  endif()
  if(NOT IS_ABSOLUTE "${ARGS_OUTPUT_DIR}")
    set(ARGS_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/${ARGS_OUTPUT_DIR}")
  endif()
  if(NOT IS_DIRECTORY "${ARGS_OUTPUT_DIR}")
    file(MAKE_DIRECTORY "${ARGS_OUTPUT_DIR}")
  endif()

  # deal with LIBRARY. if GENERATE_VAPI is not specified, add it here
  if(ARGS_LIBRARY)
    list(APPEND VALA_COMPILE_FLAGS "--library=${ARGS_LIBRARY}")
    if(NOT ARGS_GENERATE_VAPI)
      set(ARGS_GENERATE_VAPI "${ARGS_LIBRARY}.vapi")
    endif()
  endif()

  # deal with CUSTOM_VAPIS. ensure every vapi shows up only once
  set(vapi_files ${ARGS_CUSTOM_VAPIS} ${depends_vapis})
  if(vapi_files)
    list(REMOVE_DUPLICATES vapi_files)
  endif()

  # deal with COMPILE_FLAGS
  if(ARGS_COMPILE_FLAGS)
    list(APPEND VALA_COMPILE_FLAGS "${ARGS_COMPILE_FLAGS}")
  endif()

  # deal with VAPI_DIRS
  if(ARGS_VAPI_DIRS)
    set(VALA_VAPI_DIRS "${ARGS_VAPI_DIRS}")
  endif()
  set(vala_vapidir_opts)
  foreach(vapidir ${VALA_VAPI_DIRS})
    list(APPEND vala_vapidir_opts "--vapidir=${vapidir}")
  endforeach()

  # deal with BASE_DIR and make it absolute
  if(NOT ARGS_BASE_DIR)
    set(ARGS_BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
  endif()
  if(NOT IS_ABSOLUTE "${ARGS_BASE_DIR}")
    set(ARGS_BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${ARGS_BASE_DIR}")
  endif()

  # treat source files
  foreach(src ${ARGS_DEFAULT_ARGS})
    if(NOT IS_ABSOLUTE "${src}")
      get_filename_component(src "${src}" ABSOLUTE)
    endif()
    list(APPEND in_files "${src}")
    file(RELATIVE_PATH out_file "${ARGS_BASE_DIR}" "${src}")
    string(REGEX REPLACE "\\.(vala|gs)$" ".c" out_file
      "${ARGS_OUTPUT_DIR}/${CMAKE_CFG_INTDIR}/${out_file}")
    # remove back-references
    get_filename_component(out_file "${out_file}" ABSOLUTE)
    list(APPEND out_files "${out_file}")
    list(APPEND result "${out_file}")
  endforeach()

  # deal with GENERATE_INTERNAL_VAPI (make absolute if necessary and create
  # destination directory)
  if(ARGS_GENERATE_INTERNAL_VAPI)
    # Header and internal header is needed to generate internal vapi
    if (NOT ARGS_GENERATE_HEADER)
      set(ARGS_GENERATE_HEADER ${ARGS_GENERATE_VAPI})
    endif()
    if (NOT ARGS_GENERATE_INTERNAL_HEADER)
      set(ARGS_GENERATE_INTERNAL_HEADER ${ARGS_GENERATE_HEADER}_internal)
    endif()
    if(NOT IS_ABSOLUTE "${ARGS_GENERATE_INTERNAL_VAPI}")
      set(ARGS_GENERATE_INTERNAL_VAPI
        "${ARGS_OUTPUT_DIR}/${ARGS_GENERATE_INTERNAL_VAPI}")
    endif()
    get_filename_component(_genvapidir "${ARGS_GENERATE_INTERNAL_VAPI}" PATH)
    list(APPEND out_files "${ARGS_GENERATE_INTERNAL_VAPI}")
    set(vapi_arguments "--internal-vapi=${ARGS_GENERATE_INTERNAL_VAPI}")
  endif()

  # the same for GENERATE_VAPI
  if(ARGS_GENERATE_VAPI)
    if(NOT IS_ABSOLUTE "${ARGS_GENERATE_VAPI}")
      set(ARGS_GENERATE_VAPI "${ARGS_OUTPUT_DIR}/${ARGS_GENERATE_VAPI}")
    endif()
    list(APPEND out_files "${ARGS_GENERATE_VAPI}")
    set(vapi_arguments "--vapi=${ARGS_GENERATE_VAPI}")
  endif()

  # deal with GENERATE_HEADER, making absolute and creating output dir
  if(ARGS_GENERATE_HEADER)
    if(NOT IS_ABSOLUTE "${ARGS_GENERATE_HEADER}")
      set(ARGS_GENERATE_HEADER "${ARGS_OUTPUT_DIR}/${ARGS_GENERATE_HEADER}")
    endif()
    list(APPEND out_files "${ARGS_GENERATE_HEADER}")
    list(APPEND header_arguments "--header=${ARGS_GENERATE_HEADER}")
    list(APPEND result "${ARGS_GENERATE_HEADER}")
  endif()

  # the same for GENERATE_INTERNAL_HEADER
  if(ARGS_GENERATE_INTERNAL_HEADER)
    if(NOT IS_ABSOLUTE "${ARGS_GENERATE_INTERNAL_HEADER}")
      set(ARGS_GENERATE_INTERNAL_HEADER
        "${ARGS_OUTPUT_DIR}/${CMAKE_CFG_INTDIR}/${ARGS_GENERATE_INTERNAL_HEADER}")
    endif()
    list(APPEND out_files "${ARGS_GENERATE_INTERNAL_HEADER}")
    list(APPEND header_arguments
      "--internal-header=${ARGS_GENERATE_INTERNAL_HEADER}")
    list(APPEND result "${ARGS_GENERATE_INTERNAL_HEADER}")
  endif()

  # allow override of COMMENT
  if(NOT ARGS_COMMENT)
    set(ARGS_COMMENT "Precompiling Vala target ${target}")
  endif()

  # create a .cmake file to drive the precompiling
  # (sadly, this is required for multi-configuration IDE's to support
  # VALA_COMPILE_FLAGS_<BUILD_TYPE> flags)
  set(driver_file
    "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${target}.dir/precompile.cmake")
  if(CMAKE_CONFIGURATION_TYPES)
    set(config_types ${CMAKE_CONFIGURATION_TYPES})
  else()
    set(config_types Debug Release RelWithDebInfo MinSizeRel ${CMAKE_BUILD_TYPE})
    list(REMOVE_DUPLICATES config_types)
  endif()
  set(vala_config_flags)
  foreach(c IN LISTS config_types)
    string(TOUPPER ${c} cc)
    set(v VALA_COMPILE_FLAGS_${cc})
    set(vala_config_flags "${vala_config_flags}set(${v} \"${${v}}\")\n")
  endforeach()

  set(command
    "\${VALA_COMPILER}" -C
    ${header_arguments}
    ${vapi_arguments}
    -b "${ARGS_BASE_DIR}"
    -d "${ARGS_OUTPUT_DIR}/\${CMAKE_CFG_INTDIR}"
    ${vala_vapidir_opts}
    ${vala_pkg_opts}
    @VALA_COMPILE_FLAGS@
    ${in_files}
    ${vapi_files}
    )
  configure_file("${VALA_CMAKE_DIR}/ValaDriver.cmake.in"
    "${driver_file}" @ONLY)

  # create the actual command to precompile (don't list out_files in the OUTPUT
  # but use a stamp file instead, because valac only updates changed files)
  set(stamp_file
    "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${target}.dir/precompile.stamp")
  if(NOT CMAKE_CFG_INTDIR STREQUAL .)
    set(stamp_file "${stamp_file}.${CMAKE_CFG_INTDIR}")
  endif()
  if(CMAKE_GENERATOR MATCHES "Makefiles$")
    set(verbose "$(VERBOSE)")
  else()
    set(verbose 1)
  endif()
  add_custom_command(OUTPUT ${stamp_file}
    COMMAND "${CMAKE_COMMAND}"
      -DVALA_COMPILER:FILEPATH=${VALA_COMPILER}
      -DCMAKE_CFG_INTDIR:STRING=${CMAKE_CFG_INTDIR}
      -DVERBOSE:STRING=${verbose}
      -P "${driver_file}"
    COMMAND "${CMAKE_COMMAND}" -E touch "${stamp_file}"
    DEPENDS "${VALA_COMPILER}" ${in_files} ${ARGS_CUSTOM_VAPIS} "${driver_file}"
    WORKING_DIRECTORY "${ARGS_OUTPUT_DIR}"
    COMMENT "${ARGS_COMMENT}"
    VERBATIM
    )

  # mark the products as GENERATED and for automatic cleaning
  set_source_files_properties(${out_files} PROPERTIES GENERATED TRUE)
  set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY
    ADDITIONAL_MAKE_CLEAN_FILES ${out_files})

  # add custom target to depend on
  add_custom_target(${target} DEPENDS ${stamp_file})
  # have it depend on the targets in PACKAGES and their dependencies
  if(depends)
    add_dependencies(${target} ${depends})
  endif()
  # set our own dependencies property
  set_property(TARGET ${target} APPEND PROPERTY
    VALA_PACKAGE_DEPENDENCIES ${depends})
  # add the internal vapi to the list of vapis this library requires (if it is one)
  if(ARGS_GENERATE_INTERNAL_VAPI)
    list(APPEND vapi_files "${ARGS_GENERATE_INTERNAL_VAPI}")
  elseif(ARGS_GENERATE_VAPI)
    # otherwise use the public vapi
    list(APPEND vapi_files "${ARGS_GENERATE_VAPI}")
  endif()
  # set the properties describing this Vala target
  set_target_properties(${target} PROPERTIES
    VAPI_FILES "${vapi_files}"
    PUBLIC_VAPI_FILE "${ARGS_GENERATE_VAPI}"
    INTERNAL_VAPI_FILE "${ARGS_GENERATE_INTERNAL_VAPI}"
    )
  # propagate generated sources and headers to caller scope
  set(${outvar} "${result}" PARENT_SCOPE)
endfunction()

function(vala_add_executable name)
  set(valid_args PACKAGES VAPI_DIRS CUSTOM_VAPIS BASE_DIR COMPILE_FLAGS
    OUTPUT_DIR COMMENT)
  _vala_parse_arguments(ARGS "${valid_args}" "" ${ARGN})
  # repack the arguments
  set(ARGS)
  foreach(v ${valid_args})
    if(ARGS_${v})
      list(APPEND ARGS ${v} "${ARGS_${v}}")
    endif()
  endforeach()
  # precompile
  vala_precompile(${name}_precompile C_SOURCES
    ${ARGS_DEFAULT_ARGS} ${ARGS})
  # add the executable target
  add_executable(${name} ${C_SOURCES})
  # declare the dependency and inherit the custom properties
  vala_add_dependencies(${name} ${name}_precompile)
  # set the VALA_C_SOURCES property
  set_target_properties(${name} PROPERTIES
    VALA_C_SOURCES "${C_SOURCES}")
  # link against VALA_LIBRARIES
  target_link_libraries(${name} ${VALA_LIBRARIES})
endfunction()

function(vala_add_library name type)
  set(valid_args LIBRARY PACKAGES VAPI_DIRS CUSTOM_VAPIS GENERATE_VAPI
    GENERATE_HEADER GENERATE_INTERNAL_HEADER BASE_DIR COMPILE_FLAGS OUTPUT_DIR
    COMMENT)
  _vala_parse_arguments(ARGS "${valid_args}" "" ${ARGN})
  # repack the arguments
  set(ARGS)
  foreach(v ${valid_args})
    if(ARGS_${v})
      list(APPEND ARGS ${v} "${ARGS_${v}}")
    endif()
  endforeach()
  # deal with type being a source file
  if(NOT type MATCHES "^(STATIC|SHARED|MODULE)")
    list(INSERT ARGS_DEFAULT_ARGS 0 "${type}")
    set(type)
  endif()
  # deal with LIBRARY
  set(library_args)
  if(NOT ARGS_LIBRARY)
    set(library_args LIBRARY ${name})
  endif()
  # precompile
  vala_precompile(${name}_precompile C_SOURCES
    ${ARGS_DEFAULT_ARGS}
    ${library_args} ${ARGS})
  # add the library target
  add_library(${name} ${type} ${C_SOURCES})
  # declare dependency and inherit custom properties
  vala_add_dependencies(${name} ${name}_precompile)
  # inherit library specific properties
  get_target_property(vapi ${name} PUBLIC_VAPI)
  get_target_property(ivapi ${name} INTERNAL_VAPI)
  set_target_properties(${name} PROPERTIES
    VALA_C_SOURCES "${C_SOURCES}"
    PUBLIC_VAPI "${vapi}"
    INTERNAL_VAPI "${ivapi}"
    )
  # link against VALA_LIBRARIES
  target_link_libraries(${name} ${VALA_LIBRARIES})
endfunction()

function(vala_add_dependencies target)
  # inherit custom properties
  set(vapi_files)
  set(pkg_deps)
  foreach(dep ${ARGN})
    if(TARGET ${dep})
      get_property(is_vala_target TARGET ${dep} PROPERTY VAPI_FILES SET)
      if(is_vala_target)
        get_target_property(v ${dep} VAPI_FILES)
        get_target_property(p ${dep} VALA_PACKAGE_DEPENDENCIES)
        list(APPEND vapi_files ${v})
        list(APPEND pkd_deps ${p})
      endif()
    else()
      message(SEND_ERROR "Dependency ${dep} is not a top-level target")
    endif()
  endforeach()
  set_property(TARGET ${target} APPEND PROPERTY VAPI_FILES ${vapi_files})
  set_property(TARGET ${target} APPEND PROPERTY
    VALA_PACKAGE_DEPENDENCIES ${pkg_deps})
  add_dependencies(${target} ${ARGN})
endfunction()

##
# This is a helper Macro to parse optional arguments in Macros/Functions. It
# has been taken from the public CMake wiki. See
# http://www.cmake.org/Wiki/CMakeMacroParseArguments for documentation and
# licensing.
##
function(_vala_parse_arguments prefix arg_names option_names)
  # initialize
  set(DEFAULT_ARGS)
  foreach(arg_name ${arg_names})
    set(result_${arg_name})
  endforeach()
  foreach(option ${option_names})
    set(result_${option} FALSE)
  endforeach()
  # parse
  set(current_arg_name DEFAULT_ARGS)
  set(current_arg_list)
  foreach(arg ${ARGN})
    set(larg_names ${arg_names})
    list(FIND larg_names "${arg}" is_arg_name)
    if(is_arg_name GREATER -1)
      set(result_${current_arg_name} ${current_arg_list})
      set(current_arg_name ${arg})
      set(current_arg_list)
    else(is_arg_name GREATER -1)
      set(loption_names ${option_names})
      list(FIND loption_names "${arg}" is_option)
      if(is_option GREATER -1)
            set(result_${arg} TRUE)
      else(is_option GREATER -1)
            set(current_arg_list ${current_arg_list} ${arg})
      endif()
    endif()
  endforeach()
  set(result_${current_arg_name} ${current_arg_list})
  # propagate to caller scope
  foreach(arg_name DEFAULT_ARGS ${arg_names})
    set(${prefix}_${arg_name} ${result_${arg_name}} PARENT_SCOPE)
  endforeach()
  foreach(option ${option_names})
    set(${prefix}_${option} ${result_${option}} PARENT_SCOPE)
  endforeach()
endfunction()
