### ----------------------------------------------------
### -- LibXML::Class
### -- Licenses: Artistic-2.0
### -- Authors: Vadim Belman
### -- File: t/060-manual-de-serializer.rakutest
### ----------------------------------------------------
use v6.e.PREVIEW;
use MONKEY-SEE-NO-EVAL;
use Test::Async;

use LibXML::Element;
use LibXML::Class;

plan 3;

subtest "Just value" => {
    plan 2;

    my class Record {
        has Num:D $.n is required;
        has Str:D $.s is required;
    }

    my sub serialize-rec(Record:D $r) { $r.raku }
    my sub deserialize-rec(Str:D $v) { EVAL $v }

    my class Root is xml-element<root> {
        has $.rec is xml-element(:serializer(&serialize-rec), :deserializer(&deserialize-rec));
    }

    my $root = Root.new: rec => Record.new(:n(pi), :s("москалям горіти в пеклі!"));

    my $serialized = $root.to-xml;
    my $expected = "#xml" => [:root([:rec(["Record.new(n => 3.141592653589793e0, s => \"москалям горіти в пеклі!\")"])])];
    cmp-deeply $serialized.ast, $expected, "serialized OK";

    my $deserialized = Root.from-xml: $serialized.Str;
    cmp-deeply $deserialized, $root, "deserialized OK";
}

subtest "Value and element" => {
    plan 2;

    my role Recordish {};

    my class Record1 is xml-element<rec1> does Recordish {
        has Str:D $.foo is required;
    }

    my class Record2 is xml-element<rec2> does Recordish {
        has Rat:D $.foo is required;
    }

    my proto sub serialize-rec(|) {*}
    multi sub serialize-rec(LibXML::Element:D $item-elem, Record1:D $rec) {
        $item-elem.setAttribute('trick', $rec.raku);
    }
    multi sub serialize-rec(Record2:D $rec) {
        "(" ~ $rec.foo ~ ")"
    }

    my sub deserialize-str(LibXML::Element:D $elem) {
        if $elem.hasAttribute('trick') {
            return EVAL $elem.getAttribute('trick');
        }
        Record2.new: foo => EVAL($elem.textContent)
    }

    my class Root is xml-element<root> {
        has Recordish:D $.rec is xml-element(:serializer(&serialize-rec), :deserializer(&deserialize-str));
    }

    subtest "with element serializer multi-candidate" => {
        plan 2;
        my $rec = Record1.new: :foo("with some text");
        my $root = Root.new: :$rec;
        my $serialized = $root.to-xml;
        my $expected = "#xml" => [:root([:rec([:trick("Record1.new(foo => \"with some text\", xml-name => \"rec1\")")])])];
        cmp-deeply $serialized.ast, $expected, "serialized OK";
        my $deserialized = Root.from-xml: $serialized.Str;
        cmp-deeply $deserialized, $root, "deserialized OK";
    }

    subtest "with value serializer multi-candidate" => {
        plan 2;
        my $rec = Record2.new: :foo(42.12);
        my $root = Root.new: :$rec;

        my $serialized = $root.to-xml;
        my $expected = "#xml" => [:root([:rec(["(42.12)"])])];
        cmp-deeply $serialized.ast, $expected, "serialized OK";
        my $deserialized = Root.from-xml: $serialized.Str;
        cmp-deeply $deserialized, $root, "deserialized OK";
    }

}

subtest "Sequence" => {
    plan 3;

    my class SItem is xml-element<si> {
        has Int:D $.order is required;
    }

    proto sub deserialize-si(|) {*}
    multi sub deserialize-si(LibXML::Element:D $elem) {
        # This would also test for xml-I-cant since if it fails in a way then the resulting AST would differ.
        xml-I-cant unless $elem.localName eq 'si' && $elem.hasAttribute('ord');
        SItem.new: order => $elem.getAttribute('ord').Num.Int
    }
    multi sub deserialize-si(Str:D $val) {
        ($val ~~ /^ v $<ver>=[\d .+] $/) andthen Version.new: .<ver>
    }

    my proto sub serialize-si(|) {*}
    multi sub serialize-si(LibXML::Element:D $ielem, SItem:D $value) {
        xml-I-cant if $value.order == 5;
        $ielem.setAttribute: "ord", $value.order.Num.fmt('%.2e')
    }
    multi sub serialize-si(Version:D $v) {
        $v.gist
    }

    my class XMLSeq
        is xml-element(
            :sequence(
                (SItem, :serializer(&serialize-si), :deserializer(&deserialize-si)),
                :ver(Version, :serializer(&serialize-si), :deserializer(&deserialize-si))
            ) ) {}

    my $xs = XMLSeq.new;
    my $minor = 0;
    for ^10 {
        $xs.push: SItem.new(:order($_));
        $xs.push: Version.new("$_\.{$minor++}") if $_ % 3 == 0
    }

    my $serialized = $xs.to-xml;
    my $expected = "#xml" => [:XMLSeq([:si([:ord("0.00e+00")]), :ver(["v0.0"]), :si([:ord("1.00e+00")]), :si([:ord("2.00e+00")]), :si([:ord("3.00e+00")]), :ver(["v3.1"]), :si([:ord("4.00e+00")]), :si([:order("5")]), :si([:ord("6.00e+00")]), :ver(["v6.2"]), :si([:ord("7.00e+00")]), :si([:ord("8.00e+00")]), :si([:ord("9.00e+00")]), :ver(["v9.3"])])];
    cmp-deeply $serialized.ast, $expected, "serialized OK";

    my $deserialized = XMLSeq.from-xml: $serialized.Str;

    subtest "check items" => {
        plan $xs.elems + 1;
        is $deserialized.elems, $xs.elems, "all sequence items are picked from the source";
        for ^($deserialized.elems max $xs.elems) {
            cmp-deeply $deserialized[$_], $xs[$_], "item at position $_";
        }
    }
    cmp-deeply $deserialized, $xs, "sequence deserialized";
}

done-testing;


