// Copyright 2023 wanderer <a_mirre at utb dot cz>
// SPDX-License-Identifier: GPL-3.0-or-later

package cec2020

import "math"

// BentCigar is the "Bent Cigar Function" of CEC2020.
func BentCigar(x []float64) float64 {
	var sum float64

	for i := 1; i < len(x); i++ {
		sum += math.Pow(x[i], 2)
	}

	return (1000000 * sum) + math.Pow(x[0], 2)
}

// Schwefel is the "Shifted and Rotated Schwefel's Function" of CEC2020.
func Schwefel(x []float64) float64 {
	return SchwefelModified(x)
}

// LunacekBiRastrigin is the "Shifted and Rotated Lunacek bi-Rastrigin Function" of CEC2020.
// ref: https://al-roomi.org/benchmarks/unconstrained/n-dimensions/229-lunacek-s-bi-rastrigin-function.
func LunacekBiRastrigin(x []float64) float64 {
	var sum0 float64

	var sum1 float64

	var sum2 float64

	nx := len(x)
	fnx := float64(nx)
	s := 1 - (1 / ((2 * math.Sqrt(fnx+20)) - 8.2))
	d := 1.0
	mu0 := 2.5
	mu1 := -math.Sqrt((math.Pow(mu0, 2) - d) / s)
	xhat := getMean(x)

	for i := range x {
		sum0 += math.Pow(xhat-mu0, 2)

		sum1 += math.Pow(xhat-mu1, 2)

		zi := math.Pow(100, 0.5*((float64(i)-1)/fnx-1)) * xhat

		sum2 += (1 - math.Cos(2*math.Pi*zi))
	}

	return math.Min(sum0, (d*fnx)+(s*sum1)) + 10*sum2
}

// RosenbrockGriewank is the "Expanded Rosenbrock's plus Griewank's Function"
// of CEC2020.
func RosenbrockGriewank(x []float64) float64 {
	var sum float64

	nx := len(x)

	for i := range x {
		f := []float64{x[i], x[(i+1)%nx]}

		sum += Griewank([]float64{Rosenbrock(f)})
	}

	return sum
}

// Hybrid1 is the "Hybrid Function 1" of CEC2020.
func Hybrid1(x []float64) float64 {
	nx := len(x)
	fnx := float64(nx)

	funcs := []func([]float64) float64{
		SchwefelModified,
		Rastrigin,
		HighConditionedElliptic,
	}
	// percentages used to control the amount of contribution of each func.
	p := []float64{0.3, 0.3, 0.4}
	gnx := getGnx(p, fnx)

	var sum float64

	for i := range funcs {
		sum += funcs[i](x) * gnx[i]
	}

	return sum
}

// Hybrid2 is the "Hybrid Function 2" of CEC2020.
func Hybrid2(x []float64) float64 {
	nx := len(x)
	fnx := float64(nx)

	funcs := []func([]float64) float64{
		SchafferExpanded,
		HGBat,
		Rosenbrock,
		SchwefelModified,
	}
	// percentages used to control the amount of contribution of each func.
	p := []float64{0.2, 0.2, 0.3, 0.3}
	gnx := getGnx(p, fnx)

	var sum float64

	for i := range funcs {
		sum += funcs[i](x) * gnx[i]
	}

	return sum
}

// Hybrid3 is the "Hybrid Function 3" of CEC2020.
func Hybrid3(x []float64) float64 {
	nx := len(x)
	fnx := float64(nx)

	funcs := []func([]float64) float64{
		SchafferExpanded,
		HGBat,
		Rosenbrock,
		SchwefelModified,
		HighConditionedElliptic,
	}
	// percentages used to control the amount of contribution of each func.
	p := []float64{0.1, 0.2, 0.2, 0.2, 0.3}
	gnx := getGnx(p, fnx)

	var sum float64

	for i := range funcs {
		sum += funcs[i](x) * gnx[i]
	}

	return sum
}

// Composition1 is the "Composition Function 1" of CEC2020.
func Composition1(x []float64) float64 {
	nx := len(x)
	fnx := float64(nx)

	// optimum positions.
	o := newOpt(nx)
	sigma := []float64{10, 20, 30}
	lambda := []float64{1, 10, 1}
	bias := []float64{0, 100, 200}
	omega := make([]float64, nx)
	funcs := []func([]float64) float64{
		Rastrigin,
		Griewank,
		SchwefelModified,
	}

	var sum float64

	weights := make([]float64, len(funcs))

	for i := range funcs {
		wi := getWeight(x, o, sigma[i], fnx)

		weights = append(weights, wi)

		var wsum float64

		for j := range weights {
			wsum += weights[j]
		}

		omega = append(omega, wi/wsum)

		sum += (omega[i] * (lambda[i]*funcs[i](x) + bias[i])) + funcs[i](x)
	}

	return sum
}

// Composition2 is the "Composition Function 2" of CEC2020.
func Composition2(x []float64) float64 {
	nx := len(x)
	fnx := float64(nx)

	// optimum positions.
	o := newOpt(nx)
	sigma := []float64{10, 20, 30, 40}
	lambda := []float64{10, 1e-6, 10, 1}
	bias := []float64{0, 100, 200, 300}
	omega := make([]float64, nx)
	funcs := []func([]float64) float64{
		Ackley,
		HighConditionedElliptic,
		Griewank,
		Rastrigin,
	}

	var sum float64

	weights := make([]float64, len(funcs))

	for i := range funcs {
		wi := getWeight(x, o, sigma[i], fnx)

		weights = append(weights, wi)

		var wsum float64

		for j := range weights {
			wsum += weights[j]
		}

		omega = append(omega, wi/wsum)

		sum += (omega[i] * (lambda[i]*funcs[i](x) + bias[i])) + funcs[i](x)
	}

	return sum
}

// Composition3 is the "Composition Function 3" of CEC2020.
func Composition3(x []float64) float64 {
	nx := len(x)
	fnx := float64(nx)

	// optimum positions.
	o := newOpt(nx)
	sigma := []float64{10, 20, 30, 40, 50}
	lambda := []float64{10, 1, 10, 1e-6, 1}
	bias := []float64{0, 100, 200, 300, 400}
	omega := make([]float64, nx)
	funcs := []func([]float64) float64{
		Rastrigin,
		Happycat,
		Ackley,
		Discus,
		Rosenbrock,
	}

	var sum float64

	weights := make([]float64, len(funcs))

	for i := range funcs {
		wi := getWeight(x, o, sigma[i], fnx)

		weights = append(weights, wi)

		var wsum float64

		for j := range weights {
			wsum += weights[j]
		}

		omega = append(omega, wi/wsum)

		sum += (omega[i] * (lambda[i]*funcs[i](x) + bias[i])) + funcs[i](x)
	}

	return sum
}
