extern crate lazy_static;
extern crate pkg_config;
extern crate regex;

use lazy_static::lazy_static;
use pkg_config::Library;
use regex::Regex;

use std::{env, fs};
use std::path::Path;
use std::process::{Command,Stdio};

const NEWT_VERSION:   &str = "0.52.21";
const POPT_VERSION:   &str = "1.16";
const SLANG_VERSION:  &str = "2.3.2";

const OLD_CFLAGS_ENV: &str = "_OLD_CFLAGS";

lazy_static! {
    static ref MAKE: &'static str = find_gnu_make();
}

struct BuildConfig<'a> {
    build_prefix: &'a str,
    archive_name: &'a str,
    src_dir: &'a str,
    install_prefix: &'a str,
    pkg_config_path: &'a str
}

fn check_make(make: &str) -> bool {
    let cmd = Command::new(make)
        .stdin(Stdio::null())
        .args(&["-f", "-", "--version"])
        .output();

    match cmd {
        Ok(output) => {
            let re = Regex::new(r"\AGNU Make").unwrap();
            let s = String::from_utf8_lossy(output.stdout.as_slice());
            re.is_match(&s)
        },
        Err(_e) => false
    }
}

fn find_gnu_make() -> &'static str {
    for make in ["make", "gmake"].iter() {
        if check_make(make) {
            return make;
        }
    }
    panic!("GNU Make is required for building this package.");
}

fn build_newt(version: &str, cfg: &BuildConfig) -> Library {
    let archive = &format!("{}.tar.gz", cfg.archive_name);

    Command::new("tar").args(&["xzf", archive])
        .args(&["-C", cfg.build_prefix])
        .status().unwrap();

    env::set_current_dir(&Path::new(cfg.src_dir)).unwrap();
    Command::new("./configure")
        .args(&["--prefix", cfg.install_prefix])
        .arg("--disable-nls")
        .status().unwrap();

    Command::new(make())
        .arg("install")
        .status().unwrap();

    env::set_var("PKG_CONFIG_LIBDIR", cfg.pkg_config_path);
    pkg_config::Config::new()
        .atleast_version(version)
        .statik(true)
        .probe("libnewt").unwrap()
}

fn build_popt(version: &str, cfg: &BuildConfig) -> Library {
    let archive = &format!("{}.tar.gz", cfg.archive_name);
    Command::new("tar").args(&["xzf", archive])
        .args(&["-C", cfg.build_prefix])
        .status().unwrap();

    env::set_current_dir(&Path::new(cfg.src_dir)).unwrap();
    Command::new("./configure")
        .args(&["--prefix", cfg.install_prefix])
        .arg("--disable-nls")
        .arg("--disable-rpath")
        .status().unwrap();

    Command::new(make())
        .arg("install")
        .status().unwrap();

    env::set_var("PKG_CONFIG_LIBDIR", cfg.pkg_config_path);
    pkg_config::Config::new()
        .atleast_version(version)
        .arg("--cflags")
        .statik(true)
        .probe("popt").unwrap()
}

fn build_slang(version: &str, cfg: &BuildConfig) -> Library {
    let archive = &format!("{}.tar.bz2", cfg.archive_name);

    cflags_set_fpic();
    Command::new("tar").args(&["xjf", archive])
        .args(&["-C", cfg.build_prefix])
        .status().unwrap();

    env::set_current_dir(&Path::new(cfg.src_dir)).unwrap();
    Command::new("./configure")
        .args(&["--prefix", cfg.install_prefix])
        .status().unwrap();

    Command::new(make())
        .arg("install-static")
        .status().unwrap();

    cflags_restore();
    env::set_var("PKG_CONFIG_LIBDIR", cfg.pkg_config_path);
    pkg_config::Config::new()
        .atleast_version(version)
        .arg("--cflags")
        .statik(true)
        .probe("slang").unwrap()
}

#[inline]
fn make() -> &'static str {
    &MAKE
}

fn export_env_libs(libs: &[Box<Library>]) {
    let mut include_paths = String::new();
    let mut link_paths = String::new();

    for lib in libs {
        for ipath in lib.include_paths.iter() {
            if let Some(path) = ipath.to_str() {
                include_paths.push_str(&format!("-I{} ", path));
            }
        }

        for lpath in lib.link_paths.iter() {
            if let Some(path) = lpath.to_str() {
                link_paths.push_str(&format!("-L{} ", path));
            }
        }
    }

    if include_paths.len() > 0 {
        env::set_var("CPPFLAGS", include_paths)
    }

    if link_paths.len() > 0 {
        env::set_var("LDFLAGS", link_paths)
    }
}

fn unset_env_libs() {
    env::remove_var("CPPFLAGS");
    env::remove_var("LDFLAGS");
}

fn cflags_set_fpic() {
    let mut cflags = match env::var("CFLAGS") {
        Ok(val) => val,
        Err(_)  => String::new()
    };

    if !cflags.contains("-fPIC") {
        env::set_var(OLD_CFLAGS_ENV, &cflags);
        cflags.push_str(" -fPIC");
        env::set_var("CFLAGS", &cflags);
    }
}

fn cflags_restore() {
    if let Ok(old_cflags) = env::var(OLD_CFLAGS_ENV) {
        env::set_var("CFLAGS", &old_cflags);
        env::remove_var(OLD_CFLAGS_ENV);
    }
}

fn build(package: &str, version: &str, out_dir: &str,
         libs: Option<&[Box<Library>]>) -> Library {
    let crate_path = env::var("CARGO_MANIFEST_DIR").unwrap();
    let version_name = &format!("{}-{}", package, version);
    let build_prefix = &format!("{}/build", out_dir);
    let install_prefix = &format!("{}/install/{}", out_dir, version_name);

    let build_cfg = BuildConfig {
        build_prefix: &build_prefix,
        archive_name: &format!("{}/vendor/{}", crate_path, version_name),
        src_dir: &format!("{}/{}", build_prefix, version_name),
        install_prefix: &install_prefix,
        pkg_config_path: &format!("{}/lib/pkgconfig", install_prefix)
    };

    if let Some(libs) = libs { export_env_libs(&libs) }
    let old_dir = env::current_dir().unwrap();
    fs::create_dir_all(&Path::new(build_prefix)).unwrap();
    env::set_current_dir(&Path::new(build_prefix)).unwrap();
    let library = match package {
        "newt" => build_newt(version, &build_cfg),
        "popt" => build_popt(version, &build_cfg),
        "slang" => build_slang(version, &build_cfg),
        _ => panic!("Unexpected package requested to be built: {}", package)
    };
    env::set_current_dir(&old_dir).unwrap();
    unset_env_libs();
    return library;
}

fn build_libs() -> Library {
    let out_dir = env::var("OUT_DIR").unwrap();
    let mut libraries: Vec<Box<Library>> = Vec::new();

    let library = Box::new(build("popt", POPT_VERSION, &out_dir, None));
    libraries.push(library);

    let library = Box::new(build("slang", SLANG_VERSION, &out_dir, None));
    libraries.push(library);

    build("newt", NEWT_VERSION, &out_dir, Some(&libraries))
}

fn build_c(lib: &Library) {
    let mut build = cc::Build::new();
    build.file("src/colorset_custom.c");
    for path in lib.include_paths.iter() {
        build.include(path);
    }
    build.compile("libnewt-rs");
}

fn main() {
    let statik = cfg!(feature = "static") ||
                 env::var("NEWT_STATIC").is_ok();

    let result = pkg_config::Config::new()
        .atleast_version(NEWT_VERSION)
        .probe("libnewt");

    let lib: Library;
    if statik || result.is_err() {
        find_gnu_make();
        lib = build_libs();
    } else {
        lib = result.unwrap();
    }
    build_c(&lib);
}
