using Lisp_Boolean = Jellyfin.Plugin.SmartPlaylist.Lisp.Boolean;
using Lisp_Object = Jellyfin.Plugin.SmartPlaylist.Lisp.Object;
using Lisp_Integer = Jellyfin.Plugin.SmartPlaylist.Lisp.Integer;
using Jellyfin.Plugin.SmartPlaylist.Lisp;
using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler;

namespace Tests
{
    public class O {
        int _i;
        bool _b;
        public O(int i, bool b) {
            _i = i;
            _b = b;
        }
        public int i { get => _i; }
        public bool b { get => _b; }
        public int I() {
            return _i;
        }
        public string G<E>() {
            return typeof(E).FullName;
        }
    }

    public class Test {
        [Fact]
        public static void TestTokenizer() {
            StringTokenStream sts = StringTokenStream.generate("(\"some literal string\" def ghj +100 -+300 1 >= ++ !=)");
            Assert.Equal("(", sts.Get().value);
            Assert.Equal("\"", sts.Get().value);
            Assert.Equal("some", sts.Get().value);
            Assert.Equal(" ", sts.Get().value);
            Assert.Equal("literal", sts.Get().value);
            Assert.Equal(" ", sts.Get().value);
            Assert.Equal("string", sts.Get().value);
            Assert.Equal("\"", sts.Get().value);
            Assert.Equal(" ", sts.Get().value);
            Assert.Equal("def", sts.Get().value);
            Assert.Equal(" ", sts.Get().value);
            Assert.Equal("ghj", sts.Get().value);
            Assert.Equal(" ", sts.Get().value);
            Assert.Equal("+100", sts.Get().value);
            Assert.Equal(" ", sts.Get().value);
            Assert.Equal("-+300", sts.Get().value);
            Assert.Equal(" ", sts.Get().value);
            Assert.Equal("1", sts.Get().value);
            Assert.Equal(" ", sts.Get().value);
            Assert.Equal(">=", sts.Get().value);
            Assert.Equal(" ", sts.Get().value);
            Assert.Equal("++", sts.Get().value);
            Assert.Equal(" ", sts.Get().value);
            Assert.Equal("!=", sts.Get().value);
            Assert.Equal(")", sts.Get().value);
            sts.Commit();
            Assert.Equal(0, sts.Available());
        }

        [Fact]
        public static void TestParser() {
            string program = "(+ 1 (* 2 3))";
            StringTokenStream sts = StringTokenStream.generate(program);
            Parser p = new Parser(sts);
            Assert.Equal(program, string.Format("{0}", p.parse()));

            program = "(haskeys o \"i\" \"b\")";
            sts = StringTokenStream.generate(program);
            p = new Parser(sts);
            Assert.Equal(program, string.Format("{0}", p.parse()));

            program = "(abc '(1 2 3))";
            sts = StringTokenStream.generate(program);
            p = new Parser(sts);
            Assert.Equal(program, string.Format("{0}", p.parse()));

            program = "(abc \"'(1 2 3)\")";
            sts = StringTokenStream.generate(program);
            p = new Parser(sts);
            Assert.Equal(program, string.Format("{0}", p.parse()));

            program = """
                (begin ;this too
                  ;;; this is a comment
                  t
                  )
            """;
            sts = StringTokenStream.generate(program);
            p = new Parser(sts);
            Assert.Equal("(begin t)", string.Format("{0}", p.parse()));
        }

        [Fact]
        public static void TestFunctions() {
            Executor e = new Executor();
            Assert.Equal("(1 2 3)", e.eval("(quote (1 2 3))").ToString());
            Assert.Equal("abc", e.eval("(quote abc)").ToString());

            Assert.Equal("t", e.eval("(atom 1)").ToString());
            Assert.Equal("nil", e.eval("(atom (quote (1 2 3)))").ToString());

            Assert.Equal("t", e.eval("(eq 2 2)").ToString());
            Assert.Equal("nil", e.eval("(eq 2 3)").ToString());

            Assert.Equal("1", e.eval("(car (quote (1 2 3)))").ToString());
            Assert.Equal("(2 3)", e.eval("(cdr (quote (1 2 3)))").ToString());

            Assert.Equal("(1 . 2)", e.eval("(cons 1 2)").ToString());
            Assert.Equal("(1 2)", e.eval("(cons 1 (cons 2 nil))").ToString());
            Assert.Equal("(1)", e.eval("(cons 1 nil)").ToString());
            Assert.Equal("(1)", e.eval("(cons 1 ())").ToString());

            Assert.Equal("\"Case 2\"", e.eval("""
                (cond
                    ((eq 1 2) "Case 1")
                    ((eq 2 2) "Case 2"))
            """).ToString());
            Assert.Equal("\"Case 1\"", e.eval("""
                (cond
                    ((eq 2 2) "Case 1")
                    ((eq 2 2) "Case 2"))
            """).ToString());
            Assert.Equal("nil", e.eval("""
                (cond
                    ((eq 1 2) "Case 1")
                    ((eq 3 2) "Case 2"))
            """).ToString());

            Assert.Equal("t", e.eval("((lambda (a) (eq a a)) 2)").ToString());

            Assert.Equal("t", e.eval("(begin (car (quote (nil 1))) t)").ToString());
            Assert.Equal("(1)", e.eval("(begin t (cdr (quote (nil 1))))").ToString());

            Assert.Equal("t", e.eval("""
                (begin
                    (define abc 10)
                    (eq abc abc))
            """).ToString());

            Assert.Equal("1", e.eval("""
                (begin
                    (define if (lambda (condition a b) (
                        cond (condition a) (t b))))
                    (if (> 2 1) (car (quote (1 2 3))) (cdr (quote (2 3 4)))))
            """).ToString());
            Assert.Equal("(3 4)", e.eval("""
                (begin
                    (define if (lambda (condition a b) (
                        cond (condition a) (t b))))
                    (if (> 0 1) (car (quote (1 2 3))) (cdr (quote (2 3 4)))))
            """).ToString());

            Assert.Equal("a", e.eval("'a").ToString());
        }

        [Fact]
        public static void TestFunctionsAdvanced() {
            Executor e = new Executor();
            Assert.Equal("2", e.eval("""
                ((lambda (b) b) (car (quote (2 3))))
            """).ToString());

            Assert.Equal("(3 4 5)", e.eval("""
                ((lambda (x y . z) z) 1 2 3 4 5)
            """).ToString());

            Assert.Equal("3", e.eval("""
                (begin
                    (define if (lambda (condition a b) (cond (condition a) (t b))))
                    (if (< 1 2) 3 2))
            """).ToString());

            Assert.Equal("2", e.eval("""
                (begin
                    (define if (lambda (condition a b) (cond (condition a) (t b))))
                    (if (> 1 2) 3 2))
            """).ToString());
            Assert.Equal("1", e.eval("""
                (begin
                    (define if (lambda* (condition a b) (
                        cond ((eval condition) (eval a)) (t (eval b)))))
                    (if (> 2 1) (car (quote (1 2 3))) (cdr (quote (2 3 4)))))
            """).ToString());
            Assert.Equal("(3 4)", e.eval("""
                (begin
                    (define if (lambda* (condition a b) (
                        cond ((eval condition) (eval a)) (t (eval b)))))
                    (if (> 0 1) (car (quote (1 2 3))) (cdr (quote (2 3 4)))))
            """).ToString());
            Assert.Equal("120", e.eval("""
                (begin
                    (define f (lambda (n) (cond ((<= n 1) 1) (t (* n (f (- n 1)))))))
                    (f 5))
            """).ToString());
            Assert.Equal("120", e.eval("""
                (begin
                    (define if (lambda* (condition a b) (
                        cond ((eval condition) (eval a)) (t (eval b)))))
                    (define f (lambda (n) (if (<= n 1) 1 (* n (f (- n 1))))))
                    (f 5))
            """).ToString());
            Assert.Equal("(1 2 3 4 5)", e.eval("""
                (begin
                    (define if (lambda* (condition a b) (
                        cond ((eval condition) (eval a)) (t (eval b)))))
                    ((lambda (. args) args) 1 2 3 4 5))
            """).ToString());
            Assert.Equal("t", e.eval("""
                (begin
                    (define null (lambda* (x) (
                        cond ((eval x) nil) (t t))))
                    (null nil))
            """).ToString());
            Assert.Equal("nil", e.eval("""
                (begin
                    (define null (lambda* (x) (cond ((eval x) nil) (t t))))
                    (null (quote (1 2))))
            """).ToString());
        }
        [Fact]
        public static void ObjectTest() {
            Executor e = new Executor();
            Expression r;
            e.environment.Set("o", Lisp_Object.FromBase(new O(5, false)));
            r = e.eval("""(haskeys o "i" "b")""");
            Assert.True(((Lisp_Boolean)r).Value());
            r = e.eval("""(getitems o "i" "b")""");
            Assert.Equal("(5 nil)", string.Format("{0}", r));
            r = e.eval("""(invoke o "I" nil)""");
            Assert.Equal("5", string.Format("{0}", r));
            r = e.eval("""(invoke-generic o "G" nil ((lambda (. args) args) "System.String"))""");
            Assert.Equal("\"System.String\"", string.Format("{0}", r));
        }

        [Fact]
        public static void GlobalVariableTest() {
            Executor e = new Executor();
            e.environment.Set("*o*", new Lisp_Integer(5));
            Assert.Equal("10", e.eval("(* *o* 2)").ToString());
        }

        [Fact]
        public static void DefaultEnvironmentTest() {
            Executor e = new Executor(new DefaultEnvironment());
            Assert.Equal("1", e.eval("(if nil 0 1)").ToString());
            Assert.Equal("0", e.eval("(if t 0 1)").ToString());
            Assert.Equal("5", e.eval("(if t (if t 5 nil) nil)").ToString());
            Assert.Equal("nil", e.eval("(if t (if nil 5 nil) nil)").ToString());
            Assert.Equal("(1 2 3)", e.eval("(list 1 2 3)").ToString());
            Assert.Equal("3", e.eval("(find 3 (list 1 2 3 4))").ToString());
            Assert.Equal("nil", e.eval("(find 0 (list 1 2 3 4))").ToString());
            Assert.Equal("(2 4 6)", e.eval("(map (lambda (x) (* x 2)) (quote (1 2 3)))").ToString());
            Assert.Equal("nil", e.eval("(and 1 2 3 nil)").ToString());
            Assert.Equal("t", e.eval("(and t t t t)").ToString());
            Assert.Equal("t", e.eval("(or nil nil t nil)").ToString());
            Assert.Equal("nil", e.eval("(or nil nil nil nil)").ToString());
            Assert.Equal("t", e.eval("(any (lambda (x) (= x 2)) (list 1 2 3 4 5 6))").ToString());
            Assert.Equal("nil", e.eval("(any (lambda (x) (= x 2)) (list 1 3 4 5 6))").ToString());
            Assert.Equal("nil", e.eval("(any (lambda (x) (= x 2)) nil)").ToString());
            Assert.Equal("t", e.eval("(all (lambda (x) (= 1 (% x 2))) (list 1 3 5))").ToString());
            Assert.Equal("nil", e.eval("(all (lambda (x) (= 1 (% x 2))) (list 1 3 4 5))").ToString());
            Assert.Equal("nil", e.eval("(all (lambda (x) (= x 2)) nil)").ToString());
            Assert.Equal("10", e.eval("(fold (lambda (a b) (+ a b)) 0 (list 1 2 3 4))").ToString());
            Assert.Equal("(2 3 4 5 6 7)", e.eval("(append (list 2 3 4) (list 5 6 7))").ToString());
            Assert.Equal("(1 2 3 4 5 6 7)", e.eval("(qsort (lambda (a b) (> a b)) (list 5 4 7 3 2 6 1))").ToString());

            //Assert.Equal("", e.eval("(rand)").ToString());
            //Assert.Equal("", e.eval("(shuf (list 0 1 2 3 4 5 6))").ToString());
            Assert.Equal("(1 2 3 4 5 6 7)", e.eval("(qsort (lambda (a b) (> a b)) '(5 4 7 3 2 6 1))").ToString());
            Assert.Equal("(1 2 3 4 5 6 7)", e.eval("(qsort > (list 5 4 7 3 2 6 1))").ToString());
        }
    }
}