package cowutils

import (
	"context"
	"fmt"
	"io"
	"io/fs"
	"os"
)

// Reflink performs the reflink operation on the passed files, replacing
// dst's contents with src. If fallback is true and reflink fails,
// copy_file_range will be used first, and if that fails too io.Copy will
// be used to copy the data.
func Reflink(ctx context.Context, dst, src *os.File, fallback bool) error {
	err := reflink(dst, src)
	if (err != nil) && fallback {
		// reflink failed, but we can fallback, but first we need to know the file's size
		var st fs.FileInfo
		st, err = src.Stat()
		if err != nil {
			// couldn't stat source, this can't be helped
			return fmt.Errorf("failed to stat source: %w", err)
		}
		_, err = copyFileRange(dst, src, 0, 0, st.Size())
		if err != nil {
			// copyFileRange failed too, switch to simple io copy
			reader := io.NewSectionReader(src, 0, st.Size())
			writer := &sectionWriter{w: dst}
			_ = dst.Truncate(0) // assuming any error in trucate will result in copy error
			_, err = io.Copy(writer, reader)
		}
	}
	return err
}

// ReflinkRange performs a range reflink operation on the passed files, replacing
// part of dst's contents with data from src. If fallback is true and reflink
// fails, copy_file_range will be used first, and if that fails too io.CopyN
// will be used to copy the data.
func ReflinkRange(ctx context.Context, dst, src *os.File, dstOffset, srcOffset, n int64, fallback bool) error {
	err := reflinkRange(dst, src, dstOffset, srcOffset, n)
	if (err != nil) && fallback {
		_, err = copyFileRange(dst, src, dstOffset, srcOffset, n)
	}

	if (err != nil) && fallback {
		// seek both src & dst
		reader := io.NewSectionReader(src, srcOffset, n)
		writer := &sectionWriter{w: dst, base: dstOffset}
		_, err = io.CopyN(writer, reader, n)
	}
	return err
}