package compression

import (
	"io"
	"io/fs"
	"os"
	"runtime"

	"github.com/containers/image/v5/pkg/compression"
	"github.com/sirupsen/logrus"
)

type genericDecompressor struct {
	compressedFilePath string
	compressedFile     *os.File
	compressedFileInfo os.FileInfo
}

func newGenericDecompressor(compressedFilePath string) (*genericDecompressor, error) {
	d := &genericDecompressor{}
	d.compressedFilePath = compressedFilePath
	stat, err := os.Stat(d.compressedFilePath)
	if err != nil {
		return nil, err
	}
	d.compressedFileInfo = stat
	return d, nil
}

func (d *genericDecompressor) compressedFileSize() int64 {
	return d.compressedFileInfo.Size()
}

func (d *genericDecompressor) compressedFileMode() fs.FileMode {
	return d.compressedFileInfo.Mode()
}

func (d *genericDecompressor) compressedFileReader() (io.ReadCloser, error) {
	compressedFile, err := os.Open(d.compressedFilePath)
	if err != nil {
		return nil, err
	}
	d.compressedFile = compressedFile
	return compressedFile, nil
}

func (d *genericDecompressor) decompress(w io.WriteSeeker, r io.Reader) error {
	decompressedFileReader, _, err := compression.AutoDecompress(r)
	if err != nil {
		return err
	}
	defer func() {
		if err := decompressedFileReader.Close(); err != nil {
			logrus.Errorf("Unable to close gz file: %q", err)
		}
	}()

	// Use sparse-optimized copy for macOS as applehv,
	// macOS native hypervisor, uses large raw VM disk
	// files mostly empty (~2GB of content ~8GB empty).
	if runtime.GOOS == macOs {
		err = d.sparseOptimizedCopy(w, decompressedFileReader)
	} else {
		_, err = io.Copy(w, decompressedFileReader)
	}

	return err
}

func (d *genericDecompressor) close() {
	if err := d.compressedFile.Close(); err != nil {
		logrus.Errorf("Unable to close compressed file: %q", err)
	}
}

func (d *genericDecompressor) sparseOptimizedCopy(w io.WriteSeeker, r io.Reader) error {
	var err error
	sparseWriter := NewSparseWriter(w)
	defer func() {
		e := sparseWriter.Close()
		if e != nil && err == nil {
			err = e
		}
	}()
	_, err = io.Copy(sparseWriter, r)
	return err
}
