package audio

import (
	"encoding/binary"
	"math"
	"os"
)

// Represents the header of a WAV file
type WAVHeader struct {
	RiffMark      [4]byte
	FileSize      uint32
	WaveMark      [4]byte
	FmtMark       [4]byte
	FmtSize       uint32
	FormatType    uint16
	NumChannels   uint16
	SampleRate    uint32
	ByteRate      uint32
	BlockAlign    uint16
	BitsPerSample uint16
	DataMark      [4]byte
	DataSize      uint32
}

// Writes the audio data to a WAV file
func (g *Generator) WriteWAV(filename string, samples []float64) error {
	file, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer file.Close()

	// Ensure the samples are normalized first
	Normalize(samples)

	header := WAVHeader{
		RiffMark:      [4]byte{'R', 'I', 'F', 'F'},
		WaveMark:      [4]byte{'W', 'A', 'V', 'E'},
		FmtMark:       [4]byte{'f', 'm', 't', ' '},
		FmtSize:       16,
		FormatType:    1, // PCM
		NumChannels:   Channels,
		SampleRate:    uint32(g.sampleRate),
		BitsPerSample: Bits,
		ByteRate:      uint32(g.sampleRate * float64(Channels) * float64(Bits) / 8),
		BlockAlign:    uint16(Channels * Bits / 8),
		DataMark:      [4]byte{'d', 'a', 't', 'a'},
	}

	// Calculate sizes - samples are interleaved LRLR...
	header.DataSize = uint32(len(samples) * int(Bits) / 8)
	header.FileSize = 36 + header.DataSize // 36 is the size of the header up to data

	// Write header
	if err := binary.Write(file, binary.LittleEndian, &header); err != nil {
		return err
	}

	// Write audio data - batch convert and write for better performance
	const batchSize = 8192 // Write in 8KB chunks
	int16Buffer := make([]int16, batchSize)

	for i := 0; i < len(samples); i += batchSize {
		end := i + batchSize
		if end > len(samples) {
			end = len(samples)
		}

		// Convert batch of samples to int16
		for j := i; j < end; j++ {
			sample := samples[j]
			// Clamp to prevent overflow
			if sample > 1.0 {
				sample = 1.0
			} else if sample < -1.0 {
				sample = -1.0
			}
			int16Buffer[j-i] = int16(sample * 32767.0)
		}

		// Write entire batch at once
		if err := binary.Write(file, binary.LittleEndian, int16Buffer[:end-i]); err != nil {
			return err
		}
	}

	return nil
}

// Normalizes the audio buffer to prevent clipping
func Normalize(buffer []float64) {
	if len(buffer) == 0 {
		return
	}

	// Find the maximum absolute value in the buffer
	maxVal := 0.0
	for _, sample := range buffer {
		absVal := math.Abs(sample)
		if absVal > maxVal {
			maxVal = absVal
		}
	}

	// If the maximum is very small, don't normalize to avoid amplifying noise
	if maxVal < 0.0001 {
		return
	}

	// Normalize to 90% of maximum to avoid clipping
	scale := 0.9 / maxVal
	for i := range buffer {
		buffer[i] *= scale
	}
}
