# 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.

cmake_minimum_required(VERSION 2.8)

# Declare TMB project.
project(tmb CXX C)

# Look for flags to enable C++11 support, if any.
include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_HAS_STD_CXX11)
if (COMPILER_HAS_STD_CXX11)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
else()
  CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_HAS_STD_CXX0X)
  if (COMPILER_HAS_STD_CXX0X)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
  endif()
endif()

if (WIN32)
  # Disable warnings about using standard C library functions on Windows.
  set_property(DIRECTORY
               APPEND PROPERTY COMPILE_DEFINITIONS _CRT_SECURE_NO_WARNINGS)
endif()

# Turn on all warnings.
if (NOT MSVC)
  # We don't do -Wall on MSVC, because that is interpreted as /W4, which in
  # turn implies /WX, meaning that warnings are treated as errors.
  CHECK_CXX_COMPILER_FLAG("-Wall" COMPILER_HAS_WALL)
  if (COMPILER_HAS_WALL)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
  endif()
  CHECK_CXX_COMPILER_FLAG("-pedantic" COMPILER_HAS_PEDANTIC)
  if (COMPILER_HAS_PEDANTIC)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic")
  endif()
endif()

# For release builds, disable warnings about unused variables that are only
# used in assertions.
CHECK_CXX_COMPILER_FLAG("-Wno-unused-variable"
                        COMPILER_HAS_WNO_UNUSED_VARIABLE)
if (COMPILER_HAS_WNO_UNUSED_VARIABLE)
  set(CMAKE_CXX_FLAGS_RELEASE
      "${CMAKE_CXX_FLAGS_RELEASE} -Wno-unused-variable")
endif()
CHECK_CXX_COMPILER_FLAG("-Wno-unused-but-set-variable"
                        COMPILER_HAS_WNO_UNUSED_BUT_SET_VARIABLE)
if (COMPILER_HAS_WNO_UNUSED_BUT_SET_VARIABLE)
  set(CMAKE_CXX_FLAGS_RELEASE
      "${CMAKE_CXX_FLAGS_RELEASE} -Wno-unused-but-set-variable")
endif()

# Find the system's threading library.
find_package(Threads REQUIRED)

option(BUILD_BENCHMARKS "Build TMB benchmarks in addition to core library" OFF)

option(ENABLE_LEVELDB "Enable LevelDBMessageBus implementation" OFF)
option(ENABLE_MEMORYMIRROR "Enable MemoryMirrorMessageBus implementation" ON)
option(
    ENABLE_NATIVELOG
    "Enable NativeTransactionLog for persistence with MemoryMirrorMessageBus"
    ON)
option(ENABLE_NATIVENET "Enable native network protocol" ON)
option(ENABLE_PUREMEMORY "Enable PureMemoryMessageBus implementation" ON)
option(ENABLE_SQLITE "Enable SQLiteMessageBus implementation" OFF)
option(ENABLE_VOLTDB "Enable VoltDBMessageBus implementation" OFF)
option(ENABLE_ZOOKEEPER "Enable ZookeeperMessageBus implementation" OFF)

set(VOLTDB_CLASSPATH "/opt/voltdb/voltdb/*"
                     CACHE STRING "Classpath for VoltDB jars")
set(VOLTDB_COMMAND "/opt/voltdb/bin/voltdb"
                   CACHE STRING "Path for voltdb command")

# Find necessary third-party libraries.
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")

set(TMB_SRCS src/message_bus.cc)
set(TMB_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include)

if (ENABLE_LEVELDB)
  set_property(
      DIRECTORY
      APPEND PROPERTY COMPILE_DEFINITIONS TMB_LEVELDB_ENABLED)

  set(TMB_SRCS ${TMB_SRCS}
               src/leveldb_key_comparator.cc
               src/leveldb_message_bus.cc)

  find_package(LevelDB REQUIRED)
  set(TMB_INCLUDE_DIRS ${TMB_INCLUDE_DIRS}
                       ${LEVELDB_INCLUDE_DIRS})
  include_directories(${LEVELDB_INCLUDE_DIRS})
endif()

if (ENABLE_PUREMEMORY OR ENABLE_MEMORYMIRROR)
  set(TMB_SRCS ${TMB_SRCS}
               src/heap_receiver_message_queue.cc
               src/memory_based_message_bus.cc
               src/tree_receiver_message_queue.cc)
endif()

if (ENABLE_MEMORYMIRROR)
  set_property(
      DIRECTORY
      APPEND PROPERTY COMPILE_DEFINITIONS TMB_MEMORYMIRROR_ENABLED)

  set(TMB_SRCS ${TMB_SRCS}
               src/memory_mirror_message_bus.cc)
endif()

if (ENABLE_NATIVELOG)
  if (NOT ENABLE_MEMORYMIRROR)
    message(FATAL_ERROR
            "ENABLE_NATIVELOG is turned on but ENABLE_MEMORYMIRROR is turned "
            "off. The NativeTransactionLog is useless without "
            "MemoryMirrorMessageBus.")
  endif()

  set_property(
      DIRECTORY
      APPEND PROPERTY COMPILE_DEFINITIONS TMB_NATIVELOG_ENABLED)

  # Check for necessary POSIX I/O syscalls.
  include(CheckCXXSourceCompiles)
  CHECK_CXX_SOURCE_COMPILES("
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>

int main() {
  int fd = open(\"/dev/null\", O_WRONLY, S_IRUSR | S_IWUSR);

  off_t endpos = lseek(fd, 0, SEEK_END);
  ftruncate(fd, endpos + 10);

  struct iovec desc;
  desc.iov_base = nullptr;
  desc.iov_len = 0;
  writev(fd, &desc, 1);

  close(fd);

  return 0;
}
" POSIX_IO_SUPPORTED)
  if (POSIX_IO_SUPPORTED)
    set_property(DIRECTORY
                 APPEND PROPERTY COMPILE_DEFINITIONS TMB_POSIX_IO_SUPPORTED)

    # Check for fdatasync() syscall. It is part of the POSIX realtime
    # extensions, not the core spec. Linux has it, for example, but FreeBSD
    # does not.
    include(CheckSymbolExists)
    CHECK_SYMBOL_EXISTS(fdatasync "unistd.h" POSIX_FDATASYNC_SUPPORTED)
    if (POSIX_FDATASYNC_SUPPORTED)
      set_property(DIRECTORY
                   APPEND PROPERTY COMPILE_DEFINITIONS TMB_POSIX_FDATASYNC_SUPPORTED)
    else()
      # Alternately, check for F_FULLFSYNC fcntl on Mac.
      CHECK_SYMBOL_EXISTS(F_FULLFSYNC "fcntl.h" DARWIN_FFULLFSYNC_SUPPORTED)
      if (DARWIN_FFULLSYNC_SUPPORTED)
        set_property(DIRECTORY
                     APPEND PROPERTY COMPILE_DEFINITIONS TMB_DARWIN_FFULLFSYNC_SUPPORTED)
      endif()
    endif()

    set(TMB_SRCS ${TMB_SRCS}
                 src/log_reader_posix.cc
                 src/log_writer_posix.cc)
  else()
    message(WARNING "POSIX.1-2001 I/O support is not available. Building SLOW "
                    "stdio-based native transaction log.")

    # Try to find a way to sync stdio file streams.
    CHECK_CXX_SOURCE_COMPILES("
#include <unistd.h>
#include <cstdio>

int main() {
  FILE *file = std::fopen(\"/dev/null\", \"wb\");
  std::fflush(file);
  int fd = fileno(file);
  fdatasync(fd);
  std::fclose(file);

  return 0;
}
" POSIX_FDATASYNC_SUPPORTED)

    if (NOT POSIX_FDATASYNC_SUPPORTED)
      CHECK_CXX_SOURCE_COMPILES("
#include <unistd.h>
#include <cstdio>

int main() {
  FILE *file = std::fopen(\"/dev/null\", \"wb\");
  std::fflush(file);
  int fd = fileno(file);
  fsync(fd);
  std::fclose(file);

  return 0;
}
" POSIX_FSYNC_SUPPORTED)
    endif()

    if (NOT (POSIX_FDATASYNC_SUPPORTED OR POSIX_FSYNC_SUPPORTED))
      CHECK_CXX_SOURCE_COMPILES("
#define WIN32_LEAN_AND_MEAN
#include <io.h>
#include <stdio.h>
#include <windows.h>

int main() {
  FILE *file = fopen(\"/dev/null\", \"wb\");
  fflush(file);
  HANDLE filehandle = reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(file)));
  FlushFileBuffers(filehandle);
  fclose(file);

  return 0;
}
" WINDOWS_FLUSHFILEBUFFERS_SUPPORTED)
    endif()

    if (POSIX_FDATASYNC_SUPPORTED)
      set_property(
          DIRECTORY
          APPEND PROPERTY COMPILE_DEFINITIONS TMB_POSIX_FDATASYNC_SUPPORTED)
    elseif (POSIX_FSYNC_SUPPORTED)
      set_property(
          DIRECTORY
          APPEND PROPERTY COMPILE_DEFINITIONS TMB_POSIX_FSYNC_SUPPORTED)
    elseif (WINDOWS_FLUSHFILEBUFFERS_SUPPORTED)
      set_property(
          DIRECTORY
          APPEND PROPERTY COMPILE_DEFINITIONS TMB_WINDOWS_FLUSHFILEBUFFERS_SUPPORTED)
    else()
      message(WARNING "Can not find any way to synchronously flush file "
                      "buffers to disk (tried POSIX fsync() and fdatasync(), "
                      "Windows FlushFileBuffers()). Synchronous logging will "
                      "NOT be available.")
    endif()

    set(TMB_SRCS ${TMB_SRCS}
                 src/log_reader_stdio.cc
                 src/log_writer_stdio.cc)
  endif()

  set(TMB_SRCS ${TMB_SRCS}
               src/crc32.cc
               src/native_transaction_log.cc)
endif()

if (ENABLE_NATIVENET)
  set_property(
      DIRECTORY
      APPEND PROPERTY COMPILE_DEFINITIONS TMB_NATIVENET_ENABLED)

  find_package(Grpc++ REQUIRED)
  include_directories(${GRPCPLUSPLUS_INCLUDE_DIRS})

  GRPC_GENERATE_CPP(tmb_net_srcs tmb_net_hdrs include/tmb/internal src/proto/tmb_net.proto)
  include_directories(${CMAKE_CURRENT_BINARY_DIR}/include)

  set(TMB_SRCS ${TMB_SRCS}
               src/native_net_client_message_bus.cc
               src/net_service_impl.cc
               ${tmb_net_srcs})
endif()

if (ENABLE_PUREMEMORY)
  set_property(
      DIRECTORY
      APPEND PROPERTY COMPILE_DEFINITIONS TMB_PUREMEMORY_ENABLED)

  set(TMB_SRCS ${TMB_SRCS}
               src/pure_memory_message_bus.cc)
endif()

if (ENABLE_SQLITE)
  set_property(
      DIRECTORY
      APPEND PROPERTY COMPILE_DEFINITIONS TMB_SQLITE_ENABLED)

  set(TMB_SRCS ${TMB_SRCS}
               src/sqlite_connection.cc
               src/sqlite_message_bus.cc)

  find_package(SQLite3 REQUIRED)
  set(TMB_INCLUDE_DIRS ${TMB_INCLUDE_DIRS}
                       ${SQLITE3_INCLUDE_DIRS})
  include_directories(${SQLITE3_INCLUDE_DIRS})
endif()

if (ENABLE_VOLTDB)
  set_property(
      DIRECTORY
      APPEND PROPERTY COMPILE_DEFINITIONS TMB_VOLTDB_ENABLED)

  set(TMB_SRCS ${TMB_SRCS}
               src/voltdb_connection_pool.cc
               src/voltdb_message_bus.cc
               src/voltdb_procedure_factory.cc)

  # VoltDB C++ bindings.
  find_package(VoltDB REQUIRED)
  set(TMB_INCLUDE_DIRS ${TMB_INCLUDE_DIRS}
                       ${VOLTDB_INCLUDE_DIRS})
  include_directories(${VOLTDB_INCLUDE_DIRS})

  find_package(Java 1.7 REQUIRED)
  include(UseJava)

  # Compile Java stored procedures.
  set(CMAKE_JAVA_INCLUDE_PATH ${VOLTDB_CLASSPATH})
  add_jar(tmb_voltdb_stored_procedures
          src/java/CancelMessages.java
          src/java/ConnectClient.java
          src/java/DeleteMessages.java
          src/java/DeleteMessagesUnchecked.java
          src/java/DisconnectClient.java
          src/java/LoadState.java
          src/java/Receive.java
          src/java/ReceiveAndDelete.java
          src/java/RegisterReceiver.java
          src/java/RegisterSender.java
          src/java/ResetBus.java
          src/java/SendToAny.java
          src/java/SendToExplicitReceivers.java
          src/java/SendToExplicitReceiversUnchecked.java
          src/java/SendToSingleExplicitReceiver.java
          src/java/SendToSingleExplicitReceiverUnchecked.java)

  # Compile VoltDB catalog.
  add_custom_command(
      OUTPUT tmb_voltdb.jar
      COMMAND ${VOLTDB_COMMAND}
      ARGS compile --classpath=${CMAKE_CURRENT_BINARY_DIR}/tmb_voltdb_stored_procedures.jar -o tmb_voltdb.jar ${CMAKE_CURRENT_SOURCE_DIR}/src/sql/voltdb_schema.sql
      DEPENDS src/sql/voltdb_schema.sql src/java/* tmb_voltdb_stored_procedures
      COMMENT "Compiling VoltDB Catalog")
  add_custom_target(forcebuild ALL DEPENDS tmb_voltdb.jar)
endif()

if (ENABLE_ZOOKEEPER)
  set_property(
      DIRECTORY
      APPEND PROPERTY COMPILE_DEFINITIONS TMB_ZOOKEEPER_ENABLED)

  set(TMB_SRCS ${TMB_SRCS}
               src/zookeeper_message_bus.cc
               src/zookeeper_transaction_batch.cc)

  find_package(Zookeeper REQUIRED)
  set(TMB_INCLUDE_DIRS ${TMB_INCLUDE_DIRS}
                       ${ZOOKEEPER_INCLUDE_DIRS})
  include_directories(${ZOOKEEPER_INCLUDE_DIRS})
endif()

set_gflags_lib_name ()

# Include path for TMB.
include_directories(${PROJECT_SOURCE_DIR}/include)
set(TMB_INCLUDE_DIRS ${TMB_INCLUDE_DIRS} CACHE STRING
    "Required include paths for applications using TMB")

# Where TMB libraries will be found.
link_directories(${tmb_BINARY_DIR}/src)

# Build TMB library.
add_library(tmb
            ${TMB_SRCS})
target_link_libraries(tmb
                      ${CMAKE_THREAD_LIBS_INIT}
                      ${GFLAGS_LIB_NAME})

if (ENABLE_LEVELDB)
  target_link_libraries(tmb
                        ${LEVELDB_LIBRARIES})
endif()

if (ENABLE_NATIVENET)
  target_link_libraries(tmb
                        ${GRPCPLUSPLUS_LIBRARIES})
endif()

if (ENABLE_SQLITE)
  target_link_libraries(tmb
                        ${SQLITE3_LIBRARIES})
endif()

if (ENABLE_VOLTDB)
  target_link_libraries(tmb
                        ${VOLTDB_LIBRARIES})
endif()

if (ENABLE_ZOOKEEPER)
  target_link_libraries(tmb
                        ${ZOOKEEPER_LIBRARIES})
endif()

# Build the tmb_net_server executable if enabled.
if (ENABLE_NATIVENET)
  add_executable(tmb_net_server src/tmb_net_server.cc)
  target_link_libraries(tmb_net_server
                        tmb
                        ${GFLAGS_LIB_NAME})
endif()

if (BUILD_BENCHMARKS)
  add_subdirectory(benchmarks)
endif()

# Tests
include_directories(${PROJECT_SOURCE_DIR})

# Internal components
add_executable(rcu_unittest
               tests/rcu_unittest.cc)
target_link_libraries(rcu_unittest
                      gtest
                      gtest_main)
add_test(rcu_unittest rcu_unittest)

# Message Bus Implementations
if (ENABLE_LEVELDB)
  add_executable(leveldb_message_bus_unittest
                 tests/leveldb_message_bus_unittest.cc)
  target_link_libraries(leveldb_message_bus_unittest
                        gtest
                        gtest_main
                        tmb)
  add_test(leveldb_message_bus_unittest leveldb_message_bus_unittest)

  add_executable(leveldb_message_bus_async_unittest
                 tests/leveldb_message_bus_async_unittest.cc)
  target_link_libraries(leveldb_message_bus_async_unittest
                        gtest
                        gtest_main
                        tmb)
  add_test(leveldb_message_bus_async_unittest
           leveldb_message_bus_async_unittest)
endif()

if (ENABLE_MEMORYMIRROR AND ENABLE_LEVELDB)
  add_executable(memory_mirror_message_bus_with_leveldb_unittest
                 tests/memory_mirror_message_bus_with_leveldb_unittest.cc)
  target_link_libraries(memory_mirror_message_bus_with_leveldb_unittest
                        gtest
                        gtest_main
                        tmb)
  add_test(memory_mirror_message_bus_with_leveldb_unittest
           memory_mirror_message_bus_with_leveldb_unittest)
endif()

if (ENABLE_MEMORYMIRROR AND ENABLE_NATIVELOG)
  add_executable(native_logging_message_bus_unittest
                 tests/native_logging_message_bus_unittest.cc)
  target_link_libraries(native_logging_message_bus_unittest
                        gtest
                        gtest_main
                        tmb)
  add_test(native_logging_message_bus_unittest
           native_logging_message_bus_unittest)

  add_executable(native_logging_message_bus_async_unittest
                 tests/native_logging_message_bus_async_unittest.cc)
  target_link_libraries(native_logging_message_bus_async_unittest
                        gtest
                        gtest_main
                        tmb)
  add_test(native_logging_message_bus_async_unittest
           native_logging_message_bus_async_unittest)
endif()

if (ENABLE_MEMORYMIRROR AND ENABLE_SQLITE)
  add_executable(memory_mirror_message_bus_with_sqlite_unittest
                 tests/memory_mirror_message_bus_with_sqlite_unittest.cc)
  target_link_libraries(memory_mirror_message_bus_with_sqlite_unittest
                        gtest
                        gtest_main
                        tmb)
  add_test(memory_mirror_message_bus_with_sqlite_unittest
           memory_mirror_message_bus_with_sqlite_unittest)
endif()

if (ENABLE_MEMORYMIRROR AND ENABLE_VOLTDB)
  add_executable(memory_mirror_message_bus_with_voltdb_unittest
                 tests/memory_mirror_message_bus_with_voltdb_unittest.cc)
  target_link_libraries(memory_mirror_message_bus_with_voltdb_unittest
                        gtest
                        gtest_main
                        tmb)
  add_test(memory_mirror_message_bus_with_voltdb_unittest
           memory_mirror_message_bus_with_voltdb_unittest)
endif()

if (ENABLE_MEMORYMIRROR AND ENABLE_ZOOKEEPER)
  add_executable(memory_mirror_message_bus_with_zookeeper_unittest
                 tests/memory_mirror_message_bus_with_zookeeper_unittest.cc)
  target_link_libraries(memory_mirror_message_bus_with_zookeeper_unittest
                        gtest
                        gtest_main
                        tmb)
  add_test(memory_mirror_message_bus_with_zookeeper_unittest
           memory_mirror_message_bus_with_zookeeper_unittest)
endif()

if (ENABLE_NATIVENET)
  add_executable(native_net_client_message_bus_unittest
                 tests/native_net_client_message_bus_unittest.cc)
  target_link_libraries(native_net_client_message_bus_unittest
                        gtest
                        gtest_main
                        tmb)
  add_test(native_net_client_message_bus_unittest
           native_net_client_message_bus_unittest)
endif()

if (ENABLE_PUREMEMORY)
  add_executable(pure_memory_message_bus_unittest
                 tests/pure_memory_message_bus_unittest.cc)
  target_link_libraries(pure_memory_message_bus_unittest
                        gtest
                        gtest_main
                        tmb)
  add_test(pure_memory_message_bus_unittest pure_memory_message_bus_unittest)
endif()

if (ENABLE_SQLITE)
  add_executable(sqlite_message_bus_unittest
                 tests/sqlite_message_bus_unittest.cc)
  target_link_libraries(sqlite_message_bus_unittest
                        gtest
                        gtest_main
                        tmb)
  add_test(sqlite_message_bus_unittest sqlite_message_bus_unittest)
endif()

if (ENABLE_VOLTDB)
  add_executable(voltdb_message_bus_unittest
                 tests/voltdb_message_bus_unittest.cc)
  target_link_libraries(voltdb_message_bus_unittest
                        gtest
                        gtest_main
                        tmb)
  add_test(voltdb_message_bus_unittest voltdb_message_bus_unittest)
endif()

if (ENABLE_ZOOKEEPER)
  add_executable(zookeeper_message_bus_unittest
                 tests/zookeeper_message_bus_unittest.cc)
  target_link_libraries(zookeeper_message_bus_unittest
                        gtest
                        gtest_main
                        tmb)
  add_test(zookeeper_message_bus_unittest zookeeper_message_bus_unittest)
endif()
