// Copyright ©2015 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 (
	"testing"

	"golang.org/x/exp/rand"

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

type Dlarfer interface {
	Dlarf(side blas.Side, m, n int, v []float64, incv int, tau float64, c []float64, ldc int, work []float64)
}

func DlarfTest(t *testing.T, impl Dlarfer) {
	rnd := rand.New(rand.NewSource(1))
	for i, test := range []struct {
		m, n, ldc    int
		incv, lastv  int
		lastr, lastc int
		tau          float64
	}{
		{
			m:   3,
			n:   2,
			ldc: 2,

			incv:  4,
			lastv: 1,

			lastr: 2,
			lastc: 1,

			tau: 2,
		},
		{
			m:   2,
			n:   3,
			ldc: 3,

			incv:  4,
			lastv: 1,

			lastr: 1,
			lastc: 2,

			tau: 2,
		},
		{
			m:   2,
			n:   3,
			ldc: 3,

			incv:  4,
			lastv: 1,

			lastr: 0,
			lastc: 1,

			tau: 2,
		},
		{
			m:   2,
			n:   3,
			ldc: 3,

			incv:  4,
			lastv: 0,

			lastr: 0,
			lastc: 1,

			tau: 2,
		},
		{
			m:   10,
			n:   10,
			ldc: 10,

			incv:  4,
			lastv: 6,

			lastr: 9,
			lastc: 8,

			tau: 2,
		},
	} {
		// Construct a random matrix.
		c := make([]float64, test.ldc*test.m)
		for i := 0; i <= test.lastr; i++ {
			for j := 0; j <= test.lastc; j++ {
				c[i*test.ldc+j] = rnd.Float64()
			}
		}
		cCopy := make([]float64, len(c))
		copy(cCopy, c)
		cCopy2 := make([]float64, len(c))
		copy(cCopy2, c)

		// Test with side right.
		sz := max(test.m, test.n) // so v works for both right and left side.
		v := make([]float64, test.incv*sz+1)
		// Fill with nonzero entries up until lastv.
		for i := 0; i <= test.lastv; i++ {
			v[i*test.incv] = rnd.Float64()
		}
		// Construct h explicitly to compare.
		h := make([]float64, test.n*test.n)
		for i := 0; i < test.n; i++ {
			h[i*test.n+i] = 1
		}
		hMat := blas64.General{
			Rows:   test.n,
			Cols:   test.n,
			Stride: test.n,
			Data:   h,
		}
		vVec := blas64.Vector{
			Inc:  test.incv,
			Data: v,
		}
		blas64.Ger(-test.tau, vVec, vVec, hMat)

		// Apply multiplication (2nd copy is to avoid aliasing).
		cMat := blas64.General{
			Rows:   test.m,
			Cols:   test.n,
			Stride: test.ldc,
			Data:   cCopy,
		}
		cMat2 := blas64.General{
			Rows:   test.m,
			Cols:   test.n,
			Stride: test.ldc,
			Data:   cCopy2,
		}
		blas64.Gemm(blas.NoTrans, blas.NoTrans, 1, cMat2, hMat, 0, cMat)

		// cMat now stores the true answer. Compare with the function call.
		work := make([]float64, sz)
		impl.Dlarf(blas.Right, test.m, test.n, v, test.incv, test.tau, c, test.ldc, work)
		if !floats.EqualApprox(c, cMat.Data, 1e-14) {
			t.Errorf("Dlarf mismatch right, case %v. Want %v, got %v", i, cMat.Data, c)
		}

		// Test on the left side.
		copy(c, cCopy2)
		copy(cCopy, c)
		// Construct h.
		h = make([]float64, test.m*test.m)
		for i := 0; i < test.m; i++ {
			h[i*test.m+i] = 1
		}
		hMat = blas64.General{
			Rows:   test.m,
			Cols:   test.m,
			Stride: test.m,
			Data:   h,
		}
		blas64.Ger(-test.tau, vVec, vVec, hMat)
		blas64.Gemm(blas.NoTrans, blas.NoTrans, 1, hMat, cMat2, 0, cMat)
		impl.Dlarf(blas.Left, test.m, test.n, v, test.incv, test.tau, c, test.ldc, work)
		if !floats.EqualApprox(c, cMat.Data, 1e-14) {
			t.Errorf("Dlarf mismatch left, case %v. Want %v, got %v", i, cMat.Data, c)
		}
	}
}
