require "spec"
require "log"
require "log/json"

private def m(value)
  Log::Metadata.build(value)
end

private def v(value)
  Log::Metadata::Value.new(value)
end

describe Log::Metadata do
  it "empty" do
    Log::Metadata.empty.should eq(Log::Metadata.new)
    Log::Metadata.empty.object_id.should_not eq(Log::Metadata.new.object_id)
    Log::Metadata.empty.object_id.should eq(Log::Metadata.empty.object_id)
  end

  it "empty?" do
    Log::Metadata.empty.should be_empty
    m({} of Symbol => String).should be_empty
    Log::Metadata.new.should be_empty
    m({} of Symbol => String).extend({} of Symbol => String).should be_empty

    m({a: 1}).should_not be_empty
    m({} of Symbol => String).extend({a: 1}).should_not be_empty
    m({a: 1}).extend({} of Symbol => String).should_not be_empty
  end

  describe "#dup" do
    it "creates a shallow copy" do
      Log::Metadata.empty.dup.should eq(Log::Metadata.empty)
      m({a: 1}).dup.should eq(m({a: 1}))
      m({a: 1, b: 3}).dup.should eq(m({a: 1, b: 3}))
    end
  end

  it "extend" do
    m({a: 1}).extend({b: 2}).should eq(m({a: 1, b: 2}))
    m({a: 1, b: 3}).extend({b: 2}).should eq(m({a: 1, b: 2}))
    m({a: 1, b: 3}).extend({b: nil}).should eq(m({a: 1, b: nil}))
  end

  it "extend against empty values without creating a new instance" do
    c1 = m({a: 1, b: 3})
    c1.extend(NamedTuple.new).should be(c1)
    c1.extend(Hash(Symbol, String).new).should be(c1)
  end

  it "==" do
    m({} of Symbol => String).should eq(m({} of Symbol => String))
    m({a: 1}).should eq(m({a: 1}))
    m({a: 1, b: 2}).should eq(m({b: 2, a: 1}))

    m({a: 1}).should_not eq(m({a: 2}))
    m({a: 1}).should_not eq(m({b: 1}))

    m({a: 1}).extend({b: 2}).should eq(m({b: 2}).extend({a: 1}))
    m({a: 1, b: 1}).extend({b: 2}).should eq(m({b: 2}).extend({a: 1}))
    m({a: 1, b: 2}).extend({b: 1}).should eq(m({a: 1, b: 1}))
  end

  it "json" do
    m({a: 1}).to_json.should eq(%({"a":1}))
    m({a: 1, b: 1}).extend({b: 2}).to_json.should eq(%({"a":1,"b":2}))
  end

  it "defrags" do
    parent = m({a: 1, b: 2}).extend({a: 2})
    md = parent.extend({a: 3})

    md.@size.should eq(1)
    md.@max_total_size.should eq(4)
    md.@parent.should be(parent)

    md.should eq(m({a: 3, b: 2}))

    md.@size.should eq(2)
    md.@max_total_size.should eq(2)
    md.@parent.should be_nil
  end

  it "defrags deep" do
    grandparent = m({x: 100, y: 200, z: 300})
    parent = grandparent.extend({x: 101, a: 1, b: 2})
    md = parent.extend({x: 102, b: 22, c: 3})

    md.should eq(m({x: 102, y: 200, z: 300, a: 1, b: 22, c: 3}))
  end

  it "[] find parent entry after defrag" do
    grandparent = m({x: 100, y: 200, z: 300})
    parent = grandparent.extend({x: 101, a: 1, b: 2})
    md = parent.extend({x: 102, b: 22, c: 3})

    _ = md.to_a

    md[:y].should eq(200)
    md[:z].should eq(300)
    md[:x].should eq(102)
    md[:a].should eq(1)
    md[:b].should eq(22)
  end

  it "[]" do
    md = m({a: 1, b: 2}).extend({a: 3})

    md[:a].should eq(3)
    md[:b].should eq(2)
    expect_raises(KeyError) { md[:c] }
  end

  it "[]?" do
    md = m({a: 1, b: 2}).extend({a: 3})

    md[:a]?.should eq(3)
    md[:b]?.should eq(2)
    md[:c]?.should be_nil
  end
end

describe Log::Metadata::Value do
  it "initialize" do
    v({a: 1}).should eq(v({"a" => v(1)}))
    v({a: 1, b: ["str", true], num: 1i64}).should eq(v({"a" => v(1), "b" => v([v("str"), v(true)]), "num" => v(1i64)}))
    v({a: 1f32, b: 1f64}).should eq(v({"a" => v(1f32), "b" => v(1f64)}))
    t = Time.local
    v({time: t}).should eq(v({"time" => v(t)}))
    v({} of String => String).should eq(v(NamedTuple.new))
  end

  it "accessors" do
    v(nil).as_nil.should be_nil

    v(1).as_i.should eq(1)

    v("a").as_s.should eq("a")
    v(1).as_s?.should be_nil

    v(true).as_bool.should be_true
    v(false).as_bool.should be_false
    v(true).as_bool?.should be_true
    v(false).as_bool?.should be_false
    v(nil).as_bool?.should be_nil
  end

  it "json" do
    v({a: 1}).to_json.should eq(%({"a":1}))
  end

  describe "#==" do
    it "compares with String" do
      v("foo").should eq "foo"
      "foo".should eq v("foo")

      v("foo").should_not eq "bar"
      "foo".should_not eq v("bar")
    end
  end
end
