// 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 gonum

import (
	"math"

	"gonum.org/v1/gonum/blas"
	"gonum.org/v1/gonum/internal/asm/f64"
)

var _ blas.Float64Level1 = Implementation{}

// Dnrm2 computes the Euclidean norm of a vector,
//
//	sqrt(\sum_i x[i] * x[i]).
//
// This function returns 0 if incX is negative.
func (Implementation) Dnrm2(n int, x []float64, incX int) float64 {
	if incX < 1 {
		if incX == 0 {
			panic(zeroIncX)
		}
		return 0
	}
	if len(x) <= (n-1)*incX {
		panic(shortX)
	}
	if n < 2 {
		if n == 1 {
			return math.Abs(x[0])
		}
		if n == 0 {
			return 0
		}
		panic(nLT0)
	}
	if incX == 1 {
		return f64.L2NormUnitary(x[:n])
	}
	return f64.L2NormInc(x, uintptr(n), uintptr(incX))
}

// Dasum computes the sum of the absolute values of the elements of x.
//
//	\sum_i |x[i]|
//
// Dasum returns 0 if incX is negative.
func (Implementation) Dasum(n int, x []float64, incX int) float64 {
	var sum float64
	if n < 0 {
		panic(nLT0)
	}
	if incX < 1 {
		if incX == 0 {
			panic(zeroIncX)
		}
		return 0
	}
	if len(x) <= (n-1)*incX {
		panic(shortX)
	}
	if incX == 1 {
		x = x[:n]
		for _, v := range x {
			sum += math.Abs(v)
		}
		return sum
	}
	for i := 0; i < n; i++ {
		sum += math.Abs(x[i*incX])
	}
	return sum
}

// Idamax returns the index of an element of x with the largest absolute value.
// If there are multiple such indices the earliest is returned.
// Idamax returns -1 if n == 0.
func (Implementation) Idamax(n int, x []float64, incX int) int {
	if incX < 1 {
		if incX == 0 {
			panic(zeroIncX)
		}
		return -1
	}
	if len(x) <= (n-1)*incX {
		panic(shortX)
	}
	if n < 2 {
		if n == 1 {
			return 0
		}
		if n == 0 {
			return -1 // Netlib returns invalid index when n == 0.
		}
		panic(nLT0)
	}
	idx := 0
	max := math.Abs(x[0])
	if incX == 1 {
		for i, v := range x[:n] {
			absV := math.Abs(v)
			if absV > max {
				max = absV
				idx = i
			}
		}
		return idx
	}
	ix := incX
	for i := 1; i < n; i++ {
		v := x[ix]
		absV := math.Abs(v)
		if absV > max {
			max = absV
			idx = i
		}
		ix += incX
	}
	return idx
}

// Dswap exchanges the elements of two vectors.
//
//	x[i], y[i] = y[i], x[i] for all i
func (Implementation) Dswap(n int, x []float64, incX int, y []float64, incY int) {
	if incX == 0 {
		panic(zeroIncX)
	}
	if incY == 0 {
		panic(zeroIncY)
	}
	if n < 1 {
		if n == 0 {
			return
		}
		panic(nLT0)
	}
	if (incX > 0 && len(x) <= (n-1)*incX) || (incX < 0 && len(x) <= (1-n)*incX) {
		panic(shortX)
	}
	if (incY > 0 && len(y) <= (n-1)*incY) || (incY < 0 && len(y) <= (1-n)*incY) {
		panic(shortY)
	}
	if incX == 1 && incY == 1 {
		x = x[:n]
		for i, v := range x {
			x[i], y[i] = y[i], v
		}
		return
	}
	var ix, iy int
	if incX < 0 {
		ix = (-n + 1) * incX
	}
	if incY < 0 {
		iy = (-n + 1) * incY
	}
	for i := 0; i < n; i++ {
		x[ix], y[iy] = y[iy], x[ix]
		ix += incX
		iy += incY
	}
}

// Dcopy copies the elements of x into the elements of y.
//
//	y[i] = x[i] for all i
func (Implementation) Dcopy(n int, x []float64, incX int, y []float64, incY int) {
	if incX == 0 {
		panic(zeroIncX)
	}
	if incY == 0 {
		panic(zeroIncY)
	}
	if n < 1 {
		if n == 0 {
			return
		}
		panic(nLT0)
	}
	if (incX > 0 && len(x) <= (n-1)*incX) || (incX < 0 && len(x) <= (1-n)*incX) {
		panic(shortX)
	}
	if (incY > 0 && len(y) <= (n-1)*incY) || (incY < 0 && len(y) <= (1-n)*incY) {
		panic(shortY)
	}
	if incX == 1 && incY == 1 {
		copy(y[:n], x[:n])
		return
	}
	var ix, iy int
	if incX < 0 {
		ix = (-n + 1) * incX
	}
	if incY < 0 {
		iy = (-n + 1) * incY
	}
	for i := 0; i < n; i++ {
		y[iy] = x[ix]
		ix += incX
		iy += incY
	}
}

// Daxpy adds alpha times x to y
//
//	y[i] += alpha * x[i] for all i
func (Implementation) Daxpy(n int, alpha float64, x []float64, incX int, y []float64, incY int) {
	if incX == 0 {
		panic(zeroIncX)
	}
	if incY == 0 {
		panic(zeroIncY)
	}
	if n < 1 {
		if n == 0 {
			return
		}
		panic(nLT0)
	}
	if (incX > 0 && len(x) <= (n-1)*incX) || (incX < 0 && len(x) <= (1-n)*incX) {
		panic(shortX)
	}
	if (incY > 0 && len(y) <= (n-1)*incY) || (incY < 0 && len(y) <= (1-n)*incY) {
		panic(shortY)
	}
	if alpha == 0 {
		return
	}
	if incX == 1 && incY == 1 {
		f64.AxpyUnitary(alpha, x[:n], y[:n])
		return
	}
	var ix, iy int
	if incX < 0 {
		ix = (-n + 1) * incX
	}
	if incY < 0 {
		iy = (-n + 1) * incY
	}
	f64.AxpyInc(alpha, x, y, uintptr(n), uintptr(incX), uintptr(incY), uintptr(ix), uintptr(iy))
}

// Drotg computes a plane rotation
//
//	⎡  c s ⎤ ⎡ a ⎤ = ⎡ r ⎤
//	⎣ -s c ⎦ ⎣ b ⎦   ⎣ 0 ⎦
//
// satisfying c^2 + s^2 = 1.
//
// The computation uses the formulas
//
//	sigma = sgn(a)    if |a| >  |b|
//	      = sgn(b)    if |b| >= |a|
//	r = sigma*sqrt(a^2 + b^2)
//	c = 1; s = 0      if r = 0
//	c = a/r; s = b/r  if r != 0
//	c >= 0            if |a| > |b|
//
// The subroutine also computes
//
//	z = s    if |a| > |b|,
//	  = 1/c  if |b| >= |a| and c != 0
//	  = 1    if c = 0
//
// This allows c and s to be reconstructed from z as follows:
//
//	If z = 1, set c = 0, s = 1.
//	If |z| < 1, set c = sqrt(1 - z^2) and s = z.
//	If |z| > 1, set c = 1/z and s = sqrt(1 - c^2).
//
// NOTE: There is a discrepancy between the reference implementation and the
// BLAS technical manual regarding the sign for r when a or b are zero. Drotg
// agrees with the definition in the manual and other common BLAS
// implementations.
func (Implementation) Drotg(a, b float64) (c, s, r, z float64) {
	// Implementation based on Supplemental Material to:
	// Edward Anderson. 2017. Algorithm 978: Safe Scaling in the Level 1 BLAS.
	// ACM Trans. Math. Softw. 44, 1, Article 12 (July 2017), 28 pages.
	// DOI: https://doi.org/10.1145/3061665
	const (
		safmin = 0x1p-1022
		safmax = 1 / safmin
	)
	anorm := math.Abs(a)
	bnorm := math.Abs(b)
	switch {
	case bnorm == 0:
		c = 1
		s = 0
		r = a
		z = 0
	case anorm == 0:
		c = 0
		s = 1
		r = b
		z = 1
	default:
		maxab := math.Max(anorm, bnorm)
		scl := math.Min(math.Max(safmin, maxab), safmax)
		var sigma float64
		if anorm > bnorm {
			sigma = math.Copysign(1, a)
		} else {
			sigma = math.Copysign(1, b)
		}
		ascl := a / scl
		bscl := b / scl
		r = sigma * (scl * math.Sqrt(ascl*ascl+bscl*bscl))
		c = a / r
		s = b / r
		switch {
		case anorm > bnorm:
			z = s
		case c != 0:
			z = 1 / c
		default:
			z = 1
		}
	}
	return c, s, r, z
}

// Drotmg computes the modified Givens rotation. See
// http://www.netlib.org/lapack/explore-html/df/deb/drotmg_8f.html
// for more details.
func (Implementation) Drotmg(d1, d2, x1, y1 float64) (p blas.DrotmParams, rd1, rd2, rx1 float64) {
	// The implementation of Drotmg used here is taken from Hopkins 1997
	// Appendix A: https://doi.org/10.1145/289251.289253
	// with the exception of the gam constants below.

	const (
		gam    = 4096.0
		gamsq  = gam * gam
		rgamsq = 1.0 / gamsq
	)

	if d1 < 0 {
		p.Flag = blas.Rescaling // Error state.
		return p, 0, 0, 0
	}

	if d2 == 0 || y1 == 0 {
		p.Flag = blas.Identity
		return p, d1, d2, x1
	}

	var h11, h12, h21, h22 float64
	if (d1 == 0 || x1 == 0) && d2 > 0 {
		p.Flag = blas.Diagonal
		h12 = 1
		h21 = -1
		x1 = y1
		d1, d2 = d2, d1
	} else {
		p2 := d2 * y1
		p1 := d1 * x1
		q2 := p2 * y1
		q1 := p1 * x1
		if math.Abs(q1) > math.Abs(q2) {
			p.Flag = blas.OffDiagonal
			h11 = 1
			h22 = 1
			h21 = -y1 / x1
			h12 = p2 / p1
			u := 1 - float64(h12*h21)
			if u <= 0 {
				p.Flag = blas.Rescaling // Error state.
				return p, 0, 0, 0
			}

			d1 /= u
			d2 /= u
			x1 *= u
		} else {
			if q2 < 0 {
				p.Flag = blas.Rescaling // Error state.
				return p, 0, 0, 0
			}

			p.Flag = blas.Diagonal
			h21 = -1
			h12 = 1
			h11 = p1 / p2
			h22 = x1 / y1
			u := 1 + float64(h11*h22)
			d1, d2 = d2/u, d1/u
			x1 = y1 * u
		}
	}

	for d1 <= rgamsq && d1 != 0 {
		p.Flag = blas.Rescaling
		d1 = (d1 * gam) * gam
		x1 /= gam
		h11 /= gam
		h12 /= gam
	}
	for d1 > gamsq {
		p.Flag = blas.Rescaling
		d1 = (d1 / gam) / gam
		x1 *= gam
		h11 *= gam
		h12 *= gam
	}

	for math.Abs(d2) <= rgamsq && d2 != 0 {
		p.Flag = blas.Rescaling
		d2 = (d2 * gam) * gam
		h21 /= gam
		h22 /= gam
	}
	for math.Abs(d2) > gamsq {
		p.Flag = blas.Rescaling
		d2 = (d2 / gam) / gam
		h21 *= gam
		h22 *= gam
	}

	switch p.Flag {
	case blas.Diagonal:
		p.H = [4]float64{0: h11, 3: h22}
	case blas.OffDiagonal:
		p.H = [4]float64{1: h21, 2: h12}
	case blas.Rescaling:
		p.H = [4]float64{h11, h21, h12, h22}
	default:
		panic(badFlag)
	}

	return p, d1, d2, x1
}

// Drot applies a plane transformation.
//
//	x[i] = c * x[i] + s * y[i]
//	y[i] = c * y[i] - s * x[i]
func (Implementation) Drot(n int, x []float64, incX int, y []float64, incY int, c float64, s float64) {
	if incX == 0 {
		panic(zeroIncX)
	}
	if incY == 0 {
		panic(zeroIncY)
	}
	if n < 1 {
		if n == 0 {
			return
		}
		panic(nLT0)
	}
	if (incX > 0 && len(x) <= (n-1)*incX) || (incX < 0 && len(x) <= (1-n)*incX) {
		panic(shortX)
	}
	if (incY > 0 && len(y) <= (n-1)*incY) || (incY < 0 && len(y) <= (1-n)*incY) {
		panic(shortY)
	}
	if incX == 1 && incY == 1 {
		x = x[:n]
		for i, vx := range x {
			vy := y[i]
			x[i], y[i] = c*vx+s*vy, c*vy-s*vx
		}
		return
	}
	var ix, iy int
	if incX < 0 {
		ix = (-n + 1) * incX
	}
	if incY < 0 {
		iy = (-n + 1) * incY
	}
	for i := 0; i < n; i++ {
		vx := x[ix]
		vy := y[iy]
		x[ix], y[iy] = c*vx+s*vy, c*vy-s*vx
		ix += incX
		iy += incY
	}
}

// Drotm applies the modified Givens rotation to the 2×n matrix.
func (Implementation) Drotm(n int, x []float64, incX int, y []float64, incY int, p blas.DrotmParams) {
	if incX == 0 {
		panic(zeroIncX)
	}
	if incY == 0 {
		panic(zeroIncY)
	}
	if n <= 0 {
		if n == 0 {
			return
		}
		panic(nLT0)
	}
	if (incX > 0 && len(x) <= (n-1)*incX) || (incX < 0 && len(x) <= (1-n)*incX) {
		panic(shortX)
	}
	if (incY > 0 && len(y) <= (n-1)*incY) || (incY < 0 && len(y) <= (1-n)*incY) {
		panic(shortY)
	}

	if p.Flag == blas.Identity {
		return
	}

	switch p.Flag {
	case blas.Rescaling:
		h11 := p.H[0]
		h12 := p.H[2]
		h21 := p.H[1]
		h22 := p.H[3]
		if incX == 1 && incY == 1 {
			x = x[:n]
			for i, vx := range x {
				vy := y[i]
				x[i], y[i] = float64(vx*h11)+float64(vy*h12), float64(vx*h21)+float64(vy*h22)
			}
			return
		}
		var ix, iy int
		if incX < 0 {
			ix = (-n + 1) * incX
		}
		if incY < 0 {
			iy = (-n + 1) * incY
		}
		for i := 0; i < n; i++ {
			vx := x[ix]
			vy := y[iy]
			x[ix], y[iy] = float64(vx*h11)+float64(vy*h12), float64(vx*h21)+float64(vy*h22)
			ix += incX
			iy += incY
		}
	case blas.OffDiagonal:
		h12 := p.H[2]
		h21 := p.H[1]
		if incX == 1 && incY == 1 {
			x = x[:n]
			for i, vx := range x {
				vy := y[i]
				x[i], y[i] = vx+float64(vy*h12), float64(vx*h21)+vy
			}
			return
		}
		var ix, iy int
		if incX < 0 {
			ix = (-n + 1) * incX
		}
		if incY < 0 {
			iy = (-n + 1) * incY
		}
		for i := 0; i < n; i++ {
			vx := x[ix]
			vy := y[iy]
			x[ix], y[iy] = vx+float64(vy*h12), float64(vx*h21)+vy
			ix += incX
			iy += incY
		}
	case blas.Diagonal:
		h11 := p.H[0]
		h22 := p.H[3]
		if incX == 1 && incY == 1 {
			x = x[:n]
			for i, vx := range x {
				vy := y[i]
				x[i], y[i] = float64(vx*h11)+vy, -vx+float64(vy*h22)
			}
			return
		}
		var ix, iy int
		if incX < 0 {
			ix = (-n + 1) * incX
		}
		if incY < 0 {
			iy = (-n + 1) * incY
		}
		for i := 0; i < n; i++ {
			vx := x[ix]
			vy := y[iy]
			x[ix], y[iy] = float64(vx*h11)+vy, -vx+float64(vy*h22)
			ix += incX
			iy += incY
		}
	}
}

// Dscal scales x by alpha.
//
//	x[i] *= alpha
//
// Dscal has no effect if incX < 0.
func (Implementation) Dscal(n int, alpha float64, x []float64, incX int) {
	if incX < 1 {
		if incX == 0 {
			panic(zeroIncX)
		}
		return
	}
	if n < 1 {
		if n == 0 {
			return
		}
		panic(nLT0)
	}
	if (n-1)*incX >= len(x) {
		panic(shortX)
	}
	if alpha == 0 {
		if incX == 1 {
			x = x[:n]
			for i := range x {
				x[i] = 0
			}
			return
		}
		for ix := 0; ix < n*incX; ix += incX {
			x[ix] = 0
		}
		return
	}
	if incX == 1 {
		f64.ScalUnitary(alpha, x[:n])
		return
	}
	f64.ScalInc(alpha, x, uintptr(n), uintptr(incX))
}
