cmake_minimum_required(VERSION 3.10)

project(SFST
    VERSION 1.4
    DESCRIPTION "Stuttgart Finite State Transducer Tools"
    LANGUAGES CXX)

# ── Build type ────────────────────────────────────────────────────────────────
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release CACHE STRING
        "Build type: Debug, Release, RelWithDebInfo, MinSizeRel" FORCE)
endif()

# ── C++ standard ──────────────────────────────────────────────────────────────
# gnu++14 (extensions on) is required for __gnu_cxx::hash_map from <ext/hash_map>
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON)

# ── Options ───────────────────────────────────────────────────────────────────
option(WITH_READLINE        "Enable readline support in fst-mor"          ON)
option(BUILD_SHARED_LIBRARY "Build the libsfst shared library"            OFF)
option(REGENERATE_PARSER    "Re-run bison/flex to regenerate sources"     OFF)

# ── Compiler flags ────────────────────────────────────────────────────────────
add_compile_options(
    -Wall
    -Wno-deprecated
    -Wno-deprecated-declarations
)
# Release builds: optimise; Debug: keep symbols
if(CMAKE_BUILD_TYPE STREQUAL "Release")
    add_compile_options(-O3)
endif()

# ── Compile definitions ───────────────────────────────────────────────────────
# SGI__gnu_cxx   → use __gnu_cxx::hash_map  (see src/sgi.h)
add_definitions(-DSGI__gnu_cxx)

# ── Readline ──────────────────────────────────────────────────────────────────
set(READLINE_LIBS "")
if(WITH_READLINE)
    find_library(READLINE_LIB NAMES readline)
    find_library(NCURSES_LIB  NAMES ncurses tinfo curses)
    if(READLINE_LIB AND NCURSES_LIB)
        set(READLINE_LIBS "${READLINE_LIB};${NCURSES_LIB}")
        add_definitions(-DREADLINE)
        message(STATUS "readline : enabled  (${READLINE_LIB})")
    else()
        message(STATUS "readline : not found — fst-mor built without it")
    endif()
else()
    message(STATUS "readline : disabled")
endif()

# ── Optional: regenerate parser/scanner from sources ─────────────────────────
# Off by default because the generated files (fst-compiler.C, default-scanner.C,
# utf8-scanner.C) are already included in the source tree.
if(REGENERATE_PARSER)
    find_package(BISON 3.0 REQUIRED)
    find_package(FLEX        REQUIRED)

    # Parser (bison)
    # Note: DEFINES_FILE requires CMake ≥ 3.14; adjust if on an older system.
    BISON_TARGET(FSTParser
        ${CMAKE_CURRENT_SOURCE_DIR}/src/fst-compiler.yy
        ${CMAKE_CURRENT_SOURCE_DIR}/src/fst-compiler.C
        COMPILE_FLAGS "-d")

    # Derive per-mode scanner input from the unified scanner.ll
    set(_SC ${CMAKE_CURRENT_SOURCE_DIR}/src/scanner.ll)

    add_custom_command(
        OUTPUT  ${CMAKE_CURRENT_SOURCE_DIR}/src/default-scanner.ll
        COMMAND sed
                -e "/^utf8>/d"
                -e "s/^default> *//"
                ${_SC}
                > ${CMAKE_CURRENT_SOURCE_DIR}/src/default-scanner.ll
        DEPENDS ${_SC}
        COMMENT "Generating default-scanner.ll from scanner.ll")

    add_custom_command(
        OUTPUT  ${CMAKE_CURRENT_SOURCE_DIR}/src/utf8-scanner.ll
        COMMAND sed
                -e "/^default>/d"
                -e "s/^utf8> *//"
                ${_SC}
                > ${CMAKE_CURRENT_SOURCE_DIR}/src/utf8-scanner.ll
        DEPENDS ${_SC}
        COMMENT "Generating utf8-scanner.ll from scanner.ll")

    FLEX_TARGET(DefaultScanner
        ${CMAKE_CURRENT_SOURCE_DIR}/src/default-scanner.ll
        ${CMAKE_CURRENT_SOURCE_DIR}/src/default-scanner.C)
    FLEX_TARGET(UTF8Scanner
        ${CMAKE_CURRENT_SOURCE_DIR}/src/utf8-scanner.ll
        ${CMAKE_CURRENT_SOURCE_DIR}/src/utf8-scanner.C)

    ADD_FLEX_BISON_DEPENDENCY(DefaultScanner FSTParser)
    ADD_FLEX_BISON_DEPENDENCY(UTF8Scanner    FSTParser)
endif()

# ── Short path alias ──────────────────────────────────────────────────────────
set(S ${CMAKE_CURRENT_SOURCE_DIR}/src)

# ── Common object library ─────────────────────────────────────────────────────
# These seven translation units are linked into almost every program.
# Compiling them once as an OBJECT library avoids redundant rebuilds.
add_library(sfst_obj OBJECT
    ${S}/basic.C
    ${S}/utf8.C
    ${S}/alphabet.C
    ${S}/fst.C
    ${S}/operators.C
    ${S}/determinise.C
    ${S}/hopcroft.C)
target_include_directories(sfst_obj PUBLIC ${S})

# ── Helper function ───────────────────────────────────────────────────────────
# sfst_program(<name>
#              SOURCES  <src...>
#              [NO_COMMON]            # do not link sfst_obj (custom source set)
#              [EXTRA_LIBS <lib...>]) # additional link libraries
function(sfst_program name)
    cmake_parse_arguments(P "NO_COMMON" "" "SOURCES;EXTRA_LIBS" ${ARGN})

    if(P_NO_COMMON)
        add_executable(${name} ${P_SOURCES})
    else()
        add_executable(${name} ${P_SOURCES} $<TARGET_OBJECTS:sfst_obj>)
    endif()

    target_include_directories(${name} PRIVATE ${S})

    if(P_EXTRA_LIBS)
        target_link_libraries(${name} PRIVATE ${P_EXTRA_LIBS})
    endif()

    install(TARGETS ${name} RUNTIME DESTINATION bin)
endfunction()

# ── Programs: use sfst_obj ────────────────────────────────────────────────────

# Simple tools: sfst_obj + one main file
sfst_program(fst-compare  SOURCES ${S}/fst-compare.C)
sfst_program(fst-print    SOURCES ${S}/fst-print.C)
sfst_program(fst-lowmem   SOURCES ${S}/fst-lowmem.C)
sfst_program(fst-lattice  SOURCES ${S}/fst-lattice.C)
sfst_program(fst-parse    SOURCES ${S}/fst-parse.C)
sfst_program(fst-parse2   SOURCES ${S}/fst-parse2.C)
sfst_program(fst-infl     SOURCES ${S}/fst-infl.C)
sfst_program(fst-text2bin SOURCES ${S}/fst-text2bin.C)

# Tools needing extra implementation files
sfst_program(fst-generate SOURCES ${S}/fst-generate.C  ${S}/generate.C)
sfst_program(fst-compact  SOURCES ${S}/fst-compact.C   ${S}/compact.C   ${S}/make-compact.C)
sfst_program(fst-match    SOURCES ${S}/fst-match.C     ${S}/compact.C)
sfst_program(fst-train    SOURCES ${S}/fst-train.C     ${S}/compact.C)

# fst-mor needs readline (linked conditionally)
sfst_program(fst-mor      SOURCES ${S}/fst-mor.C       EXTRA_LIBS ${READLINE_LIBS})

# ── Programs: custom common-source sets ───────────────────────────────────────
# These use a different subset of the base sources (not the full sfst_obj set).

# fst-infl2: alphabet + basic + compact + robust + utf8
sfst_program(fst-infl2
    NO_COMMON
    SOURCES
        ${S}/fst-infl2.C
        ${S}/alphabet.C
        ${S}/basic.C
        ${S}/utf8.C
        ${S}/compact.C
        ${S}/robust.C)

# fst-infl2-daemon: same source set as fst-infl2 (adds TCP socket server)
sfst_program(fst-infl2-daemon
    NO_COMMON
    SOURCES
        ${S}/fst-infl2-daemon.C
        ${S}/alphabet.C
        ${S}/basic.C
        ${S}/utf8.C
        ${S}/compact.C
        ${S}/robust.C)

# fst-infl3: alphabet + basic + utf8 + lowmem (no fst.C / operators etc.)
sfst_program(fst-infl3
    NO_COMMON
    SOURCES
        ${S}/fst-infl3.C
        ${S}/alphabet.C
        ${S}/basic.C
        ${S}/utf8.C
        ${S}/lowmem.C)

# ── Compiler programs (use pre-generated bison/flex sources) ──────────────────
# fst-compiler.C  = bison output (parser + grammar)
# default-scanner.C / utf8-scanner.C = flex output (lexers)
# Both compiler binaries share the parser but differ in which lexer they use.

set(COMPILER_COMMON_SRC
    ${S}/fst-compiler.C
    ${S}/interface.C
    ${S}/compact.C
    ${S}/make-compact.C)

# fst-compiler  — ASCII / Latin-1 mode
add_executable(fst-compiler
    ${COMPILER_COMMON_SRC}
    ${S}/default-scanner.C
    $<TARGET_OBJECTS:sfst_obj>)
target_include_directories(fst-compiler PRIVATE ${S})
install(TARGETS fst-compiler RUNTIME DESTINATION bin)

# fst-compiler-utf8 — UTF-8 mode
add_executable(fst-compiler-utf8
    ${COMPILER_COMMON_SRC}
    ${S}/utf8-scanner.C
    $<TARGET_OBJECTS:sfst_obj>)
target_include_directories(fst-compiler-utf8 PRIVATE ${S})
install(TARGETS fst-compiler-utf8 RUNTIME DESTINATION bin)

# ── Optional shared library ───────────────────────────────────────────────────
if(BUILD_SHARED_LIBRARY)
    add_library(sfst SHARED
        ${S}/alphabet.C
        ${S}/basic.C
        ${S}/utf8.C
        ${S}/compact.C
        ${S}/fst.C
        ${S}/operators.C
        ${S}/determinise.C
        ${S}/generate.C
        ${S}/hopcroft.C)
    target_include_directories(sfst PUBLIC ${S})
    set_target_properties(sfst PROPERTIES
        VERSION   ${PROJECT_VERSION}
        SOVERSION ${PROJECT_VERSION_MAJOR})

    install(TARGETS sfst LIBRARY DESTINATION lib)
    install(FILES
        ${S}/alphabet.h
        ${S}/basic.h
        ${S}/compact.h
        ${S}/fst.h
        ${S}/interface.h
        ${S}/mem.h
        ${S}/sgi.h
        ${S}/utf8.h
        DESTINATION include/sfst)
endif()

# ── License ───────────────────────────────────────────────────────────────────
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/COPYING
    DESTINATION share/licenses/sfst
    RENAME COPYING)

# ── Man pages ─────────────────────────────────────────────────────────────────
install(FILES
    ${S}/man1/fst-compact.1
    ${S}/man1/fst-compare.1
    ${S}/man1/fst-compiler.1
    ${S}/man1/fst-compiler-utf8.1
    ${S}/man1/fst-generate.1
    ${S}/man1/fst-infl.1
    ${S}/man1/fst-infl2.1
    ${S}/man1/fst-infl3.1
    ${S}/man1/fst-lattice.1
    ${S}/man1/fst-lowmem.1
    ${S}/man1/fst-match.1
    ${S}/man1/fst-mor.1
    ${S}/man1/fst-parse.1
    ${S}/man1/fst-parse2.1
    ${S}/man1/fst-print.1
    ${S}/man1/fst-text2bin.1
    ${S}/man1/fst-train.1
    DESTINATION share/man/man1)

# ── Configuration summary ─────────────────────────────────────────────────────
message(STATUS "")
message(STATUS "SFST ${PROJECT_VERSION} — configuration summary")
message(STATUS "  Install prefix  : ${CMAKE_INSTALL_PREFIX}")
message(STATUS "  Build type      : ${CMAKE_BUILD_TYPE}")
message(STATUS "  C++ standard    : gnu++${CMAKE_CXX_STANDARD}")
message(STATUS "  readline        : ${READLINE_LIB}")
message(STATUS "  Shared libsfst  : ${BUILD_SHARED_LIBRARY}")
message(STATUS "  Regen parser    : ${REGENERATE_PARSER}")
message(STATUS "")
message(STATUS "Build:   cmake --build <build-dir> -j\$(nproc)")
message(STATUS "Install: cmake --install <build-dir>")
message(STATUS "")
