package video

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"os"

	"github.com/sunfish-shogi/bufseekio"

	"github.com/photoprism/photoprism/pkg/fs"
)

// Chunk represents a fixed length file chunk.
type Chunk [4]byte

// Get returns the chunk as byte array.
func (c Chunk) Get() [4]byte {
	return c
}

// Hex returns the chunk as hex formatted string.
func (c Chunk) Hex() string {
	return fmt.Sprintf("0x%x", c[:])
}

// String returns the chunk as string.
func (c Chunk) String() string {
	return string(c[:])
}

// Bytes returns the chunk as byte slice.
func (c Chunk) Bytes() []byte {
	return c[:]
}

// Uint32 returns the chunk as unsigned integer.
func (c Chunk) Uint32() uint32 {
	return binary.BigEndian.Uint32(c.Bytes())
}

// Equal compares the chunk with a byte slice.
func (c Chunk) Equal(b []byte) bool {
	return bytes.Equal(c.Bytes(), b)
}

// FileOffset returns the index of the chunk, or -1 if it was not found.
func (c Chunk) FileOffset(fileName string) (int, error) {
	if !fs.FileExists(fileName) {
		return -1, errors.New("file not found")
	}

	file, err := os.Open(fileName) //nolint:gosec // fileName validated by caller

	if err != nil {
		return -1, err
	}

	defer file.Close()

	index, err := c.DataOffset(file, 0, -1)

	return index, err
}

// DataOffset returns the index of the chunk in file, or -1 if it was not found.
func (c Chunk) DataOffset(file io.ReadSeeker, offset, maxOffset int) (int, error) {
	if file == nil {
		return -1, errors.New("file is nil")
	}

	data := c.Bytes()
	dataSize := len(data)
	blockSize := 128 * 1024
	buffer := make([]byte, blockSize)

	// Create buffered read seeker.
	r := bufseekio.NewReadSeeker(file, blockSize, dataSize)

	if seekOffset, seekErr := r.Seek(int64(offset), io.SeekStart); seekErr != nil {
		return -1, seekErr
	} else {
		offset = int(seekOffset)
	}

	// Search in batches.
	for {
		n, err := r.Read(buffer)
		buffer = buffer[:n]

		if err != nil {
			if err != io.EOF {
				return -1, err
			}

			break
		} else if n == 0 {
			break
		}

		// Return data index, if found.
		if i := bytes.Index(buffer, data); i >= 0 {
			return offset + i, nil
		}

		offset += n

		// Return if the chunk was not found up to the maximum offset.
		if maxOffset > 0 && maxOffset <= offset {
			return -1, nil
		}
	}

	return -1, nil
}
