using System.Collections.Generic;
using NUnit.Framework;
using Prexonite;
using Prexonite.Compiler;

namespace PrexoniteTests.Tests;

[Parallelizable(ParallelScope.Fixtures)]
[TestFixture]
public class Storage : CompilerTestBase
{
    const string _storedShouldBeEqual =
        "Since the in-memory and the restored application are the same, they should"
        + " result in the same serialized form.";

    [Test]
    public void TestEmpty()
    {
        const string input1 = "";
        var ldr = new Loader(engine, target);
        TestContext.WriteLine("-- Compiling fixture");
        ldr.LoadFromString(input1);
        Assert.AreEqual(0, ldr.ErrorCount);

        var stored = ldr.Options.TargetApplication!.StoreInString();
        TestContext.WriteLine(stored);

        TestContext.WriteLine("-- Compiling stored result");
        var reldr = new Loader(engine, new());
        reldr.LoadFromString(stored);
        reldr.Options.TargetApplication!.StoreInString();

        Assert.That(
            stored,
            Is.EqualTo(stored).Using((IEqualityComparer<string>)Engine.DefaultStringComparer),
            _storedShouldBeEqual
        );
    }

    [Test]
    public void TestMeta()
    {
        const string input1 = """

            Name fixture\storage\meta;
            Description "Contains dummy meta information for a unit test";
            Author SealedSun;

            Import 
            {
                System,
                System::Text
            };

            Is AsFastAsPossible;

            Add System::Xml To Imports;

            """;
        var ldr = new Loader(engine, target);
        ldr.LoadFromString(input1);
        Assert.AreEqual(0, ldr.ErrorCount);

        var stored = ldr.Options.TargetApplication!.StoreInString();
        TestContext.WriteLine(stored);

        var reldr = new Loader(engine, new());
        reldr.LoadFromString(stored);
        reldr.Options.TargetApplication!.StoreInString();

        Assert.That(
            stored,
            Is.EqualTo(stored).Using((IEqualityComparer<string>)Engine.DefaultStringComparer),
            _storedShouldBeEqual
        );
    }

    [Test]
    public void TestDeclarations()
    {
        const string input1 = """

            var obj0;
            var obj1;
            ref func0 
            [
                Description "Contains a constant value";
                is constant;
                UsedBy func1;    
            ];

            ref func2;

            function func1 does asm {}

            ref func0 [ Add func2 to UsedBy; ];

            function func2(arg0, arg1, arg2)
            [
                Uses { func0, func1 };
            ]
            {//line# 20
                goto L1;
                L0:;
                func0();
                goto LE;
                L1:;
                func1();
                goto L0;
                LE:;
            }

            """;
        var ldr = new Loader(engine, target);
        ldr.LoadFromString(input1);
        Assert.AreEqual(0, ldr.ErrorCount);

        var stored = ldr.Options.TargetApplication!.StoreInString();
        TestContext.WriteLine("//== 1st Store");
        TestContext.WriteLine(stored);

        var reldr = new Loader(engine, new());
        reldr.LoadFromString(stored);
        var restored = reldr.Options.TargetApplication!.StoreInString();
        TestContext.WriteLine("//== 2nd Store");
        TestContext.WriteLine(restored);

        //The two of them are not supposed to be equal because of a debug feature
        /*Assert.IsTrue(Engine.StringsAreEqual(stored, restored),
            storedShouldBeEqual); //*/
    }

#if Compressed

    [Test]
    public void CompressNoException()
    {
        Loader ldr = _compile(
            @"

coroutine mapf(ref f, xs) does
    foreach(var x in xs)
        yield a => f(a,x);


"
        );
        using (MemoryStream mstr = new MemoryStream())
        {
            ldr.StoreCompressed(mstr);
            Assert.Greater(mstr.Length, 10f);
        }
    }
#endif

    [Test]
    public void MetaEntryIntegerNotString()
    {
        var ldr = new Loader(engine, target);
        const string numberToStore = "1234567890";
        ldr.LoadFromString(
            """

            meta_entry 
            """
                + numberToStore
                + """
                ;

                """
        );
        foreach (var error in ldr.Errors)
            TestContext.WriteLine("ERROR: {0}", error);
        Assert.AreEqual(0, ldr.ErrorCount, "no errors expected");
        var stored = ldr.Options.TargetApplication!.StoreInString();
        TestContext.WriteLine("//== 1st Store");
        TestContext.WriteLine(stored);

        var reldr = new Loader(engine, new());
        reldr.LoadFromString(stored);
        var restored = ldr.Options.TargetApplication.StoreInString();
        TestContext.WriteLine("//== 2nd Store");
        TestContext.WriteLine(restored);

        Assert.AreEqual(stored, restored);
        Assert.AreEqual(numberToStore, ldr.Options.TargetApplication.Meta["meta_entry"].Text);
        Assert.AreEqual(numberToStore, reldr.Options.TargetApplication!.Meta["meta_entry"].Text);
    }
}
