// Copyright ©2020 The Gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package window

import "math"

// Gaussian can modify a sequence using the Gaussian window and return the result.
// See https://en.wikipedia.org/wiki/Window_function#Gaussian_window
// and https://www.recordingblogs.com/wiki/gaussian-window for details.
//
// The Gaussian window is an adjustable window.
//
// The sequence weights are
//  w[k] = exp(-0.5 * ((k-M)/(σ*M))² ), M = (N-1)/2,
// for k=0,1,...,N-1 where N is the length of the window.
//
// The properties of the window depend on the value of σ (sigma).
// It can be used as high or low resolution window, depending of the σ value.
//
// Spectral leakage parameters are summarized in the table:
//         |  σ=0.3  |  σ=0.5 |  σ=1.2 |
//  -------|---------------------------|
//  ΔF_0   |   8     |   3.4  |   2.2  |
//  ΔF_0.5 |   1.82  |   1.2  |   0.94 |
//  K      |   4     |   1.7  |   1.1  |
//  ɣ_max  | -65     | -31.5  | -15.5  |
//  β      |  -8.52  |  -4.48 |  -0.96 |
type Gaussian struct {
	Sigma float64
}

// Transform applies the Gaussian transformation to seq in place, using the value
// of the receiver as the sigma parameter, and returning the result.
func (g Gaussian) Transform(seq []float64) []float64 {
	a := float64(len(seq)-1) / 2
	for i := range seq {
		x := -0.5 * math.Pow((float64(i)-a)/(g.Sigma*a), 2)
		seq[i] *= math.Exp(x)
	}
	return seq
}

// TransformComplex applies the Gaussian transformation to seq in place, using the value
// of the receiver as the sigma parameter, and returning the result.
func (g Gaussian) TransformComplex(seq []complex128) []complex128 {
	a := float64(len(seq)-1) / 2
	for i, v := range seq {
		x := -0.5 * math.Pow((float64(i)-a)/(g.Sigma*a), 2)
		w := math.Exp(x)
		seq[i] = complex(w*real(v), w*imag(v))
	}
	return seq
}

// Tukey can modify a sequence using the Tukey window and return the result.
// See https://en.wikipedia.org/wiki/Window_function#Tukey_window
// and https://www.recordingblogs.com/wiki/tukey-window for details.
//
// The Tukey window is an adjustable window.
//
// The sequence weights are
//  w[k] = 0.5 * (1 + cos(π*(|k - M| - αM)/((1-α) * M))), |k - M| ≥ αM
//       = 1, |k - M| < αM
// with M = (N - 1)/2 for k=0,1,...,N-1 where N is the length of the window.
//
// Spectral leakage parameters are summarized in the table:
//         |  α=0.3 |  α=0.5 |  α=0.7 |
//  -------|--------------------------|
//  ΔF_0   |   1.33 |   1.22 |   1.13 |
//  ΔF_0.5 |   1.28 |   1.16 |   1.04 |
//  K      |   0.67 |   0.61 |   0.57 |
//  ɣ_max  | -18.2  | -15.1  | -13.8  |
//  β      |  -1.41 |  -2.50 |  -3.74 |
type Tukey struct {
	Alpha float64
}

// Transform applies the Tukey transformation to seq in place, using the value
// of the receiver as the Alpha parameter, and returning the result
func (t Tukey) Transform(seq []float64) []float64 {
	switch {
	case t.Alpha <= 0:
		return Rectangular(seq)
	case t.Alpha >= 1:
		return Hann(seq)
	default:
		alphaL := t.Alpha * float64(len(seq)-1)
		width := int(0.5*alphaL) + 1
		for i := range seq[:width] {
			w := 0.5 * (1 - math.Cos(2*math.Pi*float64(i)/alphaL))
			seq[i] *= w
			seq[len(seq)-1-i] *= w
		}
		return seq
	}
}

// TransformComplex applies the Tukey transformation to seq in place, using the value
// of the receiver as the Alpha parameter, and returning the result
func (t Tukey) TransformComplex(seq []complex128) []complex128 {
	switch {
	case t.Alpha <= 0:
		return RectangularComplex(seq)
	case t.Alpha >= 1:
		return HannComplex(seq)
	default:
		alphaL := t.Alpha * float64(len(seq)-1)
		width := int(0.5*alphaL) + 1
		for i, v := range seq[:width] {
			w := 0.5 * (1 - math.Cos(2*math.Pi*float64(i)/alphaL))
			v = complex(w*real(v), w*imag(v))
			seq[i] = v
			seq[len(seq)-1-i] = v
		}
		return seq
	}
}

// Values is an arbitrary real window function.
type Values []float64

// NewValues returns a Values of length n with weights corresponding to the
// provided window function.
func NewValues(window func([]float64) []float64, n int) Values {
	v := make(Values, n)
	for i := range v {
		v[i] = 1
	}
	return window(v)
}

// Transform applies the weights in the receiver to seq in place, returning the
// result. If v is nil, Transform is a no-op, otherwise the length of v must
// match the length of seq.
func (v Values) Transform(seq []float64) []float64 {
	if v == nil {
		return seq
	}
	if len(v) != len(seq) {
		panic("window: length mismatch")
	}
	for i, w := range v {
		seq[i] *= w
	}
	return seq
}

// TransformTo applies the weights in the receiver to src placing the result
// in dst. If v is nil, TransformTo is a no-op, otherwise the length of v must
// match the length of src and dst.
func (v Values) TransformTo(dst, src []float64) {
	if v == nil {
		return
	}
	if len(v) != len(src) {
		panic("window: seq length mismatch")
	}
	if len(v) != len(dst) {
		panic("window: dst length mismatch")
	}
	for i, w := range v {
		dst[i] = w * src[i]
	}
}

// TransformComplex applies the weights in the receiver to seq in place, returning
// the result. If v is nil, TransformComplex is a no-op, otherwise the length of v
// must match the length of seq.
func (v Values) TransformComplex(seq []complex128) []complex128 {
	if v == nil {
		return seq
	}
	if len(v) != len(seq) {
		panic("window: length mismatch")
	}
	for i, w := range v {
		sv := seq[i]
		seq[i] = complex(w*real(sv), w*imag(sv))
	}
	return seq
}

// TransformComplexTo applies the weights in the receiver to src placing the
// result in dst. If v is nil, TransformComplexTo is a no-op, otherwise the
// length of v must match the length of src and dst.
func (v Values) TransformComplexTo(dst, src []complex128) {
	if v == nil {
		return
	}
	if len(v) != len(src) {
		panic("window: seq length mismatch")
	}
	if len(v) != len(dst) {
		panic("window: dst length mismatch")
	}
	for i, w := range v {
		sv := src[i]
		dst[i] = complex(w*real(sv), w*imag(sv))
	}
}
