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

package ga

import (
	"git.dotya.ml/wanderer/math-optim/bench/cec2020"
)

// migration is used in SOMA-type algos.

func (p *SOMAT3APopulation) migrate(leader int, migrants []int) {
	for _, i := range migrants {
		jumps := make([][]float64, p.Njumps)

		// jump zero.
		// jumps[0] = p.jump(i, leader, getStep(0, maxFES))

		// bestJump of this migrant is now the jump zero.
		// bestJump := jumps[0]

		for j := 0; j < p.Njumps; j++ {
			step := p.getStep()
			jump := p.jump(i, leader, step)

			p.fes += 2

			jumps[j] = jump
		}

		p.Population[i].Jumps = jumps

		p.Population[i].PRTVector = p.getPrtVector()
	}

	p.PRT = p.getPrt()
}

func (p *SOMAT3APopulation) jump(migrant, leader int, step float64) []float64 {
	m := p.Population[migrant]
	l := p.Population[leader]
	prtv := m.PRTVector
	t := step
	jumpPosition := make([]float64, len(m.CurX))

	for i := range m.CurX {
		jumpPosition[i] = m.CurX[i] + ((l.CurX[i] - m.CurX[i]) * t * prtv[i])
	}

	return jumpPosition
}

// getPrtVector creates and returns the perturbation vector.
func (p *SOMAT3APopulation) getPrtVector() []float64 {
	prtv := make([]float64, p.Dimen)

	for i := range prtv {
		r := p.zeroToOneRNG.Rand()

		switch {
		case r < p.PRT:
			prtv[i] = 1

		case r > p.PRT:
			prtv[i] = 0
		}
	}

	return prtv
}

// getPrt calculates the adaptive PRT.
func (p *SOMAT3APopulation) getPrt() float64 {
	return 0.05 + 0.9*float64(p.fes/cec2020.GetMaxFES(p.Dimen))
}

// getStep calculates the step size.
func (p *SOMAT3APopulation) getStep() float64 {
	return 0.15 + 0.08*float64(p.fes/cec2020.GetMaxFES(p.Dimen))
}
