# frozen_string_literal: true
require_relative 'helper'

module Psych
  class TestCoder < TestCase
    class InitApi
      attr_accessor :implicit
      attr_accessor :style
      attr_accessor :tag
      attr_accessor :a, :b, :c

      def initialize
        @a = 1
        @b = 2
        @c = 3
      end

      def init_with coder
        @a = coder['aa']
        @b = coder['bb']
        @implicit = coder.implicit
        @tag      = coder.tag
        @style    = coder.style
      end

      def encode_with coder
        coder['aa'] = @a
        coder['bb'] = @b
      end
    end

    class TaggingCoder < InitApi
      def encode_with coder
        super
        coder.tag       = coder.tag.sub(/!/, '!hello')
        coder.implicit  = false
        coder.style     = Psych::Nodes::Mapping::FLOW
      end
    end

    class ScalarCoder
      def encode_with coder
        coder.scalar = "foo"
      end
    end

    class Represent
      yaml_tag 'foo'
      def encode_with coder
        coder.represent_scalar 'foo', 'bar'
      end
    end

    class RepresentWithInit
      yaml_tag name
      attr_accessor :str

      def init_with coder
        @str = coder.scalar
      end

      def encode_with coder
        coder.represent_scalar self.class.name, 'bar'
      end
    end

    class RepresentWithSeq
      yaml_tag name
      attr_accessor :seq

      def init_with coder
        @seq = coder.seq
      end

      def encode_with coder
        coder.represent_seq self.class.name, %w{ foo bar }
      end
    end

    class RepresentWithMap
      yaml_tag name
      attr_accessor :map

      def init_with coder
        @map = coder.map
      end

      def encode_with coder
        coder.represent_map self.class.name, { "string" => 'a', :symbol => 'b' }
      end
    end

    class RepresentWithObject
      def encode_with coder
        coder.represent_object self.class.name, 20
      end
    end

    class Referential
      attr_reader :a

      def initialize
        @a = self
      end

      def encode_with(c)
        c['a'] = @a
      end

      def init_with(c)
        @a = c['a']
      end
    end

    class CustomEncode
      def initialize(**opts)
        @opts = opts
      end

      def encode_with(coder)
        @opts.each { |k,v| coder.public_send :"#{k}=", v }
      end
    end

    def test_self_referential
      x = Referential.new
      copy = Psych.unsafe_load Psych.dump x
      assert_equal copy, copy.a
    end

    def test_represent_with_object
      thing = Psych.load(Psych.dump(RepresentWithObject.new))
      assert_equal 20, thing
    end

    def test_json_dump_exclude_tag
      refute_match('TestCoder::InitApi', Psych.to_json(InitApi.new))
    end

    def test_map_takes_block
      coder = Psych::Coder.new 'foo'
      tag = coder.tag
      style = coder.style
      coder.map { |map| map.add 'foo', 'bar' }
      assert_equal 'bar', coder['foo']
      assert_equal tag, coder.tag
      assert_equal style, coder.style
    end

    def test_map_with_tag
      coder = Psych::Coder.new 'foo'
      coder.map('hello') { |map| map.add 'foo', 'bar' }
      assert_equal 'bar', coder['foo']
      assert_equal 'hello', coder.tag
    end

    def test_map_with_tag_and_style
      coder = Psych::Coder.new 'foo'
      coder.map('hello', 'world') { |map| map.add 'foo', 'bar' }
      assert_equal 'bar', coder['foo']
      assert_equal 'hello', coder.tag
      assert_equal 'world', coder.style
    end

    def test_represent_map
      thing = Psych.unsafe_load(Psych.dump(RepresentWithMap.new))
      assert_equal({ "string" => 'a', :symbol => 'b' }, thing.map)
    end

    def test_represent_sequence
      thing = Psych.unsafe_load(Psych.dump(RepresentWithSeq.new))
      assert_equal %w{ foo bar }, thing.seq
    end

    def test_represent_with_init
      thing = Psych.unsafe_load(Psych.dump(RepresentWithInit.new))
      assert_equal 'bar', thing.str
    end

    def test_represent!
      assert_match(/foo/, Psych.dump(Represent.new))
      assert_instance_of(Represent, Psych.unsafe_load(Psych.dump(Represent.new)))
    end

    def test_scalar_coder
      foo = Psych.load(Psych.dump(ScalarCoder.new))
      assert_equal 'foo', foo
    end

    def test_load_dumped_tagging
      foo = InitApi.new
      bar = Psych.unsafe_load(Psych.dump(foo))
      assert_equal false, bar.implicit
      assert_equal "!ruby/object:Psych::TestCoder::InitApi", bar.tag
      assert_equal Psych::Nodes::Mapping::BLOCK, bar.style
    end

    def test_dump_with_tag
      foo = TaggingCoder.new
      assert_match(/hello/, Psych.dump(foo))
      assert_match(/\{aa/, Psych.dump(foo))
    end

    def test_dump_encode_with
      foo = InitApi.new
      assert_match(/aa/, Psych.dump(foo))
    end

    def test_dump_init_with
      foo = InitApi.new
      bar = Psych.unsafe_load(Psych.dump(foo))
      assert_equal foo.a, bar.a
      assert_equal foo.b, bar.b
      assert_nil bar.c
    end

    def test_coder_style_map_default
      foo = Psych.dump a: 1, b: 2
      assert_equal "---\n:a: 1\n:b: 2\n", foo
    end

    def test_coder_style_map_any
      pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/

      foo = Psych.dump CustomEncode.new \
        map: {a: 1, b: 2},
        style: Psych::Nodes::Mapping::ANY,
        tag: nil
      assert_equal "---\n:a: 1\n:b: 2\n", foo
    end

    def test_coder_style_map_block
      pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/

      foo = Psych.dump CustomEncode.new \
        map: {a: 1, b: 2},
        style: Psych::Nodes::Mapping::BLOCK,
        tag: nil
      assert_equal "---\n:a: 1\n:b: 2\n", foo
    end

    def test_coder_style_map_flow
      pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/

      foo = Psych.dump CustomEncode.new \
        map: { a: 1, b: 2 },
        style: Psych::Nodes::Mapping::FLOW,
        tag: nil
      assert_equal "--- {! ':a': 1, ! ':b': 2}\n", foo
    end

    def test_coder_style_seq_default
      foo = Psych.dump [ 1, 2, 3 ]
      assert_equal "---\n- 1\n- 2\n- 3\n", foo
    end

    def test_coder_style_seq_any
      foo = Psych.dump CustomEncode.new \
        seq: [ 1, 2, 3 ],
        style: Psych::Nodes::Sequence::ANY,
        tag: nil
      assert_equal "---\n- 1\n- 2\n- 3\n", foo
    end

    def test_coder_style_seq_block
      foo = Psych.dump CustomEncode.new \
        seq: [ 1, 2, 3 ],
        style: Psych::Nodes::Sequence::BLOCK,
        tag: nil
      assert_equal "---\n- 1\n- 2\n- 3\n", foo
    end

    def test_coder_style_seq_flow
      foo = Psych.dump CustomEncode.new \
        seq: [ 1, 2, 3 ],
        style: Psych::Nodes::Sequence::FLOW,
        tag: nil
      assert_equal "--- [1, 2, 3]\n", foo
    end

    def test_coder_style_scalar_default
      foo = Psych.dump 'some scalar'
      assert_match(/\A--- some scalar\n(?:\.\.\.\n)?\z/, foo)
    end

    def test_coder_style_scalar_any
      foo = Psych.dump CustomEncode.new \
        scalar: 'some scalar',
        style: Psych::Nodes::Scalar::ANY,
        tag: nil
      assert_match(/\A--- some scalar\n(?:\.\.\.\n)?\z/, foo)
    end

    def test_coder_style_scalar_plain
      foo = Psych.dump CustomEncode.new \
        scalar: 'some scalar',
        style: Psych::Nodes::Scalar::PLAIN,
        tag: nil
      assert_match(/\A--- some scalar\n(?:\.\.\.\n)?\z/, foo)
    end

    def test_coder_style_scalar_single_quoted
      foo = Psych.dump CustomEncode.new \
        scalar: 'some scalar',
        style: Psych::Nodes::Scalar::SINGLE_QUOTED,
        tag: nil
      assert_equal "--- ! 'some scalar'\n", foo
    end

    def test_coder_style_scalar_double_quoted
      foo = Psych.dump CustomEncode.new \
        scalar: 'some scalar',
        style: Psych::Nodes::Scalar::DOUBLE_QUOTED,
        tag: nil
      assert_equal %Q'--- ! "some scalar"\n', foo
    end

    def test_coder_style_scalar_literal
      foo = Psych.dump CustomEncode.new \
        scalar: 'some scalar',
        style: Psych::Nodes::Scalar::LITERAL,
        tag: nil
      assert_equal "--- ! |-\n  some scalar\n", foo
    end

    def test_coder_style_scalar_folded
      foo = Psych.dump CustomEncode.new \
        scalar: 'some scalar',
        style: Psych::Nodes::Scalar::FOLDED,
        tag: nil
      assert_equal "--- ! >-\n  some scalar\n", foo
    end
  end
end
