#![forbid(unsafe_code)]
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

extern crate tempfile;

pub mod preferences;
pub mod prefreader;
pub mod profile;

#[cfg(test)]
mod test {
    //    use std::fs::File;
    //    use profile::Profile;
    use crate::preferences::Pref;
    use crate::prefreader::{parse, serialize, tokenize};
    use crate::prefreader::{Position, PrefToken};
    use std::collections::BTreeMap;
    use std::io::Cursor;
    use std::str;

    #[test]
    fn tokenize_simple() {
        let prefs = "  user_pref ( 'example.pref.string', 'value' )   ;\n \
                     pref(\"example.pref.int\", -123); sticky_pref('example.pref.bool',false);";

        let p = Position::new();

        let expected = vec![
            PrefToken::UserPrefFunction(p),
            PrefToken::Paren('(', p),
            PrefToken::String("example.pref.string".into(), p),
            PrefToken::Comma(p),
            PrefToken::String("value".into(), p),
            PrefToken::Paren(')', p),
            PrefToken::Semicolon(p),
            PrefToken::PrefFunction(p),
            PrefToken::Paren('(', p),
            PrefToken::String("example.pref.int".into(), p),
            PrefToken::Comma(p),
            PrefToken::Int(-123, p),
            PrefToken::Paren(')', p),
            PrefToken::Semicolon(p),
            PrefToken::StickyPrefFunction(p),
            PrefToken::Paren('(', p),
            PrefToken::String("example.pref.bool".into(), p),
            PrefToken::Comma(p),
            PrefToken::Bool(false, p),
            PrefToken::Paren(')', p),
            PrefToken::Semicolon(p),
        ];

        tokenize_test(prefs, &expected);
    }

    #[test]
    fn tokenize_comments() {
        let prefs = "# bash style comment\n /*block comment*/ user_pref/*block comment*/(/*block \
                     comment*/ 'example.pref.string'  /*block comment*/,/*block comment*/ \
                     'value'/*block comment*/ )// line comment";

        let p = Position::new();

        let expected = vec![
            PrefToken::CommentBashLine(" bash style comment".into(), p),
            PrefToken::CommentBlock("block comment".into(), p),
            PrefToken::UserPrefFunction(p),
            PrefToken::CommentBlock("block comment".into(), p),
            PrefToken::Paren('(', p),
            PrefToken::CommentBlock("block comment".into(), p),
            PrefToken::String("example.pref.string".into(), p),
            PrefToken::CommentBlock("block comment".into(), p),
            PrefToken::Comma(p),
            PrefToken::CommentBlock("block comment".into(), p),
            PrefToken::String("value".into(), p),
            PrefToken::CommentBlock("block comment".into(), p),
            PrefToken::Paren(')', p),
            PrefToken::CommentLine(" line comment".into(), p),
        ];

        tokenize_test(prefs, &expected);
    }

    #[test]
    fn tokenize_escapes() {
        let prefs = r#"user_pref('example\x20pref', "\u0020\u2603\uD800\uDC96\"\'\n\r\\\w)"#;

        let p = Position::new();

        let expected = vec![
            PrefToken::UserPrefFunction(p),
            PrefToken::Paren('(', p),
            PrefToken::String("example pref".into(), p),
            PrefToken::Comma(p),
            PrefToken::String(" ☃𐂖\"'\n\r\\\\w".into(), p),
            PrefToken::Paren(')', p),
        ];

        tokenize_test(prefs, &expected);
    }

    fn tokenize_test(prefs: &str, expected: &[PrefToken]) {
        println!("{}\n", prefs);

        for (e, a) in expected.iter().zip(tokenize(prefs.as_bytes())) {
            let success = match (e, &a) {
                (&PrefToken::PrefFunction(_), &PrefToken::PrefFunction(_)) => true,
                (&PrefToken::UserPrefFunction(_), &PrefToken::UserPrefFunction(_)) => true,
                (&PrefToken::StickyPrefFunction(_), &PrefToken::StickyPrefFunction(_)) => true,
                (
                    &PrefToken::CommentBlock(ref data_e, _),
                    &PrefToken::CommentBlock(ref data_a, _),
                ) => data_e == data_a,
                (
                    &PrefToken::CommentLine(ref data_e, _),
                    &PrefToken::CommentLine(ref data_a, _),
                ) => data_e == data_a,
                (
                    &PrefToken::CommentBashLine(ref data_e, _),
                    &PrefToken::CommentBashLine(ref data_a, _),
                ) => data_e == data_a,
                (&PrefToken::Paren(data_e, _), &PrefToken::Paren(data_a, _)) => data_e == data_a,
                (&PrefToken::Semicolon(_), &PrefToken::Semicolon(_)) => true,
                (&PrefToken::Comma(_), &PrefToken::Comma(_)) => true,
                (&PrefToken::String(ref data_e, _), &PrefToken::String(ref data_a, _)) => {
                    data_e == data_a
                }
                (&PrefToken::Int(data_e, _), &PrefToken::Int(data_a, _)) => data_e == data_a,
                (&PrefToken::Bool(data_e, _), &PrefToken::Bool(data_a, _)) => data_e == data_a,
                (&PrefToken::Error(ref data_e, _), &PrefToken::Error(ref data_a, _)) => {
                    *data_e == *data_a
                }
                (_, _) => false,
            };
            if !success {
                println!("Expected {:?}, got {:?}", e, a);
            }
            assert!(success);
        }
    }

    #[test]
    fn parse_simple() {
        let input = "  user_pref /* block comment */ ( 'example.pref.string', 'value' )   ;\n \
                     pref(\"example.pref.int\", -123); sticky_pref('example.pref.bool',false)";

        let mut expected: BTreeMap<String, Pref> = BTreeMap::new();
        expected.insert("example.pref.string".into(), Pref::new("value"));
        expected.insert("example.pref.int".into(), Pref::new(-123));
        expected.insert("example.pref.bool".into(), Pref::new_sticky(false));

        parse_test(input, expected);
    }

    #[test]
    fn parse_escape() {
        let input = r#"user_pref('example\\pref\"string', 'val\x20ue' )"#;

        let mut expected: BTreeMap<String, Pref> = BTreeMap::new();
        expected.insert("example\\pref\"string".into(), Pref::new("val ue"));

        parse_test(input, expected);
    }

    #[test]
    fn parse_empty() {
        let inputs = ["", " ", "\n", "\n \n"];
        for input in inputs {
            let expected: BTreeMap<String, Pref> = BTreeMap::new();
            parse_test(input, expected);
        }
    }

    #[test]
    fn parse_newline() {
        let inputs = vec!["\na", "\n\nfoo"];
        for input in inputs {
            assert!(parse(input.as_bytes()).is_err());
        }
    }

    #[test]
    fn parse_minus() {
        let inputs = ["pref(-", "user_pref(\"example.pref.int\", -);"];
        for input in inputs {
            assert!(parse(input.as_bytes()).is_err());
        }
    }

    #[test]
    fn parse_boolean_eof() {
        let inputs = vec!["pref(true", "pref(false", "pref(false,", "pref(false)"];
        for input in inputs {
            assert!(parse(input.as_bytes()).is_err());
        }
    }

    fn parse_test(input: &str, expected: BTreeMap<String, Pref>) {
        match parse(input.as_bytes()) {
            Ok(ref actual) => {
                println!("Expected:\n{:?}\nActual\n{:?}", expected, actual);
                assert_eq!(actual, &expected);
            }
            Err(e) => {
                println!("{}", e);
                assert!(false)
            }
        }
    }

    #[test]
    fn serialize_simple() {
        let input = "  user_pref /* block comment */ ( 'example.pref.string', 'value' )   ;\n \
                     pref(\"example.pref.int\", -123); sticky_pref('example.pref.bool',false)";
        let expected = "sticky_pref(\"example.pref.bool\", false);
user_pref(\"example.pref.int\", -123);
user_pref(\"example.pref.string\", \"value\");\n";

        serialize_test(input, expected);
    }

    #[test]
    fn serialize_quotes() {
        let input = r#"user_pref('example\\with"quotes"', '"Value"')"#;
        let expected = r#"user_pref("example\\with\"quotes\"", "\"Value\"");
"#;

        serialize_test(input, expected);
    }

    fn serialize_test(input: &str, expected: &str) {
        let buf = Vec::with_capacity(expected.len());
        let mut out = Cursor::new(buf);
        serialize(&parse(input.as_bytes()).unwrap(), &mut out).unwrap();
        let data = out.into_inner();
        let actual = str::from_utf8(&*data).unwrap();
        println!("Expected:\n{:?}\nActual\n{:?}", expected, actual);
        assert_eq!(actual, expected);
    }
}
