# frozen_string_literal: true

# Helps to estimate archive sizes
class ZipKit::SizeEstimator
  require_relative "streamer"

  # Creates a new estimator with a Streamer object. Normally you should use
  # `estimate` instead an not use this method directly.
  #
  # @param streamer[ZipKit::Streamer]
  def initialize(streamer)
    @streamer = streamer
  end
  private :initialize

  # Performs the estimate using fake archiving. It needs to know the sizes of the
  # entries upfront. Usage:
  #
  #     expected_zip_size = SizeEstimator.estimate do | estimator |
  #       estimator.add_stored_entry(filename: "file.doc", size: 898291)
  #       estimator.add_deflated_entry(filename: "family.tif",
  #               uncompressed_size: 89281911, compressed_size: 121908)
  #     end
  #
  # @param kwargs_for_streamer_new Any options to pass to Streamer, see {Streamer#initialize}
  # @return [Integer] the size of the resulting archive, in bytes
  # @yieldparam estimator[SizeEstimator] the estimator
  def self.estimate(**kwargs_for_streamer_new)
    streamer = ZipKit::Streamer.new(ZipKit::NullWriter, **kwargs_for_streamer_new)
    estimator = new(streamer)
    yield(estimator)
    streamer.close # Returns the .tell of the contained IO
  end

  # Add a fake entry to the archive, to see how big it is going to be in the end.
  #
  # @param filename [String] the name of the file (filenames are variable-width in the ZIP)
  # @param size [Integer] size of the uncompressed entry
  # @param use_data_descriptor[Boolean] whether there is going to be a data descriptor written
  #                                     after the entry body, to specify size.
  #                                     You must enable this if you are going to be
  #                                     using {Streamer#write_stored_file} as otherwise your
  #                                     estimated size is not going to be accurate
  # @return self
  def add_stored_entry(filename:, size:, use_data_descriptor: false)
    @streamer.add_stored_entry(filename: filename,
      crc32: 0,
      size: size,
      use_data_descriptor: use_data_descriptor)
    @streamer.simulate_write(size)
    if use_data_descriptor
      @streamer.update_last_entry_and_write_data_descriptor(crc32: 0, compressed_size: size, uncompressed_size: size)
    end
    self
  end

  # Add a fake entry to the archive, to see how big it is going to be in the end.
  #
  # @param filename [String] the name of the file (filenames are variable-width in the ZIP)
  # @param uncompressed_size [Integer] size of the uncompressed entry
  # @param compressed_size [Integer] size of the compressed entry
  # @param use_data_descriptor[Boolean] whether there is going to be a data descriptor written
  #                                     after the entry body, to specify size.
  #                                     You must enable this if you are going to be
  #                                     using {Streamer#write_deflated_file} as otherwise your
  #                                     estimated size is not going to be accurate
  # @return self
  def add_deflated_entry(filename:, uncompressed_size:, compressed_size:, use_data_descriptor: false)
    @streamer.add_deflated_entry(filename: filename,
      crc32: 0,
      compressed_size: compressed_size,
      uncompressed_size: uncompressed_size,
      use_data_descriptor: use_data_descriptor)

    @streamer.simulate_write(compressed_size)
    if use_data_descriptor
      @streamer.update_last_entry_and_write_data_descriptor(crc32: 0,
        compressed_size: compressed_size,
        uncompressed_size: uncompressed_size)
    end
    self
  end

  # Add an empty directory to the archive.
  #
  # @param dirname [String] the name of the directory
  # @return self
  def add_empty_directory_entry(dirname:)
    @streamer.add_empty_directory(dirname: dirname)
    self
  end
end
