# frozen_string_literal: true

require_relative '../spec_helper'
require 'stringio'

module Aws
  describe ParamConverter do
    describe 'convert' do

      it 'performs a deeply nested conversion of values' do

        data = double('data')

        params = Structure.new(:boolean, :string_map, :nested_list, :unknown)
        params = params.new({
          boolean: true,
          string_map: { color: :red },
          nested_list: [
            { integer: 1 },
            { integer: 2.0 },
            { integer: '3' },
          ],
          unknown: data
        })

        rules = ApiHelper.sample_api.operation(:example_operation).input
        converted = ParamConverter.convert(rules, params)
        expect(converted.to_h).to eq({
          boolean: true,
          string_map: { 'color' => 'red' },
          nested_list: [
            { integer: 1 },
            { integer: 2 },
            { integer: 3 },
          ],
          unknown: data
        })
      end

    end

    describe 'default converstions' do

      describe 'undescribed' do

        it 'returns the value unmodified if the shape class is unknown' do
          shape_class = Class.new
          value = 'raw'
          expect(ParamConverter.c(shape_class, value)).to be(value)
        end

        it 'returns the value unmodified if the value class is unknown' do
          shape_class = Seahorse::Model::Shapes::StringShape
          value = double('raw')
          expect(ParamConverter.c(shape_class, value)).to be(value)
        end

      end

      describe 'structures' do

        let(:shape_class) { Seahorse::Model::Shapes::StructureShape }

        it 'returns duplicate hashes' do
          value = { a: 1 }
          converted = ParamConverter.c(shape_class, value)
          expect(converted).to eq(value)
          expect(converted).not_to be(value)
        end

        it 'does not modify structs' do
          value = ::Struct.new(:a).new(1)
          converted = ParamConverter.c(shape_class, value)
          expect(converted).to be(value)
        end

      end

      describe 'maps' do

        let(:shape_class) { Seahorse::Model::Shapes::MapShape }

        it 'returns duplicate hashes' do
          value = { a: 1 }
          converted = ParamConverter.c(shape_class, value)
          expect(converted).to eq(value)
          expect(converted).not_to be(value)
        end

        it 'creates a hash from a struct' do
          value = ::Struct.new(:a).new(1)
          converted = ParamConverter.c(shape_class, value)
          expect(converted).to eq(a: 1)
        end

      end

      describe 'lists' do

        let(:shape_class) { Seahorse::Model::Shapes::ListShape }

        it 'duplicates arrays' do
          value = [1,2,3]
          converted = ParamConverter.c(shape_class, value)
          expect(converted).to eq(value)
          expect(converted).not_to be(value)
        end

        it 'converts enumerables into arrays' do
          value = [1,2,3].enum_for(:each)
          converted = ParamConverter.c(shape_class, value)
          expect(converted).to eq([1,2,3])
        end

      end

      describe 'strings' do

        let(:shape_class) { Seahorse::Model::Shapes::StringShape }

        it 'returns strings unmodified' do
          expect(ParamConverter.c(shape_class, 'abc')).to eq('abc')
        end

        it 'converts symbols to strings' do
          expect(ParamConverter.c(shape_class, :abc)).to eq('abc')
        end

      end

      describe 'integers' do

        let(:shape_class) { Seahorse::Model::Shapes::IntegerShape }

        it 'returns integers unmodified' do
          expect(ParamConverter.c(shape_class, 123)).to eq(123)
        end

        it 'converts floats to integers' do
          expect(ParamConverter.c(shape_class, 12.34)).to eq(12)
        end

        it 'casts strings to integers' do
          expect(ParamConverter.c(shape_class, '123')).to eq(123)
        end

        it 'returns strings unmodified if cast fails' do
          expect(ParamConverter.c(shape_class, 'abc')).to eq('abc')
        end

      end

      describe 'floats' do

        let(:shape_class) { Seahorse::Model::Shapes::FloatShape }

        it 'returns floats unmodified' do
          expect(ParamConverter.c(shape_class, 12.34)).to eq(12.34)
        end

        it 'converts integers to floats' do
          expect(ParamConverter.c(shape_class, 12)).to eq(12.0)
        end

        it 'casts strings to floats' do
          expect(ParamConverter.c(shape_class, '12.34')).to eq(12.34)
        end

        it 'returns strings unmodified if cast fails' do
          expect(ParamConverter.c(shape_class, 'abc')).to eq('abc')
        end

      end

      describe 'timestamps' do

        let(:shape_class) { Seahorse::Model::Shapes::TimestampShape }

        it 'returns Time objects unmodfied' do
          time = Time.now
          expect(ParamConverter.c(shape_class, time)).to be(time)
        end

        it 'returns DateTime objects as a Time object' do
          time = DateTime.now
          expect(ParamConverter.c(shape_class, time)).to eq(time.to_time)
        end

        it 'returns Date objects as a Time object' do
          time = Date.new
          expect(ParamConverter.c(shape_class, time)).to eq(time.to_time)
        end

        it 'converts integers to Time objects' do
          time = Time.now.to_i
          expect(ParamConverter.c(shape_class, time)).to eq(Time.at(time))
        end

        it 'parses strings as time objets' do
          t1 = Time.now.utc.iso8601
          t2 = Time.now.rfc822
          t3 = Time.now.to_s
          t4 = '2013-01-02'
          expect(ParamConverter.c(shape_class, t1)).to eq(Time.parse(t1))
          expect(ParamConverter.c(shape_class, t2)).to eq(Time.parse(t2))
          expect(ParamConverter.c(shape_class, t3)).to eq(Time.parse(t3))
          expect(ParamConverter.c(shape_class, t4)).to eq(Time.parse(t4))
        end

        it 'returns strings unmodified if they can not be parsed' do
          expect(ParamConverter.c(shape_class, 'abc')).to eq('abc')
        end

      end

      describe 'booleans' do

        let(:shape_class) { Seahorse::Model::Shapes::BooleanShape }

        it 'returns true and false' do
          expect(ParamConverter.c(shape_class, true)).to be(true)
          expect(ParamConverter.c(shape_class, false)).to be(false)
        end

        it 'does not translate nil' do
          expect(ParamConverter.c(shape_class, nil)).to be(nil)
        end

      end

      describe 'blobs' do

        let(:shape_class) { Seahorse::Model::Shapes::BlobShape }

        it 'accepts io objects (like file)' do
          file = File.open(__FILE__, 'r')
          expect(ParamConverter.c(shape_class, file)).to be(file)
          file.close
        end

        it 'accepts io objects (like stringio)' do
          io = StringIO.new('abc')
          expect(ParamConverter.c(shape_class, io)).to be(io)
        end

        it 'accepts io objects (like tempfiles)' do
          file = Tempfile.new('abc')
          expect(ParamConverter.c(shape_class, file)).to be(file)
          file.close
          file.delete
        end

        it 'accepts strings' do
          expect(ParamConverter.c(shape_class, 'abc')).to eq('abc')
        end

      end

    end

    describe '.add' do

      it 'registers a new converter' do
        shape_class = Class.new
        ParamConverter.add(shape_class, String) { |s| s.to_sym }
        expect(ParamConverter.c(shape_class, 'abc')).to eq(:abc)
      end

      it 'can convert values based on parent value classes' do
        shape_class = Class.new
        special_string = Class.new(String)
        str = special_string.new('raw')
        ParamConverter.add(shape_class, special_string) { |s| 'converted' }
        expect(ParamConverter.c(shape_class, str)).to eq('converted')
      end

      it 'can convert values based on parent shape classes' do
        base = Class.new
        extended = Class.new(base)
        ParamConverter.add(base, String) { |s| 'converted' }
        expect(ParamConverter.c(extended, 'raw')).to eq('converted')
      end

      it 'replaces an existing converter' do
        shape_class = Class.new
        ParamConverter.add(shape_class, String) { |s| 'first' }
        ParamConverter.add(shape_class, String) { |s| 'second' }
        expect(ParamConverter.c(shape_class, 'value')).to eq('second')
      end

    end
  end
end
