package dsp

import "math"

// Biquad is a generic second-order IIR filter (direct form II transposed).
type Biquad struct {
	b0, b1, b2 float64
	a1, a2     float64
	z1, z2     float64
}

// Process filters one sample.
func (f *Biquad) Process(in float64) float64 {
	out := f.b0*in + f.z1
	f.z1 = f.b1*in - f.a1*out + f.z2
	f.z2 = f.b2*in - f.a2*out
	return out
}

// Reset clears state.
func (f *Biquad) Reset() { f.z1 = 0; f.z2 = 0 }

// FilterChain cascades multiple biquad sections in series.
// Used for higher-order filters (e.g. 4th-order = 2 biquads).
type FilterChain struct {
	Stages []Biquad
}

// Process runs input through all stages in series.
func (c *FilterChain) Process(in float64) float64 {
	x := in
	for i := range c.Stages {
		x = c.Stages[i].Process(x)
	}
	return x
}

// Reset clears all filter state.
func (c *FilterChain) Reset() {
	for i := range c.Stages {
		c.Stages[i].Reset()
	}
}

// --- Factory functions ---

// NewBiquadLPF creates a 2nd-order Butterworth lowpass (Q = 1/√2).
func NewBiquadLPF(cutoffHz, sampleRate float64) *Biquad {
	return newBiquadLPFWithQ(cutoffHz, sampleRate, math.Sqrt2/2)
}

func newBiquadLPFWithQ(cutoffHz, sampleRate, q float64) *Biquad {
	if cutoffHz <= 0 || sampleRate <= 0 || cutoffHz >= sampleRate/2 {
		return &Biquad{b0: 1} // passthrough
	}
	omega := 2 * math.Pi * cutoffHz / sampleRate
	cosW := math.Cos(omega)
	sinW := math.Sin(omega)
	alpha := sinW / (2 * q)
	a0 := 1 + alpha
	return &Biquad{
		b0: (1 - cosW) / 2 / a0,
		b1: (1 - cosW) / a0,
		b2: (1 - cosW) / 2 / a0,
		a1: (-2 * cosW) / a0,
		a2: (1 - alpha) / a0,
	}
}

// NewLPF4 creates a 4th-order Butterworth lowpass (two cascaded biquads).
func NewLPF4(cutoffHz, sampleRate float64) *FilterChain {
	q1 := 1.0 / (2 * math.Cos(math.Pi/8))   // ≈ 0.5412
	q2 := 1.0 / (2 * math.Cos(3*math.Pi/8))  // ≈ 1.3066
	return &FilterChain{
		Stages: []Biquad{
			*newBiquadLPFWithQ(cutoffHz, sampleRate, q1),
			*newBiquadLPFWithQ(cutoffHz, sampleRate, q2),
		},
	}
}

// NewLPF8 creates an 8th-order Butterworth lowpass (four cascaded biquads).
// Provides -48dB/octave rolloff. At 228kHz with fc=15kHz:
//
//	15kHz: -6dB, 19kHz: -28dB, 38kHz: -72dB, 57kHz: -108dB
func NewLPF8(cutoffHz, sampleRate float64) *FilterChain {
	// 8th-order Butterworth pole angles: π/16, 3π/16, 5π/16, 7π/16
	q1 := 1.0 / (2 * math.Cos(math.Pi/16))    // ≈ 0.5098
	q2 := 1.0 / (2 * math.Cos(3*math.Pi/16))   // ≈ 0.6013
	q3 := 1.0 / (2 * math.Cos(5*math.Pi/16))   // ≈ 0.8999
	q4 := 1.0 / (2 * math.Cos(7*math.Pi/16))   // ≈ 2.5629
	return &FilterChain{
		Stages: []Biquad{
			*newBiquadLPFWithQ(cutoffHz, sampleRate, q1),
			*newBiquadLPFWithQ(cutoffHz, sampleRate, q2),
			*newBiquadLPFWithQ(cutoffHz, sampleRate, q3),
			*newBiquadLPFWithQ(cutoffHz, sampleRate, q4),
		},
	}
}

// NewNotch creates a 2nd-order IIR notch (bandstop) filter.
// Q controls width: higher Q = narrower notch.
// Typical: Q=5 → ~4kHz wide at -3dB, Q=10 → ~2kHz wide.
func NewNotch(centerHz, sampleRate, q float64) *Biquad {
	if centerHz <= 0 || sampleRate <= 0 || centerHz >= sampleRate/2 {
		return &Biquad{b0: 1}
	}
	omega := 2 * math.Pi * centerHz / sampleRate
	cosW := math.Cos(omega)
	alpha := math.Sin(omega) / (2 * q)
	a0 := 1 + alpha
	return &Biquad{
		b0: 1 / a0,
		b1: -2 * cosW / a0,
		b2: 1 / a0,
		a1: -2 * cosW / a0,
		a2: (1 - alpha) / a0,
	}
}

// NewChebyshevI creates an Nth-order Chebyshev Type I lowpass filter.
// Passband ripple in dB (typ. 0.5), then steep rolloff into stopband.
// Much steeper transition band than Butterworth at the same order.
// At 228kHz, 8th-order, 0.5dB ripple, fc=15kHz: -40dB@19kHz (vs -17dB Butterworth).
func NewChebyshevI(order int, rippleDB, cutoffHz, sampleRate float64) *FilterChain {
	if order < 2 || order%2 != 0 {
		return &FilterChain{Stages: []Biquad{{b0: 1}}}
	}
	if cutoffHz <= 0 || sampleRate <= 0 || cutoffHz >= sampleRate/2 {
		return &FilterChain{Stages: []Biquad{{b0: 1}}}
	}

	N := order
	nSections := N / 2

	// Chebyshev parameters
	epsilon := math.Sqrt(math.Pow(10, rippleDB/10) - 1)
	v := math.Asinh(1/epsilon) / float64(N)

	// Bilinear transform constant and frequency pre-warp
	c := 2.0 * sampleRate
	warp := c * math.Tan(math.Pi*cutoffHz/sampleRate)

	stages := make([]Biquad, nSections)

	for i := 0; i < nSections; i++ {
		// Analog prototype pole (normalized Ωc=1)
		angle := float64(2*i+1) * math.Pi / float64(2*N)
		sigmaN := -math.Sinh(v) * math.Sin(angle)
		omegaN := math.Cosh(v) * math.Cos(angle)

		// Scale to actual cutoff frequency
		sigma := sigmaN * warp
		omega := omegaN * warp

		// Analog section: H(s) = A / (s² + Bs + A)
		A := sigma*sigma + omega*omega
		B := -2 * sigma // positive (sigma is negative)

		// Bilinear transform to digital biquad
		c2 := c * c
		a0 := c2 + B*c + A

		stages[i] = Biquad{
			b0: A / a0,
			b1: 2 * A / a0,
			b2: A / a0,
			a1: (-2*c2 + 2*A) / a0,
			a2: (c2 - B*c + A) / a0,
		}
	}

	// Normalize DC gain to unity (Chebyshev even-order has -ripple at DC)
	dcGain := 1.0
	for _, s := range stages {
		dcGain *= (s.b0 + s.b1 + s.b2) / (1 + s.a1 + s.a2)
	}
	if dcGain > 0 {
		corr := 1.0 / dcGain
		stages[0].b0 *= corr
		stages[0].b1 *= corr
		stages[0].b2 *= corr
	}

	return &FilterChain{Stages: stages}
}

// --- Broadcast-specific filter factories ---

// NewAudioLPF creates the broadcast-standard audio lowpass at 15kHz.
// 8th-order Chebyshev Type I with 0.5dB passband ripple.
// Flat to 15kHz, then steep wall: -40dB@19kHz (vs -17dB Butterworth).
// Two passes through clip-filter-clip: -80dB broadband at 19kHz.
func NewAudioLPF(sampleRate float64) *FilterChain {
	return NewChebyshevI(8, 0.5, 15000, sampleRate)
}

// NewPilotNotch creates a double-cascade 19kHz notch for maximum
// rejection at the pilot frequency. Q=15: only 1.3kHz wide (18.4–19.6kHz).
// The 8th-order LPF handles broadband; this kills the exact 19kHz peak.
func NewPilotNotch(sampleRate float64) *FilterChain {
	return &FilterChain{
		Stages: []Biquad{
			*NewNotch(19000, sampleRate, 15),
			*NewNotch(19000, sampleRate, 15),
		},
	}
}

// NewCompositeProtection creates double-cascade notch filters for the
// composite clipper. Q=10: ~1.9kHz wide at 19kHz, ~5.7kHz wide at 57kHz.
// Narrow enough to preserve audio/stereo, deep enough to protect pilot/RDS.
func NewCompositeProtection(sampleRate float64) (notch19, notch57 *FilterChain) {
	notch19 = &FilterChain{
		Stages: []Biquad{
			*NewNotch(19000, sampleRate, 10),
			*NewNotch(19000, sampleRate, 10),
		},
	}
	notch57 = &FilterChain{
		Stages: []Biquad{
			*NewNotch(57000, sampleRate, 10),
			*NewNotch(57000, sampleRate, 10),
		},
	}
	return
}
