// Copyright 2021 the Tectonic Project
// Licensed under the MIT License.

use std::{env, path::PathBuf};
use tectonic_cfg_support::*;

fn main() {
    let target = env::var("TARGET").unwrap();

    // Include paths exported by our internal dependencies.

    let xetex_layout_include_path = env::var("DEP_TECTONIC_XETEX_LAYOUT_INCLUDE_PATH").unwrap();
    let pdfio_include_path = env::var("DEP_TECTONIC_PDF_IO_INCLUDE_PATH").unwrap();
    let core_include_dir = env::var("DEP_TECTONIC_BRIDGE_CORE_INCLUDE").unwrap();
    let flate_include_dir = env::var("DEP_TECTONIC_BRIDGE_FLATE_INCLUDE").unwrap();
    let graphite2_include_path = env::var("DEP_GRAPHITE2_INCLUDE_PATH").unwrap();
    let graphite2_static = !env::var("DEP_GRAPHITE2_DEFINE_STATIC").unwrap().is_empty();
    let harfbuzz_include_path = env::var("DEP_HARFBUZZ_INCLUDE_PATH").unwrap();

    // If we want to profile, the default assumption is that we must force the
    // compiler to include frame pointers. We whitelist platforms that are
    // known to be able to profile *without* frame pointers: currently, only
    // Linux/x86_64.

    let profile_target_requires_frame_pointer: bool =
        target_cfg!(not(all(target_os = "linux", target_arch = "x86_64")));

    const PROFILE_BUILD_ENABLED: bool = cfg!(feature = "profile");

    let profile_config = |cfg: &mut cc::Build| {
        if PROFILE_BUILD_ENABLED {
            cfg.debug(true)
                .force_frame_pointer(profile_target_requires_frame_pointer);
        }
    };

    // Time to set up the C/C++ support libraries.

    let mut c_cfg = cc::Build::new();
    let mut cxx_cfg = cc::Build::new();

    cxx_cfg.cpp(true);

    for flag in C_FLAGS {
        c_cfg.flag_if_supported(flag);
    }

    for flag in CXX_FLAGS {
        cxx_cfg.flag_if_supported(flag);
    }

    profile_config(&mut c_cfg);
    profile_config(&mut cxx_cfg);

    for p in &[".", &core_include_dir, &flate_include_dir] {
        c_cfg.include(p);
        cxx_cfg.include(p);
    }

    for item in xetex_layout_include_path.split(';') {
        c_cfg.include(item);
        cxx_cfg.include(item);
    }

    for item in pdfio_include_path.split(';') {
        c_cfg.include(item);
        cxx_cfg.include(item);
    }

    for item in harfbuzz_include_path.split(';') {
        c_cfg.include(item);
        cxx_cfg.include(item);
    }

    for item in graphite2_include_path.split(';') {
        c_cfg.include(item);
        cxx_cfg.include(item);
    }

    if graphite2_static {
        c_cfg.define("GRAPHITE2_STATIC", "1");
        cxx_cfg.define("GRAPHITE2_STATIC", "1");
    }

    // Platform-specific adjustments:

    let is_mac_os = target_cfg!(target_os = "macos");

    if is_mac_os {
        c_cfg.define("XETEX_MAC", Some("1"));
        c_cfg.file("xetex/xetex-macos.c");
        cxx_cfg.define("XETEX_MAC", Some("1"));
    }

    let is_big_endian = target_cfg!(target_endian = "big");
    if is_big_endian {
        c_cfg.define("WORDS_BIGENDIAN", "1");
        cxx_cfg.define("WORDS_BIGENDIAN", "1");
    }

    if target.contains("-msvc") {
        c_cfg.flag("/EHsc");
        cxx_cfg.flag("/EHsc");
    }

    // OK, back to generic build rules.

    for file in C_FILES {
        c_cfg.file(file);
    }

    for file in CXX_FILES {
        cxx_cfg.file(file);
    }

    c_cfg.compile("libtectonic_engine_xetex_c.a");
    cxx_cfg.compile("libtectonic_engine_xetex_cxx.a");

    // Rebuild if C/C++ files have changed. We scan the whole directory to get
    // the headers too.

    for file in PathBuf::from("xetex").read_dir().unwrap() {
        let file = file.unwrap();
        println!("cargo:rerun-if-changed={}", file.path().display());
    }
}

const C_FLAGS: &[&str] = &[
    "-Wall",
    "-Wcast-qual",
    "-Wdate-time",
    "-Wendif-labels",
    "-Wextra",
    "-Wextra-semi",
    "-Wformat=2",
    "-Winit-self",
    "-Wlogical-op",
    "-Wmissing-declarations",
    "-Wmissing-include-dirs",
    "-Wmissing-prototypes",
    "-Wmissing-variable-declarations",
    "-Wnested-externs",
    "-Wold-style-definition",
    "-Wpointer-arith",
    "-Wredundant-decls",
    "-Wstrict-prototypes",
    "-Wsuggest-attribute=format",
    "-Wswitch-bool",
    "-Wundef",
    "-Wwrite-strings",
    // TODO: Fix existing warnings before enabling these:
    // "-Wbad-function-cast",
    // "-Wcast-align",
    // "-Wconversion",
    // "-Wdouble-promotion",
    // "-Wshadow",
    // "-Wsuggest-attribute=const",
    // "-Wsuggest-attribute=noreturn",
    // "-Wsuggest-attribute=pure",
    // "-Wunreachable-code-aggresive",
    "-Wno-unused-parameter",
    "-Wno-implicit-fallthrough",
    "-Wno-sign-compare",
    "-std=gnu11",
];

const C_FILES: &[&str] = &[
    "xetex/xetex-engine-interface.c",
    "xetex/xetex-errors.c",
    "xetex/xetex-ext.c",
    "xetex/xetex-ini.c",
    "xetex/xetex-io.c",
    "xetex/xetex-linebreak.c",
    "xetex/xetex-math.c",
    "xetex/xetex-output.c",
    "xetex/xetex-pagebuilder.c",
    "xetex/xetex-pic.c",
    "xetex/xetex-scaledmath.c",
    "xetex/xetex-shipout.c",
    "xetex/xetex-stringpool.c",
    "xetex/xetex-synctex.c",
    "xetex/xetex-texmfmp.c",
    "xetex/xetex-xetex0.c",
];

const CXX_FLAGS: &[&str] = &[
    "-std=c++14",
    "-Wall",
    "-Wdate-time",
    "-Wendif-labels",
    "-Wextra",
    "-Wformat=2",
    "-Wlogical-op",
    "-Wmissing-declarations",
    "-Wmissing-include-dirs",
    "-Wpointer-arith",
    "-Wredundant-decls",
    "-Wsuggest-attribute=noreturn",
    "-Wsuggest-attribute=format",
    "-Wshadow",
    "-Wswitch-bool",
    "-Wundef",
    // TODO: Fix existing warnings before enabling these:
    // "-Wdouble-promotion",
    // "-Wcast-align",
    // "-Wconversion",
    // "-Wmissing-variable-declarations",
    "-Wextra-semi",
    // "-Wsuggest-attribute=const",
    // "-Wsuggest-attribute=pure",
    // "-Wunreachable-code-aggresive",
    "-Wno-unused-parameter",
    "-Wno-implicit-fallthrough",
    "-fno-exceptions",
    "-fno-rtti",
];

const CXX_FILES: &[&str] = &["xetex/teckit-Engine.cpp", "xetex/xetex-XeTeXOTMath.cpp"];
