#
# Copyright 2014-2018 Chef Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the ''License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require "rubygems/package" unless defined?(Gem::Package)
require "zlib" unless defined?(Zlib)

module Omnibus
  class Compressor::TGZ < Compressor::Base
    id :tgz

    setup do
      # Copy the compiled package (.deb, .rpm, .pkg) into the staging directory
      copy_file(packager.package_path, "#{staging_dir}/")
    end

    build do
      write_tgz
    end

    #
    # @!group DSL methods
    # --------------------------------------------------

    #
    # Set or return the level of compression to use when generating the zipped
    # tarball. Default: max compression.
    #
    # @example
    #   compression_level 9
    #
    # @param [Fixnum] val
    #   the compression level to use
    #
    # @return [Fixnum]
    #
    def compression_level(val = NULL)
      if null?(val)
        @compression_level || Zlib::BEST_COMPRESSION
      else
        unless val.is_a?(Integer)
          raise InvalidValue.new(:compression_level, "be an Integer")
        end

        unless val.between?(1, 9)
          raise InvalidValue.new(:compression_level, "be between 1-9")
        end

        @compression_level = val
      end
    end
    expose :compression_level

    #
    # @!endgroup
    # --------------------------------------------------

    #
    # @see Base#package_name
    #
    def package_name
      "#{packager.package_name}.tar.gz"
    end

    #
    # Write the tar.gz to disk, reading in 1024 bytes at a time to reduce
    # memory usage.
    #
    # @return [void]
    #
    def write_tgz
      # Grab the contents of the gzipped tarball for reading
      contents = gzipped_tarball

      # Write the .tar.gz into the staging directory
      File.open("#{staging_dir}/#{package_name}", "wb") do |tgz|
        while chunk = contents.read(1024)
          tgz.write(chunk)
        end
      end

      # Copy the .tar.gz into the package directory
      FileSyncer.glob("#{staging_dir}/*.tar.gz").each do |tgz|
        copy_file(tgz, Config.package_dir)
      end
    end

    #
    # Create an in-memory tarball from the given packager.
    #
    # @return [StringIO]
    #
    def tarball
      tarfile = StringIO.new("")
      Gem::Package::TarWriter.new(tarfile) do |tar|
        path = "#{staging_dir}/#{packager.package_name}"
        name = packager.package_name
        mode = File.stat(path).mode

        tar.add_file(name, mode) do |tf|
          File.open(path, "rb") do |file|
            tf.write(file.read)
          end
        end
      end

      tarfile.rewind
      tarfile
    end

    #
    # Create the gzipped tarball. See {#tarball} for how the tarball is
    # constructed. This method uses maximum gzip compression, unless the user
    # specifies a different compression level.
    #
    # @return [StringIO]
    #
    def gzipped_tarball
      gz = StringIO.new("")
      z = Zlib::GzipWriter.new(gz, compression_level)
      z.write(tarball.string)
      z.close

      # z was closed to write the gzip footer, so
      # now we need a new StringIO
      StringIO.new(gz.string)
    end
  end
end
