#![cfg(all(feature = "serde", feature = "serde_derive"))]

extern crate ketos;
extern crate serde;
#[macro_use] extern crate serde_derive;

use std::collections::BTreeMap;
use std::path::PathBuf;

use ketos::{
    BuiltinModuleLoader, FileModuleLoader, ModuleLoader,
    Error, Interpreter, decode_value, encode_value,
};

macro_rules! map {
    ( $( $k:expr => $v:expr ),* ) => { {
        let mut _m = BTreeMap::new();
        $( _m.insert($k, $v); )*
        _m
    } }
}

macro_rules! test {
    ( $a:expr , $b:expr ) => { {
        let interp = interp(&format!(r#"
            (define (give v)
              (assert-eq v '{0}))
            (define (take) '{0})
            "#, $b)).unwrap();

        let v = $a;

        interp.call("give", vec![
            encode_value(interp.scope(), &v).unwrap(),
        ]).unwrap();

        let v2 = decode_value(interp.scope(),
            &interp.call("take", vec![]).unwrap()).unwrap();

        assert_eq!(v, v2);
    } }
}

fn interp(code: &str) -> Result<Interpreter, Error> {
    let mut loader = FileModuleLoader::with_search_paths(vec![PathBuf::from("lib")]);

    loader.set_read_bytecode(false);
    loader.set_write_bytecode(false);

    let interp = Interpreter::with_loader(
        Box::new(BuiltinModuleLoader.chain(loader)));

    interp.run_code("(use test (assert-eq))", None)?;
    interp.run_code(code, None)?;

    Ok(interp)
}

#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
struct StructA {
    a: i32,
    b: char,
    c: String,
}

const STRUCT_0: &'static str =
    r#"(StructA (:a 123 :b #'x' :c "foo"))"#;

fn struct_0() -> StructA {
    StructA{
        a: 123,
        b: 'x',
        c: "foo".to_owned(),
    }
}

#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
struct StructB {
    a: Vec<i32>,
    b: BTreeMap<String, String>,
}

const STRUCT_1: &'static str =
    r#"(StructB (:a () :b ()))"#;

fn struct_1() -> StructB {
    StructB{
        a: vec![],
        b: map!(),
    }
}

const STRUCT_2: &'static str =
    r#"(StructB (:a (1 2 3) :b (("a" "b") ("c" "d"))))"#;

fn struct_2() -> StructB {
    StructB{
        a: vec![1, 2, 3],
        b: map!("a".to_owned() => "b".to_owned(), "c".to_owned() => "d".to_owned()),
    }
}

#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
struct StructC(u8, (u16, u32), [i32; 2]);

const STRUCT_3: &'static str =
    "(StructC (1 (2 3) (4 5)))";

fn struct_3() -> StructC {
    StructC(1, (2, 3), [4, 5])
}

#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
struct StructD;

const STRUCT_4: &'static str =
    "(StructD ())";

fn struct_4() -> StructD { StructD }

#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
struct StructE {
    a: StructA,
    b: StructB,
    c: StructC,
    d: StructD,
}

const STRUCT_5: &'static str =
    r#"(StructE (:a (StructA (:a -1 :b #'.' :c "lol"))
                 :b (StructB (:a (0) :b (("a" "b"))))
                 :c (StructC (2 (1 0) (-1 -2)))
                 :d (StructD ())))"#;

fn struct_5() -> StructE {
    StructE{
        a: StructA{a: -1, b: '.', c: "lol".to_owned()},
        b: StructB{a: vec![0], b: map!("a".to_owned() => "b".to_owned())},
        c: StructC(2, (1, 0), [-1, -2]),
        d: StructD,
    }
}

#[test]
fn test_struct() {
    test!(struct_0(), STRUCT_0);
    test!(struct_1(), STRUCT_1);
    test!(struct_2(), STRUCT_2);
    test!(struct_3(), STRUCT_3);
    test!(struct_4(), STRUCT_4);
    test!(struct_5(), STRUCT_5);
}

#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
enum Enum {
    Alpha,
    Beta(i32),
    Gamma(char, ()),
    Delta{a: String, b: u32},
}

const ENUM_0: &'static str =
    "(Enum Alpha ())";

fn enum_0() -> Enum {
    Enum::Alpha
}

const ENUM_1: &'static str =
    "(Enum Beta (1))";

fn enum_1() -> Enum {
    Enum::Beta(1)
}

const ENUM_2: &'static str =
    "(Enum Gamma (#'a' ()))";

fn enum_2() -> Enum {
    Enum::Gamma('a', ())
}

const ENUM_3: &'static str =
    r#"(Enum Delta (:a "foo" :b 123))"#;

fn enum_3() -> Enum {
    Enum::Delta{a: "foo".to_owned(), b: 123}
}

#[test]
fn test_enum() {
    test!(enum_0(), ENUM_0);
    test!(enum_1(), ENUM_1);
    test!(enum_2(), ENUM_2);
    test!(enum_3(), ENUM_3);
}

macro_rules! de {
    ( $ty:ty => $e:expr ) => { {
        let interp = interp(&format!("
            (define (make) '{})
            ", $e)).unwrap();

        decode_value::<$ty>(interp.scope(),
            &interp.call("make", vec![]).unwrap())
    } }
}

#[test]
fn test_primitive() {
    assert_eq!(de!((u32, String) => r#"(1 "foo")"#).unwrap(),
        (1, "foo".to_owned()));
    assert_eq!(de!(Vec<u32> => "(1 2 3)").unwrap(), [1, 2, 3]);
    assert_eq!(de!(BTreeMap<u32, u32> => "((1 2) (3 4))").unwrap(),
        map!(1 => 2, 3 => 4));
}

#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
struct OptStruct {
    a: Option<i32>,
}

#[test]
fn test_option() {
    assert_eq!(de!(Option<i32> => "()").unwrap(), None);
    assert_eq!(de!(Option<i32> => "1").unwrap(), Some(1));

    assert_eq!(de!(OptStruct => "(OptStruct (:a 1))").unwrap(),
        OptStruct{a: Some(1)});
    assert_eq!(de!(OptStruct => "(OptStruct (:a ()))").unwrap(),
        OptStruct{a: None});
}

#[test]
fn test_error() {
    assert!(de!(StructA => "0").is_err());
    assert!(de!(StructA => "()").is_err());
    assert!(de!(StructA => "(StructA)").is_err());
    assert!(de!(StructA => "(StructA ())").is_err());
    assert!(de!(StructA => "(StructA () ())").is_err());
    assert!(de!(StructA => "(StructB (:a ()))").is_err());
    assert!(de!(StructA => "(StructB (:a () :b () :c ()))").is_err());

    assert!(de!(Enum => "0").is_err());
    assert!(de!(Enum => "()").is_err());
    assert!(de!(Enum => "(Enum)").is_err());
    assert!(de!(Enum => "(Enum Alpha)").is_err());
    assert!(de!(Enum => "(Enum Alpha (0))").is_err());
    assert!(de!(Enum => "(Enum Alpha () ())").is_err());
    assert!(de!(Enum => "(Enum Gamma (#'x'))").is_err());
    assert!(de!(Enum => "(Enum Lol ())").is_err());

    assert!(de!(BTreeMap<u32, u32> => "((0 1) ())").is_err());
    assert!(de!(BTreeMap<u32, u32> => "((0 1) (1))").is_err());
    assert!(de!(BTreeMap<u32, u32> => "((0 1) (1 2 3))").is_err());
    assert!(de!(Vec<u32> => "(1 2 ())").is_err());
}