package producer

import (
	"fmt"
	"math"
	"sort"

	log "github.com/sirupsen/logrus"
	"gonum.org/v1/gonum/mat"
)

type VX struct {
	v float64
	x mat.VecDense
}

type SolveMethod int

const (
	SolveMethodOptimize SolveMethod = iota
	SolveMethodLeastSquare
	SolveMethodNelderMead
)

const epsilon = 1e-4

func normalize(v *mat.VecDense) (*mat.VecDense, float64, float64) {
	var vOff, vScale float64
	vOff = mat.Sum(v) / float64(v.Len())
	vScale = math.Max(math.Abs(mat.Max(v)-vOff), math.Abs(mat.Min(v)-vOff))
	for i := 0; i < v.Len(); i++ {
		v.SetVec(i, (v.AtVec(i)-vOff)/vScale)
	}

	return v, vOff, vScale
}

func normalize2(v *mat.VecDense, vOff, vScale float64) *mat.VecDense {
	for i := 0; i < v.Len(); i++ {
		v.SetVec(i, (v.AtVec(i)-vOff)/vScale)
	}

	return v
}

func solveCoefficients(f, latVec, lonVec, heightVec *mat.VecDense, method SolveMethod) ([]float64, error) {
	M := setupSystemOfEquations(f, latVec, lonVec, heightVec)
	n := f.Len()

	// 计算初始值 x = (M^T * M)^-1 * (M^T * f)
	var x0 mat.VecDense
	var MtM mat.Dense
	MtM.Mul(M.T(), M)
	invMtM, err := invertRPCMatrix(&MtM)
	if err != nil {
		return nil, err
	}
	var MtF mat.VecDense
	MtF.MulVec(M.T(), f)
	x0.MulVec(invMtM, &MtF)
	// return mat.Col(nil, 0, &x0), nil

	if method == SolveMethodNelderMead {
		numerator := mat.NewVecDense(20, nil)
		denominator := mat.NewVecDense(20, nil)
		denominator.SetVec(0, 1.0)
		numerator.SetVec(0, x0.AtVec(0))
		for i := 1; i < 20; i++ {
			numerator.SetVec(i, x0.AtVec(i))
			denominator.SetVec(i, x0.AtVec(i+19))
		}

		num, den, err := solveNelderMead(numerator, denominator, f, latVec, lonVec, heightVec)
		if err != nil {
			return nil, err
		}

		var coeffs []float64
		coeffs = append(coeffs, num.RawVector().Data...)
		for i := 1; i < 20; i++ {
			coeffs = append(coeffs, den.AtVec(i))
		}
		return coeffs, nil
	}

	// 迭代

	var x1 mat.VecDense

	var vx []*VX
	iterations := 0
	maxIterations := 10
	denominator := mat.NewVecDense(20, nil)
	w2 := mat.NewDiagDense(n, nil)
	var MtW2, MtW2M mat.Dense
	var MtW2F mat.VecDense
	for iterations < maxIterations {
		denominator.SetVec(0, 1.0)
		for idx := 1; idx < 20; idx++ {
			denominator.SetVec(idx, x0.AtVec(idx+19))
		}
		weights := setupWeightMatrix(denominator, latVec, lonVec, heightVec)
		for i := 0; i < n; i++ {
			w2.SetDiag(i, weights.At(i, i)*weights.At(i, i))
		}
		MtW2.Mul(M.T(), w2)
		MtW2M.Mul(&MtW2, M)
		invMtW2M, err := invertRPCMatrix(&MtW2M)
		if err != nil {
			return nil, err
		}
		MtW2F.MulVec(&MtW2, f)
		x1.MulVec(invMtW2M, &MtW2F)

		numerator1 := mat.NewVecDense(20, nil)
		denominator1 := mat.NewVecDense(20, nil)
		denominator1.SetVec(0, 1.0)
		numerator1.SetVec(0, x0.AtVec(0))
		for i := 1; i < 20; i++ {
			numerator1.SetVec(i, x1.AtVec(i))
			denominator1.SetVec(i, x1.AtVec(i+19))
		}

		errorSquared := 0.0
		lambda := 1e-4
		for i := 0; i < n; i++ {
			predictedV := project(numerator1, denominator1, latVec.AtVec(i), lonVec.AtVec(i), heightVec.AtVec(i))
			errorV := predictedV - f.AtVec(i)
			errorSquared += errorV * errorV
		}
		var coeffsSquared float64
		for i := 0; i < 20; i++ {
			coeffsSquared += lambda * (numerator1.AtVec(i)*numerator1.AtVec(i) + denominator1.AtVec(i)*denominator1.AtVec(i))
		}

		x0 = x1
		iterations++

		vx = append(vx, &VX{v: errorSquared, x: x1})
		if errorSquared < 0.001 {
			break
		}
	}

	log.Println("iterations:", iterations)
	if iterations == maxIterations {
		sort.Slice(vx, func(i, j int) bool {
			return vx[i].v < vx[j].v
		})
		x0 = vx[0].x
		log.Printf("select min v-err: %.8f", vx[0].v)
	}

	return mat.Col(nil, 0, &x0), nil
}

func setupSystemOfEquations(Rn, latVec, lonVec, heightVec *mat.VecDense) *mat.Dense {
	n := latVec.Len()
	// 设计矩阵 B = [ 20个分子系数 19个分母系数 ]
	B := mat.NewDense(n, 39, nil)
	for i := 0; i < n; i++ {
		P := latVec.AtVec(i)
		L := lonVec.AtVec(i)
		H := heightVec.AtVec(i)
		r := Rn.AtVec(i)

		B.Set(i, 0, 1)
		B.Set(i, 1, L)
		B.Set(i, 2, P)
		B.Set(i, 3, H)
		B.Set(i, 4, L*P)
		B.Set(i, 5, L*H)
		B.Set(i, 6, P*H)
		B.Set(i, 7, L*L)
		B.Set(i, 8, P*P)
		B.Set(i, 9, H*H)
		B.Set(i, 10, L*P*H)
		B.Set(i, 11, L*L*L)
		B.Set(i, 12, L*P*P)
		B.Set(i, 13, L*H*H)
		B.Set(i, 14, L*L*P)
		B.Set(i, 15, P*P*P)
		B.Set(i, 16, P*H*H)
		B.Set(i, 17, L*L*H)
		B.Set(i, 18, P*P*H)
		B.Set(i, 19, H*H*H)
		B.Set(i, 20, -L*r)
		B.Set(i, 21, -P*r)
		B.Set(i, 22, -H*r)
		B.Set(i, 23, -L*P*r)
		B.Set(i, 24, -L*H*r)
		B.Set(i, 25, -P*H*r)
		B.Set(i, 26, -L*L*r)
		B.Set(i, 27, -P*P*r)
		B.Set(i, 28, -H*H*r)
		B.Set(i, 29, -L*P*H*r)
		B.Set(i, 30, -L*L*L*r)
		B.Set(i, 31, -L*P*P*r)
		B.Set(i, 32, -L*H*H*r)
		B.Set(i, 33, -L*L*P*r)
		B.Set(i, 34, -P*P*P*r)
		B.Set(i, 35, -P*H*H*r)
		B.Set(i, 36, -L*L*H*r)
		B.Set(i, 37, -P*P*H*r)
		B.Set(i, 38, -H*H*H*r)
	}

	return B
}

// 构建权矩阵 [ 1/B ]
func setupWeightMatrix(coeffs, latVec, lonVec, heightVec *mat.VecDense) *mat.DiagDense {
	n := latVec.Len()
	row := mat.NewDense(n, 20, nil)
	result := mat.NewDiagDense(n, nil)
	for i := 0; i < n; i++ {
		P := latVec.AtVec(i)
		L := lonVec.AtVec(i)
		H := heightVec.AtVec(i)

		row.Set(i, 0, 1)
		row.Set(i, 1, L)
		row.Set(i, 2, P)
		row.Set(i, 3, H)
		row.Set(i, 4, L*P)
		row.Set(i, 5, L*H)
		row.Set(i, 6, P*H)
		row.Set(i, 7, L*L)
		row.Set(i, 8, P*P)
		row.Set(i, 9, H*H)
		row.Set(i, 10, L*P*H)
		row.Set(i, 11, L*L*L)
		row.Set(i, 12, L*P*P)
		row.Set(i, 13, L*H*H)
		row.Set(i, 14, L*L*P)
		row.Set(i, 15, P*P*P)
		row.Set(i, 16, P*H*H)
		row.Set(i, 17, L*L*H)
		row.Set(i, 18, P*P*H)
		row.Set(i, 19, H*H*H)

		var B float64
		for idx2 := 0; idx2 < 20; idx2++ {
			B += coeffs.AtVec(idx2) * row.At(i, idx2)
		}

		result.SetDiag(i, 1/B)
	}

	return result
}

func invertRPCMatrix(At *mat.Dense) (*mat.Dense, error) {
	var AtInv mat.Dense
	err := AtInv.Inverse(At)

	if err != nil {
		// 岭估计方法调整法方程状态，使得矩阵非奇异，最小二乘平差可以收敛
		r, c := At.Dims()
		log.Debugf("cannot inverse matrix(%d*%d): %v", r, c, err)
		k := 0.0000001 // [0.00000005, 0.000005]
		log.Debugf("try to adjust matrix with +kI, k=%.8f", k)
		I := mat.NewDiagDense(r, nil)
		for i := 0; i < r; i++ {
			I.SetDiag(i, k)
		}
		At.Add(At, I)

		err = AtInv.Inverse(At)
	}

	if err != nil {
		log.Debugf("cannot inverse matrix: %v, try SVD method", err)
		// 计算矩阵的 SVD 分解
		var svd mat.SVD
		ok := svd.Factorize(At, mat.SVDThin)
		if !ok {
			fmt.Println("SVD 分解失败")
			return nil, fmt.Errorf("设计矩阵不可逆, SVD 分解失败: %v", err)
		}

		// 获取 U、Σ 和 V^T
		var u, v mat.Dense
		svd.UTo(&u)
		svd.VTo(&v)
		sigma := svd.Values(nil)

		// 计算 Σ^+ (Sigma pseudo-inverse)
		m, n := u.Dims()
		sigmaInv := mat.NewDense(n, m, nil)
		for i := 0; i < len(sigma); i++ {
			if sigma[i] > 1e-10 { // 避免除以零
				sigmaInv.Set(i, i, 1/sigma[i])
			}
		}

		// 计算 V * Σ^+ * U^T
		var temp mat.Dense
		temp.Mul(&v, sigmaInv)
		AtInv.Mul(&temp, u.T())
	}

	return &AtInv, nil
}

// SolveNormalEquation 使用正规方程法求解最小二乘问题
func SolveNormalEquation(A *mat.Dense, b *mat.VecDense) ([]float64, error) {
	var At mat.Dense
	At.Mul(A.T(), A) // At = A^T * A

	// 求解 (A^T * A)^-1 * (A^T * b)
	var AtInv mat.Dense
	err := AtInv.Inverse(&At)

	if err != nil {
		// 岭估计方法调整法方程状态，使得矩阵非奇异，最小二乘平差可以收敛
		r, c := At.Dims()
		log.Infof("cannot inverse design matrix(%d*%d): %v", r, c, err)
		log.Info("try to adjust design matrix with +kI, k=0.0000001")
		k := 0.0000001 // [0.00000005, 0.000005]
		I := mat.NewDiagDense(r, nil)
		for i := 0; i < r; i++ {
			I.SetDiag(i, k)
		}
		At.Add(&At, I)

		err = AtInv.Inverse(&At)
	}

	if err != nil {
		log.Infof("cannot inverse design matrix: %v, try SVD method", err)
		// 计算矩阵的 SVD 分解
		var svd mat.SVD
		ok := svd.Factorize(&At, mat.SVDThin)
		if !ok {
			fmt.Println("SVD 分解失败")
			return nil, fmt.Errorf("设计矩阵不可逆, SVD 分解失败: %v", err)
		}

		// 获取 U、Σ 和 V^T
		var u, v mat.Dense
		svd.UTo(&u)
		svd.VTo(&v)
		sigma := svd.Values(nil)

		// 计算 Σ^+ (Sigma pseudo-inverse)
		m, n := u.Dims()
		sigmaInv := mat.NewDense(n, m, nil)
		for i := 0; i < len(sigma); i++ {
			if sigma[i] > 1e-10 { // 避免除以零
				sigmaInv.Set(i, i, 1/sigma[i])
			}
		}

		// 计算 V * Σ^+ * U^T
		var temp mat.Dense
		temp.Mul(&v, sigmaInv)
		AtInv.Mul(&temp, u.T())
	}

	var Atb mat.VecDense
	Atb.MulVec(A.T(), b) // Atb = A^T * b

	var x mat.VecDense
	x.MulVec(&AtInv, &Atb) // x = (A^T * A)^-1 * (A^T * b)

	return mat.Col(nil, 0, &x), nil
}

func localize(num, den *mat.VecDense, row, col float64) (P, L, H float64) {

	return
}

func project(num, den *mat.VecDense, P, L, H float64) (v float64) {
	v = applyPolynominal(num, P, L, H) / applyPolynominal(den, P, L, H)
	return v
}

func applyPolynominal(poly *mat.VecDense,
	P, L, H float64) (v float64) {
	v = 0.0
	v += poly.AtVec(0)
	v += poly.AtVec(1) * L
	v += poly.AtVec(2) * P
	v += poly.AtVec(3) * H
	v += poly.AtVec(4) * L * P
	v += poly.AtVec(5) * L * H
	v += poly.AtVec(6) * P * H
	v += poly.AtVec(7) * L * L
	v += poly.AtVec(8) * P * P
	v += poly.AtVec(9) * H * H
	v += poly.AtVec(10) * P * L * H
	v += poly.AtVec(11) * L * L * L
	v += poly.AtVec(12) * L * P * P
	v += poly.AtVec(13) * L * H * H
	v += poly.AtVec(14) * L * L * P
	v += poly.AtVec(15) * P * P * P
	v += poly.AtVec(16) * P * H * H
	v += poly.AtVec(17) * L * L * H
	v += poly.AtVec(18) * P * P * H
	v += poly.AtVec(19) * H * H * H
	return v
}
