#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you 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.
#
 
# This file allows cross-compiling of proton to JavaScript using emscripten
# (https://github.com/kripken/emscripten). As it is really a cross-compilation
# (as opposed to a binding like with swig) the approach is rather different and
# somewhat replicates the main build in the proton-c/CMakeLists.txt using quite
# a bit of "copy and paste reuse".
# TODO refactor this file (and proton-c/CMakeLists.txt) to keep the main
# compilation and cross-compilation consistent.

message(STATUS "Found emscripten, using that to build JavaScript binding")

# Find and install the node.js packages that we might need. We can assume that
# node.js itself is installed because Emscripten has a dependency on it.
find_package(NodePackages)

# Describe the target OS we are building to - Emscripten mimics the Linux platform.
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_VERSION 1)
set(CMAKE_CROSSCOMPILING TRUE)

# Specify the compiler to use for cross-compilation.
set(CMAKE_C_COMPILER "${EMCC}")

# Don't do compiler autodetection, since we are cross-compiling.
include(CMakeForceCompiler)
CMAKE_FORCE_C_COMPILER("${CMAKE_C_COMPILER}" Clang)

# The Proton default build type is RelWithDebInfo, but for JavaScript the C debug
# mechanism is irrelevant. If Debug is explicitly set we turn off optimisations
# and don't run the closure compiler so any error/profiling messages are readable.
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
    message(STATUS "JavaScript build type is \"Debug\"")
else()
    set(CMAKE_BUILD_TYPE Release)
    message(STATUS "JavaScript build type is \"Release\"")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3")
    set(EMSCRIPTEN_LINK_OPTIMISATIONS "-O2 --closure 1")
endif()

# From this point we should be using emscripten compilation tools.
message(STATUS "emscripten compilation environment:")
message(STATUS "CMAKE_C_COMPILER: ${CMAKE_C_COMPILER}")


# Set this to the proton-c directory, we're cross-compiling code from there.
set(PN_PATH ${CMAKE_SOURCE_DIR}/proton-c)


# TODO the OpenSSL stuff won't work for emscripten by default. It might well be
# possible to compile it from source using emscripten (that's the standard practice
# for libraries with emscripten) see https://github.com/kripken/emscripten/wiki/FAQ
# "Q. How do I link against system libraries like SDL, boost, etc.?"
# Though it might be more natural to simply use a TLS protected wss:// WebSocket URL.
#  set(pn_ssl_impl src/ssl/openssl.c)
#  set(SSL_LIB ${OPENSSL_LIBRARIES})
set(pn_ssl_impl ${PN_PATH}/src/ssl/ssl_stub.c)

# emscripten is Linux like so use the posix impls.
set(pn_io_impl ${PN_PATH}/src/posix/io.c)
set(pn_selector_impl ${PN_PATH}/src/posix/selector.c)

CHECK_LIBRARY_EXISTS (uuid uuid_generate "" UUID_GENERATE_IN_UUID)
if (UUID_GENERATE_IN_UUID)
  set (UUID_LIB uuid)
endif (UUID_GENERATE_IN_UUID)

# Generate encodings.h and protocol.h
# It may be possible to use the ones generated for the main proton-c build but
# qpid-proton-core has a dependency on the generated files, so I'm not sure if
# it'll work without a command that can be run as a dependency. Probably best
# do it this way when cross-compiling - though more copy'n'paste
add_custom_command(
  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/encodings.h
  COMMAND python ${PN_PATH}/env.py PYTHONPATH=${PN_PATH} python ${PN_PATH}/src/codec/encodings.h.py > ${CMAKE_CURRENT_BINARY_DIR}/encodings.h
  DEPENDS ${PN_PATH}/src/codec/encodings.h.py
  )

add_custom_command(
  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/protocol.h
  COMMAND python ${PN_PATH}/env.py PYTHONPATH=${PN_PATH} python ${PN_PATH}/src/protocol.h.py > ${CMAKE_CURRENT_BINARY_DIR}/protocol.h
  DEPENDS ${PN_PATH}/src/protocol.h.py
  )

set(COMPILE_WARNING_FLAGS "-Werror -Wall -pedantic-errors -Wno-comment -Wno-warn-absolute-paths")

# Explicitly set PLATFORM_DEFINITIONS to Linux ones for emscripten as we don't
# want to inadvertently use Windows versions if we happen to be cross-compiling
# from a Windows platform
set(PLATFORM_DEFINITIONS "USE_CLOCK_GETTIME;USE_UUID_GENERATE;USE_STRERROR_R;USE_ATOLL")

# The following is largely a copy from the the main proton-c/CMakeLists.txt.
# The main difference is prefixing paths with ${PN_PATH}/ as we can't use a
# relative path from this CMakeLists.txt.

set(qpid-proton-platform
  ${pn_io_impl}
  ${pn_selector_impl}
  ${PN_PATH}/src/platform.c
  ${pn_ssl_impl}
  )

set(qpid-proton-core
  ${PN_PATH}/src/object/object.c
  ${PN_PATH}/src/object/list.c
  ${PN_PATH}/src/object/map.c
  ${PN_PATH}/src/object/string.c
  ${PN_PATH}/src/object/iterator.c
  ${PN_PATH}/src/object/record.c

  ${PN_PATH}/src/log.c
  ${PN_PATH}/src/util.c
  ${PN_PATH}/src/url.c
  ${PN_PATH}/src/error.c
  ${PN_PATH}/src/buffer.c
  ${PN_PATH}/src/parser.c
  ${PN_PATH}/src/scanner.c
  ${PN_PATH}/src/types.c

  ${PN_PATH}/src/framing/framing.c

  ${PN_PATH}/src/codec/codec.c
  ${PN_PATH}/src/codec/decoder.c
  ${PN_PATH}/src/codec/encoder.c

  ${PN_PATH}/src/dispatcher/dispatcher.c
  ${PN_PATH}/src/engine/engine.c
  ${PN_PATH}/src/events/event.c
  ${PN_PATH}/src/transport/autodetect.c
  ${PN_PATH}/src/transport/transport.c
  ${PN_PATH}/src/message/message.c
  ${PN_PATH}/src/sasl/sasl.c
  ${PN_PATH}/src/sasl/none_sasl.c

  ${PN_PATH}/src/messenger/messenger.c
  ${PN_PATH}/src/messenger/subscription.c
  ${PN_PATH}/src/messenger/store.c
  ${PN_PATH}/src/messenger/transform.c
  ${PN_PATH}/src/selectable.c

  ${CMAKE_CURRENT_BINARY_DIR}/encodings.h
  ${CMAKE_CURRENT_BINARY_DIR}/protocol.h
  )

set_source_files_properties(
  ${qpid-proton-core}
  PROPERTIES
  COMPILE_FLAGS "${COMPILE_WARNING_FLAGS} ${COMPILE_LANGUAGE_FLAGS}"
  )

set_source_files_properties(
  ${qpid-proton-platform}
  PROPERTIES
  COMPILE_FLAGS "${COMPILE_WARNING_FLAGS} ${COMPILE_PLATFORM_FLAGS}"
  COMPILE_DEFINITIONS "${PLATFORM_DEFINITIONS}"
  )

# Compile Proton into LLVM bitcode which will be used later in the linking stage
# of add_executable for compilation into Javascript.
add_library(
  qpid-proton-bitcode SHARED

  ${qpid-proton-core}
  ${qpid-proton-platform}
  )

set_target_properties(
  qpid-proton-bitcode
  PROPERTIES
  VERSION   "${PN_LIB_SOMAJOR}.${PN_LIB_SOMINOR}"
  SOVERSION "${PN_LIB_SOMAJOR}"
  LINK_FLAGS "${CATCH_UNDEFINED}"
  )
target_link_libraries(qpid-proton-bitcode)


# Compile the send-async.c and recv-async.c examples into JavaScript
include_directories(${Proton_SOURCE_DIR}/examples/c/include)
add_executable(send-async.js ${Proton_SOURCE_DIR}/examples/c/messenger/send-async.c)
target_link_libraries(send-async.js qpid-proton-bitcode)

add_executable(recv-async.js ${Proton_SOURCE_DIR}/examples/c/messenger/recv-async.c)
target_link_libraries(recv-async.js qpid-proton-bitcode)

set_target_properties(
  send-async.js recv-async.js
  PROPERTIES
  COMPILE_FLAGS "${COMPILE_WARNING_FLAGS} ${COMPILE_PLATFORM_FLAGS}"
  RUNTIME_OUTPUT_DIRECTORY examples
  DEPENDS ws

  LINK_FLAGS "-s \"WEBSOCKET_SUBPROTOCOL='AMQPWSB10'\" -${EMSCRIPTEN_LINK_OPTIMISATIONS}"
  )

# Build the main JavaScript library called proton-messenger.js
add_executable(proton-messenger.js binding.c)
target_link_libraries(proton-messenger.js qpid-proton-bitcode ${UUID_LIB})

set_target_properties(
  proton-messenger.js
  PROPERTIES
  COMPILE_FLAGS "${COMPILE_WARNING_FLAGS} ${COMPILE_PLATFORM_FLAGS}"

  # The --memory-init-file 0 stops emscripten emitting a separate memory
  # initialization file, if this was enabled it makes packaging harder as
  # applications would expect proton-messenger.js.mem to be served too. It's even
  # more fiddly with node.js packages. This behaviour might be reinstated if the
  # packaging mechanism improves.

  LINK_FLAGS "-s \"EXPORT_NAME='proton'\" -s \"WEBSOCKET_SUBPROTOCOL='AMQPWSB10'\" ${EMSCRIPTEN_LINK_OPTIMISATIONS} --memory-init-file 0 --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/binding-open.js --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/module.js --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/error.js --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/messenger.js --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/subscription.js --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/message.js --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/data.js --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/data-uuid.js --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/data-symbol.js --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/data-described.js --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/data-array.js --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/data-typed-number.js --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/data-long.js --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/data-binary.js --post-js ${CMAKE_CURRENT_SOURCE_DIR}/binding-close.js -s DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=\"[]\" -s EXPORTED_FUNCTIONS=\"['_pn_get_version_major', '_pn_get_version_minor', '_pn_uuid_generate', '_pn_bytes', '_pn_error_text', '_pn_code', '_pn_messenger', '_pn_messenger_name', '_pn_messenger_set_blocking', '_pn_messenger_free', '_pn_messenger_errno', '_pn_messenger_error', '_pn_messenger_get_outgoing_window', '_pn_messenger_set_outgoing_window', '_pn_messenger_get_incoming_window', '_pn_messenger_set_incoming_window', '_pn_messenger_start', '_pn_messenger_stop', '_pn_messenger_stopped', '_pn_messenger_subscribe', '_pn_messenger_put', '_pn_messenger_status', '_pn_messenger_buffered', '_pn_messenger_settle', '_pn_messenger_outgoing_tracker', '_pn_messenger_recv', '_pn_messenger_receiving', '_pn_messenger_get', '_pn_messenger_incoming_tracker', '_pn_messenger_incoming_subscription', '_pn_messenger_accept', '_pn_messenger_reject', '_pn_messenger_outgoing', '_pn_messenger_incoming',  '_pn_messenger_route', '_pn_messenger_rewrite', '_pn_messenger_set_passive', '_pn_messenger_selectable', '_pn_subscription_get_context', '_pn_subscription_set_context', '_pn_subscription_address', '_pn_message', '_pn_message_id', '_pn_message_correlation_id', '_pn_message_free', '_pn_message_errno', '_pn_message_error', '_pn_message_clear', '_pn_message_is_inferred', '_pn_message_set_inferred', '_pn_message_is_durable', '_pn_message_set_durable', '_pn_message_get_priority', '_pn_message_set_priority', '_pn_message_get_ttl', '_pn_message_set_ttl', '_pn_message_is_first_acquirer', '_pn_message_set_first_acquirer', '_pn_message_get_delivery_count', '_pn_message_set_delivery_count', '_pn_message_get_user_id', '_pn_message_set_user_id', '_pn_message_get_address', '_pn_message_set_address', '_pn_message_get_subject', '_pn_message_set_subject', '_pn_message_get_reply_to', '_pn_message_set_reply_to', '_pn_message_get_content_type', '_pn_message_set_content_type', '_pn_message_get_content_encoding', '_pn_message_set_content_encoding', '_pn_message_get_expiry_time', '_pn_message_set_expiry_time', '_pn_message_get_creation_time', '_pn_message_set_creation_time', '_pn_message_get_group_id', '_pn_message_set_group_id', '_pn_message_get_group_sequence', '_pn_message_set_group_sequence', '_pn_message_get_reply_to_group_id', '_pn_message_set_reply_to_group_id', '_pn_message_encode', '_pn_message_decode', '_pn_message_instructions', '_pn_message_annotations', '_pn_message_properties', '_pn_message_body', '_pn_data', '_pn_data_free', '_pn_data_error', '_pn_data_errno', '_pn_data_clear', '_pn_data_rewind', '_pn_data_next', '_pn_data_prev', '_pn_data_enter', '_pn_data_exit', '_pn_data_lookup', '_pn_data_narrow', '_pn_data_widen', '_pn_data_type', '_pn_data_encode', '_pn_data_decode', '_pn_data_put_list', '_pn_data_put_map', '_pn_data_put_array', '_pn_data_put_described', '_pn_data_put_null', '_pn_data_put_bool', '_pn_data_put_ubyte', '_pn_data_put_byte', '_pn_data_put_ushort', '_pn_data_put_short', '_pn_data_put_uint', '_pn_data_put_int', '_pn_data_put_char', '_pn_data_put_ulong', '_pn_data_put_long', '_pn_data_put_timestamp', '_pn_data_put_float', '_pn_data_put_double', '_pn_data_put_decimal32', '_pn_data_put_decimal64', '_pn_data_put_decimal128', '_pn_data_put_uuid', '_pn_data_put_binary', '_pn_data_put_string', '_pn_data_put_symbol', '_pn_data_get_list', '_pn_data_get_map', '_pn_data_get_array', '_pn_data_is_array_described', '_pn_data_get_array_type', '_pn_data_is_described', '_pn_data_is_null', '_pn_data_get_bool', '_pn_data_get_ubyte', '_pn_data_get_byte', '_pn_data_get_ushort', '_pn_data_get_short', '_pn_data_get_uint', '_pn_data_get_int', '_pn_data_get_char', '_pn_data_get_ulong', '_pn_data_get_long', '_pn_data_get_timestamp', '_pn_data_get_float', '_pn_data_get_double', '_pn_data_get_decimal32', '_pn_data_get_decimal64', '_pn_data_get_decimal128', '_pn_data_get_uuid', '_pn_data_get_binary', '_pn_data_get_string', '_pn_data_get_symbol', '_pn_data_copy', '_pn_data_format', '_pn_data_dump', '_pn_selectable_readable', '_pn_selectable_is_reading', '_pn_selectable_writable', '_pn_selectable_is_writing', '_pn_selectable_is_terminal', '_pn_selectable_get_fd', '_pn_selectable_free']\""
  )

# This command packages up the compiled proton-messenger.js into a node.js package
# called qpid-proton-messenger and copies it to the <proton>/node_modules directory.
# This allows the node.js test and example programs to do:
# var proton = require("qpid-proton-messenger");
add_custom_command(
    TARGET proton-messenger.js
    COMMAND ${CMAKE_COMMAND}
            -E copy_directory 
            ${CMAKE_CURRENT_SOURCE_DIR}/qpid-proton-messenger
            ${PROJECT_SOURCE_DIR}/node_modules/qpid-proton-messenger
    COMMAND ${CMAKE_COMMAND}
            -E copy
            ${CMAKE_CURRENT_BINARY_DIR}/proton-messenger.js
            ${PROJECT_SOURCE_DIR}/node_modules/qpid-proton-messenger/lib
   COMMENT "Building qpid-proton-messenger package for node.js"
)

# The cleanall target removes the qpid-proton-messenger package from <proton>/node_modules
add_custom_target(
    cleanall
    COMMAND echo "CLEAN NODE MODULES"
    COMMAND ${CMAKE_COMMAND}
            -E remove_directory
            ${PROJECT_SOURCE_DIR}/node_modules/qpid-proton-messenger
)

# If the docs target is specified and the jsdoc3 package for node.js has been
# installed then build the JavaScript API documentation.
if (NODE_JSDOC_FOUND)
    set(JSDOC_EXE ${PROJECT_SOURCE_DIR}/node_modules/.bin/jsdoc)

    message(STATUS "Documentation Enabled. Using ${JSDOC_EXE} to build JavaScript docs")
    add_custom_target(docs-js COMMAND ${JSDOC_EXE}
                      -d ${CMAKE_CURRENT_BINARY_DIR}/html
                      ${CMAKE_CURRENT_SOURCE_DIR}/module.js
                      ${CMAKE_CURRENT_SOURCE_DIR}/error.js
                      ${CMAKE_CURRENT_SOURCE_DIR}/messenger.js
                      ${CMAKE_CURRENT_SOURCE_DIR}/subscription.js
                      ${CMAKE_CURRENT_SOURCE_DIR}/message.js
                      ${CMAKE_CURRENT_SOURCE_DIR}/data.js
                      ${CMAKE_CURRENT_SOURCE_DIR}/data-uuid.js
                      ${CMAKE_CURRENT_SOURCE_DIR}/data-symbol.js
                      ${CMAKE_CURRENT_SOURCE_DIR}/data-described.js
                      ${CMAKE_CURRENT_SOURCE_DIR}/data-array.js
                      ${CMAKE_CURRENT_SOURCE_DIR}/data-typed-number.js
                      ${CMAKE_CURRENT_SOURCE_DIR}/data-long.js
                      ${CMAKE_CURRENT_SOURCE_DIR}/data-binary.js)
    add_dependencies(docs docs-js)
endif (NODE_JSDOC_FOUND)

