//
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//

package snapshot

import (
	"archive/tar"
	"compress/gzip"
	"io"
	"os"
	"path/filepath"

	"github.com/jaypipes/ghw/pkg/option"
)

const (
	TargetRoot = "ghw-snapshot-*"
)

const (
	// If set, `ghw` will not unpack the snapshot in the user-supplied directory
	// unless the aforementioned directory is empty.
	OwnTargetDirectory = 1 << iota
)

// Clanup removes the unpacket snapshot from the target root.
// Please not that the environs variable `GHW_SNAPSHOT_PRESERVE`, if set,
// will make this function silently skip.
func Cleanup(targetRoot string) error {
	if option.EnvOrDefaultSnapshotPreserve() {
		return nil
	}
	return os.RemoveAll(targetRoot)
}

// Unpack expands the given snapshot in a temporary directory managed by `ghw`. Returns the path of that directory.
func Unpack(snapshotName string) (string, error) {
	targetRoot, err := os.MkdirTemp("", TargetRoot)
	if err != nil {
		return "", err
	}
	_, err = UnpackInto(snapshotName, targetRoot, 0)
	return targetRoot, err
}

// UnpackInto expands the given snapshot in a client-supplied directory.
// Returns true if the snapshot was actually unpacked, false otherwise
func UnpackInto(snapshotName, targetRoot string, flags uint) (bool, error) {
	if (flags&OwnTargetDirectory) == OwnTargetDirectory && !isEmptyDir(targetRoot) {
		return false, nil
	}
	snap, err := os.Open(snapshotName)
	if err != nil {
		return false, err
	}
	defer snap.Close()
	return true, Untar(targetRoot, snap)
}

// Untar extracts data from the given reader (providing data in tar.gz format) and unpacks it in the given directory.
func Untar(root string, r io.Reader) error {
	var err error
	gzr, err := gzip.NewReader(r)
	if err != nil {
		return err
	}
	defer gzr.Close()

	tr := tar.NewReader(gzr)
	for {
		header, err := tr.Next()
		if err == io.EOF {
			// we are done
			return nil
		}

		if err != nil {
			// bail out
			return err
		}

		if header == nil {
			// TODO: how come?
			continue
		}

		target := filepath.Join(root, header.Name)
		mode := os.FileMode(header.Mode)

		switch header.Typeflag {
		case tar.TypeDir:
			err = os.MkdirAll(target, mode)
			if err != nil {
				return err
			}

		case tar.TypeReg:
			dst, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, mode)
			if err != nil {
				return err
			}

			_, err = io.Copy(dst, tr)
			if err != nil {
				return err
			}

			dst.Close()

		case tar.TypeSymlink:
			err = os.Symlink(header.Linkname, target)
			if err != nil {
				return err
			}
		}
	}
}

func isEmptyDir(name string) bool {
	entries, err := os.ReadDir(name)
	if err != nil {
		return false
	}
	return len(entries) == 0
}
