package archives

import (
	"bytes"
	"context"
	"io"
	"path/filepath"
	"strings"

	"github.com/klauspost/compress/gzip"
	"github.com/klauspost/pgzip"
)

func init() {
	RegisterFormat(Gz{})
}

// Gz facilitates gzip compression.
type Gz struct {
	// Gzip compression level. See https://pkg.go.dev/compress/flate#pkg-constants
	// for some predefined constants. If 0, DefaultCompression is assumed rather
	// than no compression.
	CompressionLevel int

	// DisableMultistream controls whether the reader supports multistream files.
	// See https://pkg.go.dev/compress/gzip#example-Reader.Multistream
	DisableMultistream bool

	// Use a fast parallel Gzip implementation. This is only
	// effective for large streams (about 1 MB or greater).
	Multithreaded bool
}

func (Gz) Extension() string { return ".gz" }
func (Gz) MediaType() string { return "application/gzip" }

func (gz Gz) Match(_ context.Context, filename string, stream io.Reader) (MatchResult, error) {
	var mr MatchResult

	// match filename
	if filepath.Ext(strings.ToLower(filename)) == gz.Extension() {
		mr.ByName = true
	}

	// match file header
	buf, err := readAtMost(stream, len(gzHeader))
	if err != nil {
		return mr, err
	}
	mr.ByStream = bytes.Equal(buf, gzHeader)

	return mr, nil
}

func (gz Gz) OpenWriter(w io.Writer) (io.WriteCloser, error) {
	// assume default compression level if 0, rather than no
	// compression, since no compression on a gzipped file
	// doesn't make any sense in our use cases
	level := gz.CompressionLevel
	if level == 0 {
		level = gzip.DefaultCompression
	}

	var wc io.WriteCloser
	var err error
	if gz.Multithreaded {
		wc, err = pgzip.NewWriterLevel(w, level)
	} else {
		wc, err = gzip.NewWriterLevel(w, level)
	}
	return wc, err
}

func (gz Gz) OpenReader(r io.Reader) (io.ReadCloser, error) {
	if gz.Multithreaded {
		gzR, err := pgzip.NewReader(r)
		if gzR != nil && gz.DisableMultistream {
			gzR.Multistream(false)
		}
		return gzR, err
	}

	gzR, err := gzip.NewReader(r)
	if gzR != nil && gz.DisableMultistream {
		gzR.Multistream(false)
	}
	return gzR, err
}

// magic number at the beginning of gzip files
var gzHeader = []byte{0x1f, 0x8b}
