package audio

import (
	"log"
	"math"
	"sync"

	"git.atri.dad/atridad/muse/schema"
)

const (
	SampleRate = 44100
	Bits       = 16
	Channels   = 2
)

// Handles audio synthesis
type Generator struct {
	sampleRate float64
	bufferPool sync.Pool
}

// Creates a new audio generator
func NewGenerator() *Generator {
	return &Generator{
		sampleRate: float64(SampleRate),
		bufferPool: sync.Pool{
			New: func() interface{} {
				return make([]float64, 0, 4096)
			},
		},
	}
}

// Renders the complete song to audio samples
func (g *Generator) Generate(song schema.Song) ([]float64, error) {
	samplesPerBeat := (60.0 / float64(song.BPM)) * g.sampleRate
	log.Printf("Generating song at %.1f BPM, sample rate: %.0f, samples per beat: %.1f\n",
		float64(song.BPM), g.sampleRate, samplesPerBeat)

	totalBeats := song.CalculateTotalLength()
	log.Printf("Total song length: %.1f beats", totalBeats)

	totalSamples := int(float64(totalBeats) * samplesPerBeat)
	buffer := make([]float64, totalSamples*Channels)

	if totalSamples == 0 {
		log.Printf("Warning: Zero samples calculated for song")
	}

	var wg sync.WaitGroup
	var mu sync.Mutex

	for _, track := range song.Tracks {
		wg.Add(1)
		go func(track schema.Track) {
			defer wg.Done()

			patternLength := len(track.Pattern)
			if patternLength == 0 {
				return
			}

			for beat := 0; beat < int(totalBeats); beat++ {
				var note string

				if track.Loop {
					patternIndex := beat % patternLength
					note = track.Pattern[patternIndex]
				} else {
					if beat < patternLength {
						note = track.Pattern[beat]
					} else {
						note = "."
					}
				}

				if note == "." || note == "" {
					continue
				}

				startSample := int(float64(beat) * samplesPerBeat)
				endSample := startSample + int(samplesPerBeat)

				if startSample >= len(buffer)/Channels {
					continue
				}
				if endSample > len(buffer)/Channels {
					endSample = len(buffer) / Channels
				}

				freq := noteToFreq(note)
				amplitude := 0.5
				duration := float64(endSample-startSample) / g.sampleRate

				audioData := g.generateOscillator(track.Instrument, freq, duration, amplitude*track.Volume, 0)
				if len(audioData) == 0 {
					continue
				}

				if len(track.Effects) > 0 {
					effectChain := CreateEffectChain(track.Effects, g.sampleRate)
					audioData = effectChain.Process(audioData)
				}

				pan := (track.Pan + 1) / 2
				leftGain := math.Sqrt(1.0 - pan)
				rightGain := math.Sqrt(pan)

				mu.Lock()
				for i, sample := range audioData {
					if startSample+i >= len(buffer)/Channels {
						break
					}

					leftSample := sample * leftGain
					rightSample := sample * rightGain

					buffer[(startSample+i)*Channels] += leftSample
					buffer[(startSample+i)*Channels+1] += rightSample
				}
				mu.Unlock()
			}
		}(track)
	}

	wg.Wait()
	return buffer, nil
}
