package filecheck

import (
	"encoding/hex"
	"hash"
	"io"
	"os"
)

// HashMatches reports whether the hash of the file at fp matches chksum.
// If any error occurs while opening or reading the file, it returns false along
// with the error.
func HashMatches(fp string, chksum string, h hash.Hash) (bool, error) {
	f, err := os.Open(fp)
	if err != nil {
		return false, err
	}
	defer f.Close()
	if _, err = io.Copy(h, f); err != nil {
		return false, err
	}
	return hex.EncodeToString(h.Sum(nil)) == chksum, nil
}

// EqualContent returns if the content of the files at the specified paths are
// equal. If any error occurs while opening or reading the files, it returns
// false along with the error.
func EqualContent(fp1, fp2 string) (bool, error) {
	f1, err := os.Open(fp1)
	if err != nil {
		return false, err
	}
	defer f1.Close()

	f2, err := os.Open(fp2)
	if err != nil {
		return false, err
	}
	defer f2.Close()

	const chunkSize = 8192
	buf1 := make([]byte, chunkSize)
	buf2 := make([]byte, chunkSize)

	for {
		n1, err1 := f1.Read(buf1)
		n2, err2 := f2.Read(buf2)

		if n1 != n2 || string(buf1[:n1]) != string(buf2[:n2]) {
			return false, nil
		}

		if err1 == io.EOF && err2 == io.EOF {
			break
		}
		if err1 != nil && err1 != io.EOF {
			return false, err1
		}
		if err2 != nil && err2 != io.EOF {
			return false, err2
		}
	}

	return true, nil
}

// Exists checks if a file or directory exists at the specified path.
func Exists(path string) bool {
	_, err := os.Stat(path)
	return !os.IsNotExist(err)
}

// IsExecutable checks if the file at the specified path is a regular file
// and has execute permissions for the owner, group, or others.
func IsExecutable(path string) (bool, error) {
	info, err := os.Stat(path)
	if err != nil {
		return false, err
	}
	m := info.Mode()
	return m.IsRegular() && m.Perm()&0o111 != 0, nil
}
