// Copyright ©2016 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 testlapack

import (
	"math"
	"math/rand/v2"

	"gonum.org/v1/gonum/blas/blas64"
)

// A123 is the non-symmetric singular matrix
//
//	    [ 1 2 3 ]
//	A = [ 4 5 6 ]
//	    [ 7 8 9 ]
//
// It has three distinct real eigenvalues.
type A123 struct{}

func (A123) Matrix() blas64.General {
	return blas64.General{
		Rows:   3,
		Cols:   3,
		Stride: 3,
		Data: []float64{
			1, 2, 3,
			4, 5, 6,
			7, 8, 9,
		},
	}
}

func (A123) Eigenvalues() []complex128 {
	return []complex128{16.116843969807043, -1.116843969807043, 0}
}

func (A123) LeftEV() blas64.General {
	return blas64.General{
		Rows:   3,
		Cols:   3,
		Stride: 3,
		Data: []float64{
			-0.464547273387671, -0.570795531228578, -0.677043789069485,
			-0.882905959653586, -0.239520420054206, 0.403865119545174,
			0.408248290463862, -0.816496580927726, 0.408248290463863,
		},
	}
}

func (A123) RightEV() blas64.General {
	return blas64.General{
		Rows:   3,
		Cols:   3,
		Stride: 3,
		Data: []float64{
			-0.231970687246286, -0.785830238742067, 0.408248290463864,
			-0.525322093301234, -0.086751339256628, -0.816496580927726,
			-0.818673499356181, 0.612327560228810, 0.408248290463863,
		},
	}
}

// AntisymRandom is a anti-symmetric random matrix. All its eigenvalues are
// imaginary with one zero if the order is odd.
type AntisymRandom struct {
	mat blas64.General
}

func NewAntisymRandom(n int, rnd *rand.Rand) AntisymRandom {
	a := zeros(n, n, n)
	for i := 0; i < n; i++ {
		for j := i + 1; j < n; j++ {
			r := rnd.NormFloat64()
			a.Data[i*a.Stride+j] = r
			a.Data[j*a.Stride+i] = -r
		}
	}
	return AntisymRandom{a}
}

func (a AntisymRandom) Matrix() blas64.General {
	return cloneGeneral(a.mat)
}

func (AntisymRandom) Eigenvalues() []complex128 {
	return nil
}

// Circulant is a generally non-symmetric matrix given by
//
//	A[i,j] = 1 + (j-i+n)%n.
//
// For example, for n=5,
//
//	    [ 1 2 3 4 5 ]
//	    [ 5 1 2 3 4 ]
//	A = [ 4 5 1 2 3 ]
//	    [ 3 4 5 1 2 ]
//	    [ 2 3 4 5 1 ]
//
// It has real and complex eigenvalues, some possibly repeated.
type Circulant int

func (c Circulant) Matrix() blas64.General {
	n := int(c)
	a := zeros(n, n, n)
	for i := 0; i < n; i++ {
		for j := 0; j < n; j++ {
			a.Data[i*a.Stride+j] = float64(1 + (j-i+n)%n)
		}
	}
	return a
}

func (c Circulant) Eigenvalues() []complex128 {
	n := int(c)
	w := rootsOfUnity(n)
	ev := make([]complex128, n)
	for k := 0; k < n; k++ {
		ev[k] = complex(float64(n), 0)
	}
	for i := n - 1; i > 0; i-- {
		for k := 0; k < n; k++ {
			ev[k] = ev[k]*w[k] + complex(float64(i), 0)
		}
	}
	return ev
}

// Clement is a generally non-symmetric matrix given by
//
//	A[i,j] = i+1  if j == i+1,
//	       = n-i  if j == i-1,
//	       = 0    otherwise.
//
// For example, for n=5,
//
//	    [ . 1 . . . ]
//	    [ 4 . 2 . . ]
//	A = [ . 3 . 3 . ]
//	    [ . . 2 . 4 ]
//	    [ . . . 1 . ]
//
// It has n distinct real eigenvalues.
type Clement int

func (c Clement) Matrix() blas64.General {
	n := int(c)
	a := zeros(n, n, n)
	for i := 0; i < n; i++ {
		if i < n-1 {
			a.Data[i*a.Stride+i+1] = float64(i + 1)
		}
		if i > 0 {
			a.Data[i*a.Stride+i-1] = float64(n - i)
		}
	}
	return a
}

func (c Clement) Eigenvalues() []complex128 {
	n := int(c)
	ev := make([]complex128, n)
	for i := range ev {
		ev[i] = complex(float64(-n+2*i+1), 0)
	}
	return ev
}

// Creation is a singular non-symmetric matrix given by
//
//	A[i,j] = i  if j == i-1,
//	       = 0  otherwise.
//
// For example, for n=5,
//
//	    [ . . . . . ]
//	    [ 1 . . . . ]
//	A = [ . 2 . . . ]
//	    [ . . 3 . . ]
//	    [ . . . 4 . ]
//
// Zero is its only eigenvalue.
type Creation int

func (c Creation) Matrix() blas64.General {
	n := int(c)
	a := zeros(n, n, n)
	for i := 1; i < n; i++ {
		a.Data[i*a.Stride+i-1] = float64(i)
	}
	return a
}

func (c Creation) Eigenvalues() []complex128 {
	return make([]complex128, int(c))
}

// Diagonal is a diagonal matrix given by
//
//	A[i,j] = i+1  if i == j,
//	       = 0    otherwise.
//
// For example, for n=5,
//
//	    [ 1 . . . . ]
//	    [ . 2 . . . ]
//	A = [ . . 3 . . ]
//	    [ . . . 4 . ]
//	    [ . . . . 5 ]
//
// It has n real eigenvalues {1,...,n}.
type Diagonal int

func (d Diagonal) Matrix() blas64.General {
	n := int(d)
	a := zeros(n, n, n)
	for i := 0; i < n; i++ {
		a.Data[i*a.Stride+i] = float64(i)
	}
	return a
}

func (d Diagonal) Eigenvalues() []complex128 {
	n := int(d)
	ev := make([]complex128, n)
	for i := range ev {
		ev[i] = complex(float64(i), 0)
	}
	return ev
}

// Downshift is a non-singular upper Hessenberg matrix given by
//
//	A[i,j] = 1  if (i-j+n)%n == 1,
//	       = 0  otherwise.
//
// For example, for n=5,
//
//	    [ . . . . 1 ]
//	    [ 1 . . . . ]
//	A = [ . 1 . . . ]
//	    [ . . 1 . . ]
//	    [ . . . 1 . ]
//
// Its eigenvalues are the complex roots of unity.
type Downshift int

func (d Downshift) Matrix() blas64.General {
	n := int(d)
	a := zeros(n, n, n)
	a.Data[n-1] = 1
	for i := 1; i < n; i++ {
		a.Data[i*a.Stride+i-1] = 1
	}
	return a
}

func (d Downshift) Eigenvalues() []complex128 {
	return rootsOfUnity(int(d))
}

// Fibonacci is an upper Hessenberg matrix with 3 distinct real eigenvalues. For
// example, for n=5,
//
//	    [ . 1 . . . ]
//	    [ 1 1 . . . ]
//	A = [ . 1 1 . . ]
//	    [ . . 1 1 . ]
//	    [ . . . 1 1 ]
type Fibonacci int

func (f Fibonacci) Matrix() blas64.General {
	n := int(f)
	a := zeros(n, n, n)
	if n > 1 {
		a.Data[1] = 1
	}
	for i := 1; i < n; i++ {
		a.Data[i*a.Stride+i-1] = 1
		a.Data[i*a.Stride+i] = 1
	}
	return a
}

func (f Fibonacci) Eigenvalues() []complex128 {
	n := int(f)
	ev := make([]complex128, n)
	if n == 0 || n == 1 {
		return ev
	}
	phi := 0.5 * (1 + math.Sqrt(5))
	ev[0] = complex(phi, 0)
	for i := 1; i < n-1; i++ {
		ev[i] = 1 + 0i
	}
	ev[n-1] = complex(1-phi, 0)
	return ev
}

// Gear is a singular non-symmetric matrix with real eigenvalues. For example,
// for n=5,
//
//	    [ . 1 . . 1 ]
//	    [ 1 . 1 . . ]
//	A = [ . 1 . 1 . ]
//	    [ . . 1 . 1 ]
//	    [-1 . . 1 . ]
type Gear int

func (g Gear) Matrix() blas64.General {
	n := int(g)
	a := zeros(n, n, n)
	if n == 1 {
		return a
	}
	for i := 0; i < n-1; i++ {
		a.Data[i*a.Stride+i+1] = 1
	}
	for i := 1; i < n; i++ {
		a.Data[i*a.Stride+i-1] = 1
	}
	a.Data[n-1] = 1
	a.Data[(n-1)*a.Stride] = -1
	return a
}

func (g Gear) Eigenvalues() []complex128 {
	n := int(g)
	ev := make([]complex128, n)
	if n == 0 || n == 1 {
		return ev
	}
	if n == 2 {
		ev[0] = complex(0, 1)
		ev[1] = complex(0, -1)
		return ev
	}
	w := 0
	ev[w] = math.Pi / 2
	w++
	phi := (n - 1) / 2
	for p := 1; p <= phi; p++ {
		ev[w] = complex(float64(2*p)*math.Pi/float64(n), 0)
		w++
	}
	phi = n / 2
	for p := 1; p <= phi; p++ {
		ev[w] = complex(float64(2*p-1)*math.Pi/float64(n), 0)
		w++
	}
	for i, v := range ev {
		ev[i] = complex(2*math.Cos(real(v)), 0)
	}
	return ev
}

// Grcar is an upper Hessenberg matrix given by
//
//	A[i,j] = -1  if i == j+1,
//	       = 1   if i <= j and j <= i+k,
//	       = 0   otherwise.
//
// For example, for n=5 and k=2,
//
//	    [  1  1  1  .  . ]
//	    [ -1  1  1  1  . ]
//	A = [  . -1  1  1  1 ]
//	    [  .  . -1  1  1 ]
//	    [  .  .  . -1  1 ]
//
// The matrix has sensitive eigenvalues but they are not given explicitly.
type Grcar struct {
	N int
	K int
}

func (g Grcar) Matrix() blas64.General {
	n := g.N
	a := zeros(n, n, n)
	for k := 0; k <= g.K; k++ {
		for i := 0; i < n-k; i++ {
			a.Data[i*a.Stride+i+k] = 1
		}
	}
	for i := 1; i < n; i++ {
		a.Data[i*a.Stride+i-1] = -1
	}
	return a
}

func (Grcar) Eigenvalues() []complex128 {
	return nil
}

// Hanowa is a non-symmetric non-singular matrix of even order given by
//
//	A[i,j] = alpha    if i == j,
//	       = -i-1     if i < n/2 and j == i + n/2,
//	       = i+1-n/2  if i >= n/2 and j == i - n/2,
//	       = 0        otherwise.
//
// The matrix has complex eigenvalues.
type Hanowa struct {
	N     int // Order of the matrix, must be even.
	Alpha float64
}

func (h Hanowa) Matrix() blas64.General {
	if h.N&0x1 != 0 {
		panic("lapack: matrix order must be even")
	}
	n := h.N
	a := zeros(n, n, n)
	for i := 0; i < n; i++ {
		a.Data[i*a.Stride+i] = h.Alpha
	}
	for i := 0; i < n/2; i++ {
		a.Data[i*a.Stride+i+n/2] = float64(-i - 1)
	}
	for i := n / 2; i < n; i++ {
		a.Data[i*a.Stride+i-n/2] = float64(i + 1 - n/2)
	}
	return a
}

func (h Hanowa) Eigenvalues() []complex128 {
	if h.N&0x1 != 0 {
		panic("lapack: matrix order must be even")
	}
	n := h.N
	ev := make([]complex128, n)
	for i := 0; i < n/2; i++ {
		ev[2*i] = complex(h.Alpha, float64(-i-1))
		ev[2*i+1] = complex(h.Alpha, float64(i+1))
	}
	return ev
}

// Lesp is a tridiagonal, generally non-symmetric matrix given by
//
//	A[i,j] = -2*i-5   if i == j,
//	       = 1/(i+1)  if i == j-1,
//	       = j+1      if i == j+1.
//
// For example, for n=5,
//
//	    [  -5    2    .    .    . ]
//	    [ 1/2   -7    3    .    . ]
//	A = [   .  1/3   -9    4    . ]
//	    [   .    .  1/4  -11    5 ]
//	    [   .    .    .  1/5  -13 ].
//
// The matrix has sensitive eigenvalues but they are not given explicitly.
type Lesp int

func (l Lesp) Matrix() blas64.General {
	n := int(l)
	a := zeros(n, n, n)
	for i := 0; i < n; i++ {
		a.Data[i*a.Stride+i] = float64(-2*i - 5)
	}
	for i := 0; i < n-1; i++ {
		a.Data[i*a.Stride+i+1] = float64(i + 2)
	}
	for i := 1; i < n; i++ {
		a.Data[i*a.Stride+i-1] = 1 / float64(i+1)
	}
	return a
}

func (Lesp) Eigenvalues() []complex128 {
	return nil
}

// Rutis is the 4×4 non-symmetric matrix
//
//	    [ 4 -5  0  3 ]
//	A = [ 0  4 -3 -5 ]
//	    [ 5 -3  4  0 ]
//	    [ 3  0  5  4 ]
//
// It has two distinct real eigenvalues and a pair of complex eigenvalues.
type Rutis struct{}

func (Rutis) Matrix() blas64.General {
	return blas64.General{
		Rows:   4,
		Cols:   4,
		Stride: 4,
		Data: []float64{
			4, -5, 0, 3,
			0, 4, -3, -5,
			5, -3, 4, 0,
			3, 0, 5, 4,
		},
	}
}

func (Rutis) Eigenvalues() []complex128 {
	return []complex128{12, 1 + 5i, 1 - 5i, 2}
}

// Tris is a tridiagonal matrix given by
//
//	A[i,j] = x  if i == j-1,
//	       = y  if i == j,
//	       = z  if i == j+1.
//
// If x*z is negative, the matrix has complex eigenvalues.
type Tris struct {
	N       int
	X, Y, Z float64
}

func (t Tris) Matrix() blas64.General {
	n := t.N
	a := zeros(n, n, n)
	for i := 1; i < n; i++ {
		a.Data[i*a.Stride+i-1] = t.X
	}
	for i := 0; i < n; i++ {
		a.Data[i*a.Stride+i] = t.Y
	}
	for i := 0; i < n-1; i++ {
		a.Data[i*a.Stride+i+1] = t.Z
	}
	return a
}

func (t Tris) Eigenvalues() []complex128 {
	n := t.N
	ev := make([]complex128, n)
	for i := range ev {
		angle := float64(i+1) * math.Pi / float64(n+1)
		arg := t.X * t.Z
		if arg >= 0 {
			ev[i] = complex(t.Y+2*math.Sqrt(arg)*math.Cos(angle), 0)
		} else {
			ev[i] = complex(t.Y, 2*math.Sqrt(-arg)*math.Cos(angle))
		}
	}
	return ev
}

// Wilk4 is a 4×4 lower triangular matrix with 4 distinct real eigenvalues.
type Wilk4 struct{}

func (Wilk4) Matrix() blas64.General {
	return blas64.General{
		Rows:   4,
		Cols:   4,
		Stride: 4,
		Data: []float64{
			0.9143e-4, 0.0, 0.0, 0.0,
			0.8762, 0.7156e-4, 0.0, 0.0,
			0.7943, 0.8143, 0.9504e-4, 0.0,
			0.8017, 0.6123, 0.7165, 0.7123e-4,
		},
	}
}

func (Wilk4) Eigenvalues() []complex128 {
	return []complex128{
		0.9504e-4, 0.9143e-4, 0.7156e-4, 0.7123e-4,
	}
}

// Wilk12 is a 12×12 lower Hessenberg matrix with 12 distinct real eigenvalues.
type Wilk12 struct{}

func (Wilk12) Matrix() blas64.General {
	return blas64.General{
		Rows:   12,
		Cols:   12,
		Stride: 12,
		Data: []float64{
			12, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
			11, 11, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0,
			10, 10, 10, 9, 0, 0, 0, 0, 0, 0, 0, 0,
			9, 9, 9, 9, 8, 0, 0, 0, 0, 0, 0, 0,
			8, 8, 8, 8, 8, 7, 0, 0, 0, 0, 0, 0,
			7, 7, 7, 7, 7, 7, 6, 0, 0, 0, 0, 0,
			6, 6, 6, 6, 6, 6, 6, 5, 0, 0, 0, 0,
			5, 5, 5, 5, 5, 5, 5, 5, 4, 0, 0, 0,
			4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 0, 0,
			3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 0,
			2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1,
			1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
		},
	}
}

func (Wilk12) Eigenvalues() []complex128 {
	return []complex128{
		32.2288915015722210,
		20.1989886458770691,
		12.3110774008685340,
		6.9615330855671154,
		3.5118559485807528,
		1.5539887091319704,
		0.6435053190136506,
		0.2847497205488856,
		0.1436465181918488,
		0.0812276683076552,
		0.0495074140194613,
		0.0310280683208907,
	}
}

// Wilk20 is a 20×20 lower Hessenberg matrix. If the parameter is 0, the matrix
// has 20 distinct real eigenvalues. If the parameter is 1e-10, the matrix has 6
// real eigenvalues and 7 pairs of complex eigenvalues.
type Wilk20 float64

func (w Wilk20) Matrix() blas64.General {
	a := zeros(20, 20, 20)
	for i := 0; i < 20; i++ {
		a.Data[i*a.Stride+i] = float64(i + 1)
	}
	for i := 0; i < 19; i++ {
		a.Data[i*a.Stride+i+1] = 20
	}
	a.Data[19*a.Stride] = float64(w)
	return a
}

func (w Wilk20) Eigenvalues() []complex128 {
	if float64(w) == 0 {
		ev := make([]complex128, 20)
		for i := range ev {
			ev[i] = complex(float64(i+1), 0)
		}
		return ev
	}
	return nil
}

// Zero is a matrix with all elements equal to zero.
type Zero int

func (z Zero) Matrix() blas64.General {
	n := int(z)
	return zeros(n, n, n)
}

func (z Zero) Eigenvalues() []complex128 {
	n := int(z)
	return make([]complex128, n)
}
