# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2006-2013, by Nick Sieger.
# Copyright, 2010, by Tohru Hashimoto.
# Copyright, 2011, by Jeff Hodges.
# Copyright, 2011, by Alex Koppel.
# Copyright, 2011, by Christine Yen.
# Copyright, 2011, by Gerrit Riessen.
# Copyright, 2011, by Luke Redpath.
# Copyright, 2013, by Mislav Marohnić.
# Copyright, 2013, by Leo Cassarani.
# Copyright, 2019, by Olle Jonsson.
# Copyright, 2019, by Patrick Davey.
# Copyright, 2021, by Lewis Cowles.
# Copyright, 2021-2024, by Samuel Williams.

module Multipart
  module Post
    # Concatenate together multiple IO objects into a single, composite IO object
    # for purposes of reading as a single stream.
    #
    # @example
    #     crio = CompositeReadIO.new(StringIO.new('one'),
    #                                StringIO.new('two'),
    #                                StringIO.new('three'))
    #     puts crio.read # => "onetwothree"
    class CompositeReadIO
      # Create a new composite-read IO from the arguments, all of which should
      # respond to #read in a manner consistent with IO.
      def initialize(*ios)
        @ios = ios.flatten
        @index = 0
      end

      # Close all the underyling IOs.
      def close
        @ios.each do |io|
          io.close if io.respond_to?(:close)
        end

        @ios = nil
        @index = 0
      end

      def closed?
        @ios.nil?
      end

      # Read from IOs in order until `length` bytes have been received.
      def read(length = nil, outbuf = nil)
        if @ios.nil?
          raise IOError, "CompositeReadIO is closed!"
        end

        got_result = false
        outbuf = outbuf ? outbuf.replace("") : String.new

        while io = current_io
          if result = io.read(length)
            got_result ||= !result.nil?
            result.force_encoding("BINARY") if result.respond_to?(:force_encoding)
            outbuf << result
            length -= result.length if length
            break if length == 0
          end
          advance_io
        end
        (!got_result && length) ? nil : outbuf
      end

      def rewind
        @ios.each { |io| io.rewind }
        @index = 0
      end

      private

      def current_io
        @ios[@index]
      end

      def advance_io
        @index += 1
      end
    end
  end
end

CompositeIO = Multipart::Post::CompositeReadIO
Object.deprecate_constant :CompositeIO
