# Functions and macros to generate upgrade and downgrade scripts

# concatenate_files(<output> <file> ...)
#
# Concatenate a list of files into <output>
function(concatenate_files OUTPUT_FILE FIRST_FILE)
  file(READ ${FIRST_FILE} _contents)
  file(WRITE ${OUTPUT_FILE} "${_contents}")
  foreach(_file ${ARGN})
    file(READ ${_file} _contents)
    file(APPEND ${OUTPUT_FILE} "${_contents}")
  endforeach()
endfunction()

# generate_script(...)
#
# Generate a script file and install it into the script directory.
#
# VERSION <version> Version to use for shared libraryx.
#
# SCRIPT <file> File name for script file.
#
# OUTPUT_DIRECTORY <dir> Directory for where script file should be written.
# Defaults to CMAKE_CURRENT_BINARY_DIR.
#
# FILES <file> ... List of files to include, in order, in the script file.
function(generate_script)
  set(options)
  set(oneValueArgs VERSION SCRIPT OUTPUT_DIRECTORY)
  set(multiValueArgs FILES)
  cmake_parse_arguments(_generate "${options}" "${oneValueArgs}"
                        "${multiValueArgs}" ${ARGN})

  if(NOT _generate_OUTPUT_DIRECTORY)
    set(_generate_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
  endif()

  # Set the necessary variables, generate the real files, and append the output
  # to the final update script. Ideally, the template files should have a '.in'
  # suffix so that we can keep the template file and the final file available
  # for debugging, but for some reason it was decided to not use the '.in'
  # suffix for template files so now we're stuck with it.
  set(MODULE_PATHNAME "$libdir/timescaledb-${_generate_VERSION}")
  set(LOADER_PATHNAME "$libdir/timescaledb")

  # Process all files. They are template files and should end with '.in' but for
  # some reason they do not, so we append '.gen' instead to indicate generated
  # files.
  set(_result_files)
  foreach(_file ${_generate_FILES})
    configure_file(${_file} ${_file}.gen @ONLY)
    list(APPEND _result_files ${_file}.gen)
  endforeach()

  # Concatenate the real files into the update file.
  message(STATUS "Generating script ${_generate_SCRIPT}")
  concatenate_files(${_generate_OUTPUT_DIRECTORY}/${_generate_SCRIPT}
                    ${_result_files})

  install(FILES ${_generate_OUTPUT_DIRECTORY}/${_generate_SCRIPT}
          DESTINATION "${PG_SHAREDIR}/extension")
endfunction()

# generate_downgrade_script(<options>)
#
# Create a downgrade script from a source version to a target
# version. To figure out what files are necessary, the
# ScriptFiles.cmake manifest is read from the target version. If that
# file does not exist in the target version, the manifest in the
# current version is used, but it is assumed that files are only
# added. This situation only occur in the first version where we start
# to generate downgrade scripts and in this case files were only
# added, so we can safely ignore them.
#
# SOURCE_VERSION <version>
#
# Version to generate downgrade script from.
#
# TARGET_VERSION <version>
#
# Version to generate downgrade script to.
#
# OUTPUT_DIRECTORY <dir>
#
# Output directory for script file. Defaults to CMAKE_CURRENT_BINARY_DIR.
#
# INPUT_DIRECTORY <dir>
#
# Input directory for downgrade files. Defaults to CMAKE_CURRENT_SOURCE_DIR.
#
# FILES <file> ...
#
# Files to include, in order, when generating the downgrade script.
#
# The downgrade script is generated by creating a new file of the
# format "timescaledb--<current>--<version>.sql" consisting of the
# sequence of files:
#
# 1. Generated prolog from the target version
# 2. Generated downgrade files from the source version
# 3. Generated epilog from the target version
#
# The files provided are assumed to be configure templates and configure_files
# will be run on them with the following variables set:
#
# LOADER_PATHNAME: Pathname to loader shared library. This is the same as in the
# update.
#
# MODULE_PATHNAME: Pathname to timescale extension of the version being dowgraded
# to. This can be used to load "old" functions from the correct library.
function(generate_downgrade_script)
  set(options)
  set(oneValueArgs SOURCE_VERSION TARGET_VERSION OUTPUT_DIRECTORY
                   INPUT_DIRECTORY FILES)
  cmake_parse_arguments(_downgrade "${options}" "${oneValueArgs}"
                        "${multiValueArgs}" ${ARGN})

  if(NOT _downgrade_OUTPUT_DIRECTORY)
    set(_downgrade_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
  endif()

  if(NOT _downgrade_INPUT_DIRECTORY)
    set(_downgrade_INPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
  endif()

  foreach(_downgrade_file ${_downgrade_FILES})
    if(NOT EXISTS ${_downgrade_INPUT_DIRECTORY}/${_downgrade_file})
      message(FATAL_ERROR "No downgrade file ${_downgrade_file} found!")
    endif()
  endforeach()

  # Fetch manifest with list of files for the prolog and epilog from
  # the target version, if we are in a version that supports
  # downgrades.  Otherwise, take the one in the current version.
  #
  # We have a specific exception where we allow a missing manifest for
  # the first version that supports downgrades and assume that the
  # files to include are the same in the target version as the current
  # one.
  if(_downgrade_TARGET_VERSION VERSION_GREATER 2.3)
    git_versioned_get(VERSION ${_downgrade_TARGET_VERSION} FILES
                      ${CMAKE_SOURCE_DIR}/cmake/ScriptFiles.cmake)
  else()
    file(MAKE_DIRECTORY
         "${CMAKE_BINARY_DIR}/v${_downgrade_TARGET_VERSION}/cmake")
    file(COPY "${CMAKE_SOURCE_DIR}/cmake/ScriptFiles.cmake"
         DESTINATION "${CMAKE_BINARY_DIR}/v${_downgrade_TARGET_VERSION}/cmake")
  endif()

  # This will include the variables in this scope, but not in the parent scope
  # so we can read them locally without affecting the parent scope.
  include(
    ${CMAKE_BINARY_DIR}/v${_downgrade_TARGET_VERSION}/cmake/ScriptFiles.cmake)

  set(_downgrade_PRE_FILES ${PRE_UPDATE_FILES})
  set(_downgrade_POST_FILES ${SOURCE_FILES} ${SET_POST_UPDATE_STAGE}
                            ${POST_UPDATE_FILES} ${UNSET_UPDATE_STAGE})

  # Fetch prolog and epilog from target version.
  git_versioned_get(
    VERSION
    ${_downgrade_TARGET_VERSION}
    FILES
    ${_downgrade_PRE_FILES}
    RESULT_FILES
    _prolog_files
    IGNORE_ERRORS)
  git_versioned_get(
    VERSION
    ${_downgrade_TARGET_VERSION}
    FILES
    ${_downgrade_POST_FILES}
    RESULT_FILES
    _epilog_files
    IGNORE_ERRORS)

  set(_files ${_prolog_files})
  foreach(_downgrade_file ${_downgrade_FILES})
    list(APPEND _files ${_downgrade_INPUT_DIRECTORY}/${_downgrade_file})
  endforeach()
  list(APPEND _files ${_epilog_files})

  generate_script(
    VERSION
    ${_downgrade_TARGET_VERSION}
    SCRIPT
    timescaledb--${_downgrade_SOURCE_VERSION}--${_downgrade_TARGET_VERSION}.sql
    OUTPUT_DIRECTORY
    ${_downgrade_OUTPUT_DIRECTORY}
    FILES
    ${_files})
endfunction()
