# Copyright (c) DataStax, Inc.
#
# 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
#
# http://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 2.8.12 FATAL_ERROR)
include(ExternalProject)
include(Windows-Environment)
if(NOT MSVC_ENVIRONMENT_SCRIPT)
  message(FATAL_ERROR "Visual Studio environment script is required to build OpenSSL")
endif()

# OpenSSL related CMake options
option(OPENSSL_VERSION "OpenSSL version to build and install")
if(NOT OPENSSL_VERSION)
  # TODO: Should we default to OpenSSL 1.1 (e.g. 1.1.0i)?
  set(OPENSSL_VERSION "1.0.2p")
endif()
option(OPENSSL_INSTALL_PREFIX "OpenSSL installation prefix location")
if(CASS_USE_ZLIB)
  include(ExternalProject-zlib)
endif()
set(OPENSSL_VERSION ${OPENSSL_VERSION} CACHE STRING "OpenSSL version to build and install" FORCE)

# Determine the major and minor version of OpenSSL used
string(REPLACE "." ";" OPENSSL_VERSION_LIST ${OPENSSL_VERSION})
list(GET OPENSSL_VERSION_LIST 0 OPENSSL_VERSION_MAJOR)
list(GET OPENSSL_VERSION_LIST 1 OPENSSL_VERSION_MINOR)
set(OPENSSL_MAJOR_MINOR_VERSION "${OPENSSL_VERSION_MAJOR}.${OPENSSL_VERSION_MINOR}")

# Determine the OpenSSL archive name to download
string(REPLACE "." "_" OPENSSL_ARCHIVE_VERSION ${OPENSSL_VERSION})
set(OPENSSL_ARCHIVE_NAME OpenSSL_${OPENSSL_ARCHIVE_VERSION} CACHE STRING "OpenSSL archive name" FORCE)

# Determine if OpenSSL installation directory should be set
if(NOT OPENSSL_INSTALL_PREFIX)
  set(OPENSSL_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/libs/openssl" CACHE STRING "OpenSSL installation prefix" FORCE)
endif()

# OpenSSL external project variables
set(OPENSSL_LIBRARY_NAME "openssl-${OPENSSL_VERSION}-library")
set(OPENSSL_PROJECT_PREFIX ${CMAKE_BINARY_DIR}/external/openssl)
set(OPENSSL_ARCHIVE_URL_PREFIX "https://github.com/openssl/openssl/archive/")
set(OPENSSL_ARCHIVE_URL_SUFFIX ".tar.gz")
set(OPENSSL_ARCHIVE_URL "${OPENSSL_ARCHIVE_URL_PREFIX}${OPENSSL_ARCHIVE_NAME}${OPENSSL_ARCHIVE_URL_SUFFIX}")

# Make sure Visual Studio is available
if(NOT MSVC)
  message(FATAL_ERROR "Visual Studio is required to build OpenSSL")
endif()

# Perl is required to create NMake files for OpenSSL build
find_package(Perl REQUIRED)
if(OPENSSL_MAJOR_MINOR_VERSION STREQUAL "1.1")
  if(PERL_VERSION_STRING VERSION_LESS "5.10.0")
    message(FATAL_ERROR "Invalid Perl Version: OpenSSL v1.1.0+ requires Perl v5.10.0+")
  endif()
endif()
if(CYGWIN_INSTALL_PATH)
  if(PERL_EXECUTABLE MATCHES "${CYGWIN_INSTALL_PATH}")
    message(FATAL_ERROR "Cygwin Perl Executable Found: Please install Strawberry Perl [http://strawberryperl.com/releases.html]")
  endif()
endif()
get_filename_component(PERL_PATH ${PERL_EXECUTABLE} DIRECTORY)
file(TO_NATIVE_PATH ${PERL_PATH} PERL_PATH)

message(STATUS "OpenSSL: ${OPENSSL_VERSION}")

# OpenSSL library configuration variables
set(OPENSSL_INSTALL_DIR "${OPENSSL_INSTALL_PREFIX}" CACHE STRING "OpenSSL installation directory" FORCE)
set(OPENSSL_BINARY_DIR "${OPENSSL_INSTALL_DIR}/bin" CACHE STRING "OpenSSL binary directory" FORCE)
set(OPENSSL_INCLUDE_DIR "${OPENSSL_INSTALL_DIR}/include" CACHE STRING "OpenSSL include directory" FORCE)
set(OPENSSL_INCLUDE_DIRS "${OPENSSL_INCLUDE_DIR}" CACHE STRING "OpenSSL include directory" FORCE) # Alias to stay consistent with FindOpenSSL
set(OPENSSL_LIBRARY_DIR "${OPENSSL_INSTALL_DIR}/lib" CACHE STRING "OpenSSL library directory" FORCE)
if(OPENSSL_MAJOR_MINOR_VERSION STREQUAL "1.0")
  set(OPENSSL_LIBRARIES ${OPENSSL_LIBRARY_DIR}/libeay32.lib
                        ${OPENSSL_LIBRARY_DIR}/ssleay32.lib
                        CACHE STRING "OpenSSL libraries" FORCE)
else()
  set(OPENSSL_LIBRARIES ${OPENSSL_LIBRARY_DIR}/libcrypto.lib
                        ${OPENSSL_LIBRARY_DIR}/libssl.lib
                        CACHE STRING "OpenSSL libraries" FORCE)
endif()
set(OPENSSL_ROOT_DIR "${OPENSSL_INSTALL_DIR}" CACHE STRING "OpenSSL root directory" FORCE)

# Create build options for the platform build scripts
if(BUILD_SHARED_LIBS)
  if(CASS_USE_ZLIB)
    set(OPENSSL_ZLIB_CONFIGURE_ARGUMENT "zlib-dynamic")
    set(ZLIB_LIB zlib.lib)
  endif()
else()
  if(CASS_USE_ZLIB)
    set(OPENSSL_ZLIB_CONFIGURE_ARGUMENT "no-zlib-dynamic")
    set(ZLIB_LIB zlibstatic.lib)
  endif()
endif()

# Determine if shared or static library should be built
set(OPENSSL_CONFIGURE_COMPILER "no-asm no-ssl2")
if(BUILD_SHARED_LIBS)
  set(OPENSSL_CONFIGURE_COMPILER "${OPENSSL_CONFIGURE_COMPILER} shared")
  set(OPENSSL_1_0_MAKEFILE "ms\\ntdll.mak")
  set(OPENSSL_1_1_MAKEFILE "makefile.shared")
else()
  set(OPENSSL_CONFIGURE_COMPILER "${OPENSSL_CONFIGURE_COMPILER} no-shared")
  set(OPENSSL_1_0_MAKEFILE "ms\\nt.mak")
  set(OPENSSL_1_1_MAKEFILE "makefile")
endif()

# Determine which compiler to use for configuration script
if(CMAKE_CL_64)
  set(OPENSSL_CONFIGURE_COMPILER "${OPENSSL_CONFIGURE_COMPILER} VC-WIN64A")
  set(OPENSSL_1_0_CONFIGURE_MAKEFILE_SCRIPT "ms\\do_win64a.bat")
else()
  set(OPENSSL_CONFIGURE_COMPILER "${OPENSSL_CONFIGURE_COMPILER} VC-WIN32")
  set(OPENSSL_1_0_CONFIGURE_MAKEFILE_SCRIPT "ms\\do_ms.bat")
endif()

# Determine which Makefile is being used
if(OPENSSL_MAJOR_MINOR_VERSION STREQUAL "1.0")
  set(OPENSSL_MAKEFILE ${OPENSSL_1_0_MAKEFILE})
else()
  set(OPENSSL_MAKEFILE ${OPENSSL_1_1_MAKEFILE})
endif()

# Create a package name for the binaries
set(OPENSSL_PACKAGE_NAME "openssl-${OPENSSL_VERSION}-${PACKAGE_BUILD_TYPE}-${PACKAGE_ARCH_TYPE}-msvc${VS_INTERNAL_VERSION}.zip" CACHE STRING "OpenSSL package name" FORCE)

# Create a place holder for the configure script (source_dir property required from external project)
set(OPENSSL_CONFIGURE_SCRIPT "${OPENSSL_PROJECT_PREFIX}/scripts/configure_openssl.bat")
set(OPENSSL_CONFIGURE_COMMAND "${OPENSSL_CONFIGURE_SCRIPT}")

# Create the make script
set(OPENSSL_MAKE_SCRIPT "${OPENSSL_PROJECT_PREFIX}/scripts/make_openssl.bat")
file(REMOVE ${OPENSSL_MAKE_SCRIPT})
file(WRITE ${OPENSSL_MAKE_SCRIPT}
  "@REM Generated make script for OpenSSL\r\n"
  "@ECHO OFF\r\n"
  "SETLOCAL ENABLEDELAYEDEXPANSION\r\n"
  "PUSHD .\r\n")
if(DEFINED ENV{APPVEYOR} AND CMAKE_CL_64)
  file(APPEND ${OPENSSL_MAKE_SCRIPT}
    "CALL \"C:\\Program Files\\Microsoft SDKs\\Windows\\v7.1\\Bin\\SetEnv.cmd\" /x64\r\n"
    "CALL \"${MSVC_ENVIRONMENT_SCRIPT}\" ${MSVC_ENVIRONMENT_ARCH}\r\n")
else()
  file(APPEND ${OPENSSL_MAKE_SCRIPT}
    "CALL \"${MSVC_ENVIRONMENT_SCRIPT}\"\r\n")
endif()
file(APPEND ${OPENSSL_MAKE_SCRIPT}
  "POPD\r\n")
if(OPENSSL_MAJOR_MINOR_VERSION STREQUAL "1.0")
  file(APPEND ${OPENSSL_MAKE_SCRIPT}
    "NMake /F \"${OPENSSL_1_0_MAKEFILE}\"\r\n")
else()
  file(APPEND ${OPENSSL_MAKE_SCRIPT}
    "NMake\r\n")
endif()
file(APPEND ${OPENSSL_MAKE_SCRIPT}
  "IF NOT %ERRORLEVEL% EQU 0 (\r\n"
  "  ENDLOCAL\r\n"
  "  EXIT /B 1\r\n"
  ")\r\n"
  "ENDLOCAL\r\n"
  "EXIT /B\r\n")
set(OPENSSL_BUILD_COMMAND "${OPENSSL_MAKE_SCRIPT}")

# Create the install script
file(TO_NATIVE_PATH ${OPENSSL_BINARY_DIR} OPENSSL_NATIVE_BINARY_DIR)
file(TO_NATIVE_PATH ${OPENSSL_INCLUDE_DIR} OPENSSL_NATIVE_INCLUDE_DIR)
file(TO_NATIVE_PATH ${OPENSSL_INSTALL_DIR} OPENSSL_NATIVE_INSTALL_DIR)
file(TO_NATIVE_PATH ${OPENSSL_LIBRARY_DIR} OPENSSL_NATIVE_LIBRARY_DIR)
set(OPENSSL_INSTALL_SCRIPT "${OPENSSL_PROJECT_PREFIX}/scripts/install_openssl.bat")
file(REMOVE ${OPENSSL_INSTALL_SCRIPT})
file(WRITE ${OPENSSL_INSTALL_SCRIPT}
  "@REM Generated install script for OpenSSL\r\n"
  "@ECHO OFF\r\n"
  "SETLOCAL ENABLEDELAYEDEXPANSION\r\n"
  "PUSHD .\r\n")
if(DEFINED ENV{APPVEYOR} AND CMAKE_CL_64)
  file(APPEND ${OPENSSL_INSTALL_SCRIPT}
    "CALL \"C:\\Program Files\\Microsoft SDKs\\Windows\\v7.1\\Bin\\SetEnv.cmd\" /x64\r\n"
    "CALL \"${MSVC_ENVIRONMENT_SCRIPT}\" ${MSVC_ENVIRONMENT_ARCH}\r\n")
else()
  file(APPEND ${OPENSSL_INSTALL_SCRIPT}
    "CALL \"${MSVC_ENVIRONMENT_SCRIPT}\"\r\n")
endif()
file(APPEND ${OPENSSL_INSTALL_SCRIPT}
  "POPD\r\n"
  "CALL :SHORTENPATH \"${OPENSSL_NATIVE_BINARY_DIR}\" SHORTENED_OPENSSL_BINARY_DIR\r\n"
  "CALL :SHORTENPATH \"${OPENSSL_NATIVE_INCLUDE_DIR}\" SHORTENED_OPENSSL_INCLUDE_DIR\r\n"
  "CALL :SHORTENPATH \"${OPENSSL_NATIVE_INSTALL_DIR}\" SHORTENED_OPENSSL_INSTALL_DIR\r\n"
  "CALL :SHORTENPATH \"${OPENSSL_NATIVE_LIBRARY_DIR}\" SHORTENED_OPENSSL_LIBRARY_DIR\r\n")
if(OPENSSL_MAJOR_MINOR_VERSION STREQUAL "1.0")
  file(APPEND ${OPENSSL_INSTALL_SCRIPT}
    "NMake /F \"${OPENSSL_1_0_MAKEFILE}\" install\r\n")
else()
  file(APPEND ${OPENSSL_INSTALL_SCRIPT}
    "NMake install\r\n")
endif()
file(APPEND ${OPENSSL_INSTALL_SCRIPT}
  "IF NOT %ERRORLEVEL% EQU 0 (\r\n"
  "  ENDLOCAL\r\n"
  "  EXIT /B 1\r\n"
  ")\r\n"
  "IF EXIST ms\\applink,c (\r\n"
  "  COPY /Y ms\\applink.c \"!SHORTENED_OPENSSL_INCLUDE_DIR!\\openssl\"\r\n"
  "  IF NOT %ERRORLEVEL% EQU 0 (\r\n"
  "    ENDLOCAL\r\n"
  "    EXIT /B 1\r\n"
  "  )\r\n"
  ")\r\n"
  "IF EXIST tmp32dll\\lib.pdb (\r\n"
  "  COPY /Y tmp32dll\\lib.pdb \"!SHORTENED_OPENSSL_BINARY_DIR!\"\r\n"
  "  IF NOT %ERRORLEVEL% EQU 0 (\r\n"
  "    ENDLOCAL\r\n"
  "    EXIT /B 1\r\n"
  "  )\r\n"
  ")\r\n"
  "IF EXIST tmp32\\lib.pdb (\r\n"
  "  COPY /Y tmp32\\lib.pdb \"!SHORTENED_OPENSSL_LIBRARY_DIR!\"\r\n"
  "  IF NOT %ERRORLEVEL% EQU 0 (\r\n"
  "    ENDLOCAL\r\n"
  "    EXIT /B 1\r\n"
  "  )\r\n"
  ")\r\n"
  "COPY /Y LICENSE \"!SHORTENED_OPENSSL_INSTALL_DIR!\"\r\n"
  "ENDLOCAL\r\n"
  "EXIT /B\r\n"
  ":SHORTENPATH\r\n"
  "  FOR %%A IN (\"%~1\") DO SET %~2=%%~SA\r\n"
  "  EXIT /B\r\n")
set(OPENSSL_INSTALL_COMMAND "${OPENSSL_INSTALL_SCRIPT}")

# Add OpenSSL as an external project
externalproject_add(${OPENSSL_LIBRARY_NAME}
  PREFIX ${OPENSSL_PROJECT_PREFIX}
  URL ${OPENSSL_ARCHIVE_URL}
  DOWNLOAD_DIR ${OPENSSL_PROJECT_PREFIX}
  CONFIGURE_COMMAND "${OPENSSL_CONFIGURE_COMMAND}"
  BUILD_COMMAND "${OPENSSL_BUILD_COMMAND}"
  INSTALL_COMMAND "${OPENSSL_INSTALL_COMMAND}"
  BUILD_IN_SOURCE 1
  LOG_DOWNLOAD 1
  LOG_CONFIGURE 1
  LOG_BUILD 1
  LOG_INSTALL 1)

# Create the configure script
file(REMOVE ${OPENSSL_CONFIGURE_SCRIPT})
file(WRITE ${OPENSSL_CONFIGURE_SCRIPT}
  "@REM Generated configure script for OpenSSL\r\n"
  "@ECHO OFF\r\n"
  "SETLOCAL ENABLEDELAYEDEXPANSION\r\n"
  "PUSHD .\r\n")
if(DEFINED ENV{APPVEYOR} AND CMAKE_CL_64)
  file(APPEND ${OPENSSL_CONFIGURE_SCRIPT}
    "CALL \"C:\\Program Files\\Microsoft SDKs\\Windows\\v7.1\\Bin\\SetEnv.cmd\" /x64\r\n"
    "CALL \"${MSVC_ENVIRONMENT_SCRIPT}\" ${MSVC_ENVIRONMENT_ARCH}\r\n")
else()
  file(APPEND ${OPENSSL_CONFIGURE_SCRIPT}
    "CALL \"${MSVC_ENVIRONMENT_SCRIPT}\"\r\n")
endif()
file(APPEND ${OPENSSL_CONFIGURE_SCRIPT}
  "POPD\r\n"
  "SET PATH=${PERL_PATH};%PATH%\r\n"
  "CALL :SHORTENPATH \"${OPENSSL_NATIVE_INSTALL_DIR}\" SHORTENED_OPENSSL_INSTALL_DIR\r\n")
if(CASS_USE_ZLIB)
  # OpenSSL requires zlib paths to be relative (otherwise build errors may occur)
  externalproject_get_property(${OPENSSL_LIBRARY_NAME} SOURCE_DIR)
  file(RELATIVE_PATH ZLIB_INCLUDE_RELATIVE_DIR ${SOURCE_DIR} ${ZLIB_INCLUDE_DIR})
  file(TO_NATIVE_PATH ${ZLIB_INCLUDE_RELATIVE_DIR} ZLIB_NATIVE_INCLUDE_RELATIVE_DIR)
  file(RELATIVE_PATH ZLIB_LIBRARY_RELATIVE_DIR ${SOURCE_DIR} ${ZLIB_LIBRARY_DIR})
  file(TO_NATIVE_PATH ${ZLIB_LIBRARY_RELATIVE_DIR} ZLIB_NATIVE_LIBRARY_RELATIVE_DIR)
  set(OPENSSL_WITH_ZLIB_ARGUMENT "zlib ${OPENSSL_ZLIB_CONFIGURE_ARGUMENT} --with-zlib-include=\"${ZLIB_NATIVE_INCLUDE_RELATIVE_DIR}\" --with-zlib-lib=\"${ZLIB_NATIVE_LIBRARY_RELATIVE_DIR}\\${ZLIB_LIB}\"")
  set(OPENSSL_WITH_ZLIB_ARGUMENT "zlib ${OPENSSL_ZLIB_CONFIGURE_ARGUMENT} --with-zlib-include=\"${ZLIB_INCLUDE_RELATIVE_DIR}\" --with-zlib-lib=\"${ZLIB_LIBRARY_RELATIVE_DIR}\\${ZLIB_LIB}\"")
endif()
file(APPEND ${OPENSSL_CONFIGURE_SCRIPT}
  "perl Configure ${OPENSSL_WITH_ZLIB_ARGUMENT} --openssldir=!SHORTENED_OPENSSL_INSTALL_DIR! --prefix=!SHORTENED_OPENSSL_INSTALL_DIR! ${OPENSSL_CONFIGURE_COMPILER}\r\n"
  "IF NOT %ERRORLEVEL% EQU 0 (\r\n"
  "  ENDLOCAL\r\n"
  "  EXIT /B 1\r\n"
  ")\r\n")
if(OPENSSL_MAJOR_MINOR_VERSION STREQUAL "1.0")
  file(APPEND ${OPENSSL_CONFIGURE_SCRIPT}
    "CALL ${OPENSSL_1_0_CONFIGURE_MAKEFILE_SCRIPT}\r\n"
    "IF NOT %ERRORLEVEL% EQU 0 (\r\n"
    "  ENDLOCAL\r\n"
    "  EXIT /B 1\r\n"
    ")\r\n"
    "perl -p -i\".backup\" -e \"s/exe: \\$\\(T_EXE\\)/exe:/g\" ${OPENSSL_1_0_MAKEFILE}\r\n"
    "IF NOT %ERRORLEVEL% EQU 0 (\r\n"
    "  ENDLOCAL\r\n"
    "  EXIT /B 1\r\n"
    ")\r\n")
endif()
if(NOT BUILD_SHARED_LIBS)
  file(APPEND ${OPENSSL_CONFIGURE_SCRIPT}
    "perl -p -i\".backup\" -e \"s/\\/MT/\\/MD/g\" ${OPENSSL_MAKEFILE}\r\n"
    "IF NOT %ERRORLEVEL% EQU 0 (\r\n"
    "  ENDLOCAL\r\n"
    "  EXIT /B 1\r\n"
    ")\r\n")
endif()
file(APPEND ${OPENSSL_CONFIGURE_SCRIPT}
  "ENDLOCAL\r\n"
  "EXIT /B\r\n"
  ":SHORTENPATH\r\n"
  "  FOR %%A IN (\"%~1\") DO SET %~2=%%~SA\r\n"
  "  EXIT /B\r\n")

# Determine if zlib should be added as a dependency
if(CASS_USE_ZLIB)
  add_dependencies(${OPENSSL_LIBRARY_NAME} ${ZLIB_LIBRARY_NAME})
endif()

# Update the include directory to use OpenSSL
include_directories(${OPENSSL_INCLUDE_DIR})
