# This file is part of Projecteur - https://github.com/jahnf/projecteur - See LICENSE.md and README.md
cmake_minimum_required(VERSION 3.0)
include(LinuxDistributionInfo)

set(_LinuxPackaging_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}")
set(_LinuxPackaging_cpack_template "LinuxPkgCPackConfig.cmake.in")
list(APPEND _LinuxPackaging_MAP_dist_pkgtype
  "debian::DEB"
  "ubuntu::DEB"
  "opensuse::RPM"
  "opensuse-leap::RPM"
  "fedora::RPM"
  "centos::RPM"
  "rhel::RPM"
  "arch::PKGBUILD"
  "archlinux::PKGBUILD"
)
set(_LinuxPackaging_default_pkgtype "TGZ")

# Function that adds 'dist-package' target
# Arguments:
#  PROJECT            : Project name to package
#  TARGET             : Main executable target with version information
#  DESCRIPTION_BRIEF  : Brief package description.
#  DESCRIPTION_FULL   : Full package description.
#  CONTACT            : Package maintainer/contact.
#  HOMEAPGE           : The project homepage
#  DEBIAN_SECTION.....: A valid debian package section (default=devel)

function(add_dist_package_target)
  set(oneValueArgs
    PROJECT # project name to package
    TARGET  # main executable build target that has version information attached to it
    DESCRIPTION_BRIEF
    DESCRIPTION_FULL
    CONTACT # Maintainer / contact person
    HOMEPAGE
    DEBIAN_SECTION
    PREINST_SCRIPT
    POSTINST_SCRIPT
    PRERM_SCRIPT
    POSTRM_SCRIPT
  )
  set(requiredArgs PROJECT TARGET)
  cmake_parse_arguments(PKG "" "${oneValueArgs}" "" ${ARGN})

  foreach(arg IN LISTS requiredArgs)
    if("${PKG_${arg}}" STREQUAL "")
      message(FATAL_ERROR "Required argument '${arg}' is not set.")
    endif()
  endforeach()

  if(NOT TARGET ${PKG_TARGET})
    message(FATAL_ERROR "Argument 'TARGET' needs to be a valid target.")
  endif()

  get_target_property(PKG_VERSION_STRING_FULL ${PKG_TARGET} VERSION_STRING_FULL)
  get_target_property(PKG_VERSION_STRING ${PKG_TARGET} VERSION_STRING)
  get_target_property(PKG_VERSION_MAJOR ${PKG_TARGET} VERSION_MAJOR)
  get_target_property(PKG_VERSION_MINOR ${PKG_TARGET} VERSION_MINOR)
  get_target_property(PKG_VERSION_PATCH ${PKG_TARGET} VERSION_PATCH)
  get_target_property(PKG_VERSION_FLAG ${PKG_TARGET} VERSION_FLAG)
  get_target_property(PKG_VERSION_DISTANCE ${PKG_TARGET} VERSION_DISTANCE)
  get_target_property(PKG_VERSION_BRANCH ${PKG_TARGET} VERSION_BRANCH)
  if("${PKG_VERSION_MAJOR}" STREQUAL "")
    set(PKG_VERSION_MAJOR 0)
  endif()
  if("${PKG_VERSION_MINOR}" STREQUAL "")
    set(PKG_VERSION_MINOR 0)
  endif()
  if("${PKG_VERSION_PATCH}" STREQUAL "")
    set(PKG_VERSION_PATCH 0)
  endif()
  set(PKG_VERSION_STRING_BASE "${PKG_VERSION_MAJOR}.${PKG_VERSION_MINOR}.${PKG_VERSION_PATCH}")
  if("${PKG_VERSION_FLAG}" STREQUAL "")
    set(PKG_VERSION_IDENTIFIERS "1")
  else()
    set(PKG_VERSION_IDENTIFIERS "0${PKG_VERSION_FLAG}.${PKG_VERSION_DISTANCE}")
  endif()
  # Set defaults if not set
  if("${PKG_CONTACT}" STREQUAL "")
    set(PKG_CONTACT "Generic Maintainer <generic@main.tainer>")
  endif()

  if("${PKG_DEBIAN_SECTION}" STREQUAL "")
    set(PKG_DEBIAN_SECTION "devel")
  endif()

  find_program(CPACK_COMMAND cpack)
  if(NOT CPACK_COMMAND)
    message(FATAL_ERROR "CPack command was not found.")
  endif()

  get_linux_distribution(LINUX_DIST_NAME LINUX_DIST_VERSION)
  # Get the package type to be generated by the target from our map variable
  set(PKG_TYPE "${_LinuxPackaging_default_pkgtype}")
  set(PKG_TYPE_FOUND 0)
  foreach(v "${LINUX_DIST_NAME}-${LINUX_DIST_VERSION}" "${LINUX_DIST_NAME}")
    foreach(pair ${_LinuxPackaging_MAP_dist_pkgtype})
      if( "${pair}" MATCHES "${v}::(.*)")
        set(PKG_TYPE "${CMAKE_MATCH_1}")
        set(PKG_TYPE_FOUND 1)
        break()
      endif()
    endforeach()
    if(PKG_TYPE_FOUND)
      break()
    endif()
  endforeach()

  # Check if project package dependencies exist
  include(PkgDependencies${PKG_PROJECT} OPTIONAL RESULT_VARIABLE INCLUDED_PROJECT_DEPENDENCIES)
  if(INCLUDED_PROJECT_DEPENDENCIES AND PkgDependencies_MAP_${PKG_PROJECT})
    set(PKG_DEPENDENCY_FOUND 0)
    # Find dependencies for Linux distribution (and version)
    foreach(v "${LINUX_DIST_NAME}-${LINUX_DIST_VERSION}" "${LINUX_DIST_NAME}")
      foreach(pair ${PkgDependencies_MAP_${PKG_PROJECT}})
        if( "${pair}" MATCHES "${v}::(.*)")
          if("${PKG_TYPE}" STREQUAL "PKGBUILD")
            unset(_install_deps)
            foreach(_dep ${${CMAKE_MATCH_1}})
              list(APPEND _install_deps "'${_dep}'")
            endforeach()
            string(REPLACE ";" " " PKG_DEPENDENCIES "${_install_deps}")
          else()
            string(REPLACE ";" ", " PKG_DEPENDENCIES "${${CMAKE_MATCH_1}}")
          endif()
          set(PKG_DEPENDENCY_FOUND 1)
          break()
        endif()
      endforeach()
      if(PKG_DEPENDENCY_FOUND)
        break()
      endif()
    endforeach()
  endif()

  if(INCLUDED_PROJECT_DEPENDENCIES AND PkgDependenciesMake_MAP_${PKG_PROJECT})
    set(PKG_BUILD_DEPENDENCY_FOUND 0)
    # Find dependencies for Linux distribution (and version)
    foreach(v "${LINUX_DIST_NAME}-${LINUX_DIST_VERSION}" "${LINUX_DIST_NAME}")
      foreach(pair ${PkgDependenciesMake_MAP_${PKG_PROJECT}})
        if( "${pair}" MATCHES "${v}::(.*)")
          if("${PKG_TYPE}" STREQUAL "PKGBUILD")
            unset(_install_deps)
            foreach(_dep ${${CMAKE_MATCH_1}})
              list(APPEND _install_deps "'${_dep}'")
            endforeach()
            string(REPLACE ";" " " PKG_BUILD_DEPENDENCIES "${_install_deps}")
          else()
            string(REPLACE ";" ", " PKG_BUILD_DEPENDENCIES "${${CMAKE_MATCH_1}}")
          endif()
          set(PKG_BUILD_DEPENDENCY_FOUND 1)
          break()
        endif()
      endforeach()
      if(PKG_BUILD_DEPENDENCY_FOUND)
        break()
      endif()
    endforeach()
  endif()

  string(TOLOWER "${PKG_PROJECT}" PKG_NAME)
  set(PKG_LICENSE "MIT")
  set(PKG_DIST "${LINUX_DIST_NAME}-${LINUX_DIST_VERSION}")
  string(TIMESTAMP PKG_DATE "%Y-%m-%d")

  if("${PKG_TYPE}" STREQUAL "PKGBUILD")
    _makepkg_packaging()
  else()
    _cpack_default_packaging()
  endif()

  configure_file(
    "${_LinuxPackaging_DIRECTORY}/travis-ci-bintray-deploy.json.in"
    "${CMAKE_CURRENT_BINARY_DIR}/travis-ci-bintray-deploy.json" @ONLY)

  message(STATUS "Configured target 'dist-package' with Linux '${PKG_DIST}' and package type '${PKG_TYPE}'")

  # Make some information available to parent scope
  set(PKG_DIST "${PKG_DIST}" PARENT_SCOPE)
  set(PKG_TYPE "${PKG_TYPE}" PARENT_SCOPE)
endfunction()

# makepg packaging (arch linux/pacman)
function(_makepkg_packaging)
  get_target_property(PKG_SOURCE_ARCHIVE_PATH source-archive OUTPUT_ARCHIVE)
  get_filename_component(PKG_SOURCE_ARCHIVE_DIR "${PKG_SOURCE_ARCHIVE_PATH}" DIRECTORY)
  get_filename_component(PKG_SOURCE_ARCHIVE_FILE "${PKG_SOURCE_ARCHIVE_PATH}" NAME)
  set(PKG_PKGBUILD_PKGREL 1)
  set(PKG_PKGBUILD_ARCH "${CMAKE_SYSTEM_PROCESSOR}")

  set(PKG_PKGBUILD_INSTALL_FILE "${PKG_NAME}.install")
  set(PKG_PKGBUILD_INSTALL_FILE_PATH "${PKG_SOURCE_ARCHIVE_DIR}/${PKG_PKGBUILD_INSTALL_FILE}")
  file(MAKE_DIRECTORY "${PKG_SOURCE_ARCHIVE_DIR}")
  file(WRITE "${PKG_PKGBUILD_INSTALL_FILE_PATH}" "# generated install file\n\n")

  if(PKG_PREINST_SCRIPT)
    file(READ "${PKG_PREINST_SCRIPT}" _pkg_preinst_script_content)
    file(APPEND "${PKG_PKGBUILD_INSTALL_FILE_PATH}"
      "pre_install() {\n"
      "  : # null operation in case the preinst file is empty\n"
      "  ${_pkg_preinst_script_content}"
      "}\n\n"
      "pre_upgrade() {\n"
      "  : # null operation in case the preinst file is empty\n"
      "  ${_pkg_preinst_script_content}"
      "}\n\n"
    )
  endif()

  if(PKG_POSTINST_SCRIPT)
    file(READ "${PKG_POSTINST_SCRIPT}" _pkg_postinst_script_content)
    file(APPEND "${PKG_PKGBUILD_INSTALL_FILE_PATH}"
      "post_install() {\n"
      "  : # null operation in case the preinst file is empty\n"
      "  ${_pkg_postinst_script_content}"
      "}\n\n"
      "post_upgrade() {\n"
      "  : # null operation in case the preinst file is empty\n"
      "  ${_pkg_postinst_script_content}"
      "}\n\n"
    )
  endif()

  if(PKG_PRERM_SCRIPT)
    file(READ "${PKG_PRERM_SCRIPT}" _pkg_prerm_script_content)
    file(APPEND "${PKG_PKGBUILD_INSTALL_FILE_PATH}"
      "pre_remove() {\n"
      "  : # null operation in case the preinst file is empty\n"
      "  ${_pkg_prerm_script_content}"
      "}\n\n"
    )
  endif()

  if(PKG_POSTRM_SCRIPT)
    file(READ "${PKG_POSTRM_SCRIPT}" _pkg_postrm_script_content)
    file(APPEND "${PKG_PKGBUILD_INSTALL_FILE_PATH}"
      "post_remove() {\n"
      "  : # null operation in case the preinst file is empty\n"
      "  ${_pkg_postrm_script_content}"
      "}\n\n"
    )
  endif()

  # makepkg: '-' is not allowed in version number
  string(REPLACE "-" "" PKG_PKGBUILD_VER "${PKG_VERSION_STRING_FULL}")

  set(PKG_CONFIG_TEMPLATE "${_LinuxPackaging_DIRECTORY}/PKGBUILD.in")
  set(PKG_CONFIG_FILE "${PKG_SOURCE_ARCHIVE_DIR}/PKGBUILD")
  configure_file("${PKG_CONFIG_TEMPLATE}" "${PKG_CONFIG_FILE}" @ONLY)


  find_program(MAKEPKG_EXECUTABLE makepkg)
  set(PKGBUILD_OUTPUT_FILE "${PKG_NAME}-${PKG_PKGBUILD_VER}-${PKG_PKGBUILD_PKGREL}-${PKG_PKGBUILD_ARCH}.pkg.tar.xz")
  set(PKGBUILD_OUTPUT_PATH "${PKG_SOURCE_ARCHIVE_DIR}/${PKGBUILD_OUTPUT_FILE}")
  set(PKGBUILD_FINAL_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/dist-pkg")
  set(PKGBUILD_FINAL_FILE "${PKG_NAME}-${PKG_PKGBUILD_VER}-${PKG_PKGBUILD_PKGREL}_${LINUX_DIST_NAME}-${PKG_PKGBUILD_ARCH}.pkg.tar.xz")

  add_custom_target(dist-package
    COMMAND ${MAKEPKG_EXECUTABLE} -f --log --skipinteg
    WORKING_DIRECTORY "${PKG_SOURCE_ARCHIVE_DIR}"
    VERBATIM
  )

  add_custom_command(TARGET dist-package PRE_BUILD
    COMMAND ${CMAKE_COMMAND} ARGS -E make_directory "${PKGBUILD_FINAL_OUTPUT_DIR}"
  )

  add_custom_command(TARGET dist-package POST_BUILD
    COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different "${PKGBUILD_OUTPUT_PATH}" "${PKGBUILD_FINAL_OUTPUT_DIR}/${PKGBUILD_FINAL_FILE}"
  )

  add_dependencies(dist-package source-archive)
endfunction()

# Default cpack packaging (DEB, RPM, TGZ)
function(_cpack_default_packaging)
  set(PKG_CPACK_PKG_FILENAME "${PKG_NAME}-${PKG_VERSION_STRING}_${PKG_DIST}-${CMAKE_SYSTEM_PROCESSOR}")
  set(PKG_CPACK_PKG_FILE_PREFIX "dist-pkg")
  set(PKG_CONFIG_TEMPLATE "${_LinuxPackaging_DIRECTORY}/LinuxPkgCPackConfig.cmake.in")
  set(PKG_CONFIG_FILE "${CMAKE_CURRENT_BINARY_DIR}/CPackConfig-${PKG_TYPE}.cmake")
  configure_file("${PKG_CONFIG_TEMPLATE}" "${PKG_CONFIG_FILE}" @ONLY)

  add_custom_target(dist-package
    COMMAND ${CPACK_COMMAND} --config "${PKG_CONFIG_FILE}"
    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
    VERBATIM
  )
endfunction()

## Add 'source-archive' target
function(add_source_archive_target target)
  find_package(Git)
  find_program(TAR_EXECUTABLE tar)
  find_program(GZIP_EXECUTABLE gzip)
  if(GIT_FOUND)
    get_target_property(VERSION_STRING ${target} VERSION_STRING)
    execute_process(COMMAND ${GIT_EXECUTABLE} describe --always
      RESULT_VARIABLE result
      OUTPUT_VARIABLE GIT_TREEISH
      ERROR_VARIABLE error_out
      OUTPUT_STRIP_TRAILING_WHITESPACE
      WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
    )
    if(NOT result EQUAL 0)
      set(GIT_TREEISH "HEAD")
    endif()

    # Write
    set(ARCHIVE_STAGE_DIR "${PROJECT_BINARY_DIR}/archive_stage")
    set(FILE_BASENAME "${target}-${VERSION_STRING}_source")
    set(GIT_TAR_FILE_PATH "${ARCHIVE_STAGE_DIR}/${FILE_BASENAME}.git-stage.tar")
    add_custom_command(OUTPUT "${GIT_TAR_FILE_PATH}"
      COMMAND ${CMAKE_COMMAND} ARGS -E make_directory "${ARCHIVE_STAGE_DIR}"
      COMMAND ${GIT_EXECUTABLE} ARGS archive --format=tar --prefix=${target}-${VERSION_STRING}/ --output="${GIT_TAR_FILE_PATH}" ${GIT_TREEISH}
      WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
      COMMENT "Running git archive (${target})..."
    )
    set(ARCHIVE_OUTPUT_DIR "${PROJECT_BINARY_DIR}/archive_output")
    set(TAR_FILE_PATH "${ARCHIVE_OUTPUT_DIR}/${FILE_BASENAME}.tar")
    set(TARGZ_FILE_PATH "${TAR_FILE_PATH}.gz")
    set(TARGZ_FILE_NAME "${FILE_BASENAME}.tar.gz")
    set(TAR_APPEND_DIR "${PROJECT_BINARY_DIR}/archive_append")
    add_custom_command(OUTPUT "${TARGZ_FILE_PATH}"
      DEPENDS "${GIT_TAR_FILE_PATH}"
      COMMAND ${CMAKE_COMMAND} ARGS -E copy "${GIT_TAR_FILE_PATH}" "${TAR_FILE_PATH}"
      COMMAND ${CMAKE_COMMAND} ARGS -E create_symlink "${PROJECT_BINARY_DIR}/archive_append" "${ARCHIVE_STAGE_DIR}/${target}-${VERSION_STRING}"
      COMMAND ${TAR_EXECUTABLE} ARGS -rf "${TAR_FILE_PATH}" "${target}-${VERSION_STRING}/*"
      COMMAND ${GZIP_EXECUTABLE} ARGS -9f "${TAR_FILE_PATH}"
      WORKING_DIRECTORY ${ARCHIVE_STAGE_DIR}
      COMMENT "Add version information to git archive (${target})..."
    )
    set(ARCHIVE_FINAL_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/dist-pkg")
    add_custom_target(source-archive DEPENDS "${TARGZ_FILE_PATH}")
    set_target_properties(source-archive PROPERTIES OUTPUT_ARCHIVE "${TARGZ_FILE_PATH}")
    add_custom_command(TARGET source-archive PRE_BUILD
      COMMAND ${CMAKE_COMMAND} ARGS -E make_directory "${ARCHIVE_FINAL_OUTPUT_DIR}")
    add_custom_command(TARGET source-archive POST_BUILD
      COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different "${TARGZ_FILE_PATH}" "${ARCHIVE_FINAL_OUTPUT_DIR}/${TARGZ_FILE_NAME}")
  else()
    message(STATUS "Cannot add 'source-archive' target, git not found.")
  endif()
endfunction()
