// Copyright (c) 2011, 2015 Kiel University and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0.
//
// SPDX-License-Identifier: EPL-2.0

package force

import (
	

	
	
	
)

const displacementBoundFactor = 16

type forceModel interface {
	layout(*FGraph, core.ProgressMonitor)
}

type abstractForceModel struct {
	graph             *FGraph
	random            *javacompat.Random
	displacementBound float64
}

func ( *abstractForceModel) ( *FGraph) {
	.graph = 
	.random = .Random
	.CalcAdjacency()
	.displacementBound = math.Max(
		float64(len(.Nodes)*displacementBoundFactor+len(.Edges)),
		displacementBoundFactor*displacementBoundFactor,
	)
	if !.Interactive {
		 := float64(len(.Nodes))
		for ,  := range .Nodes {
			.Position.X = .random.NextDouble() * 
			.Position.Y = .random.NextDouble() * 
		}
	}
	for ,  := range .Edges {
		for  := 0;  < .RepulsivePower; ++ {
			 := &FBendpoint{Particle: Particle{Priority: 1}, Edge: }
			.Particle.bendEdge = 
			.Bendpoints = append(.Bendpoints, )
			.Bendpoints = append(.Bendpoints, )
		}
		.DistributeBendpoints()
	}
}

func ( *abstractForceModel) () {
	for ,  := range .graph.Edges {
		for ,  := range .Labels {
			.RefreshPosition()
		}
		.DistributeBendpoints()
	}
}

func avoidSamePosition( *javacompat.Random, ,  *Particle) {
	for .Position.X-.Position.X == 0 && .Position.Y-.Position.Y == 0 {
		if .bendEdge != nil && .bendEdge != nil {
			// ELK 0.12 first separates coincident bend particles
			// orthogonally to their respective edge directions. Its second
			// update intentionally targets the first particle as well.
			 := .bendEdge.TargetPoint().Sub(.bendEdge.SourcePoint())
			 := forceVectorLength()
			.Position = .Position.Add(geometry.Point{
				X: .X /  * 2,
				Y: -.Y /  * 2,
			})
			 := .bendEdge.TargetPoint().Sub(.bendEdge.SourcePoint())
			 := forceVectorLength()
			 := 2.0
			if .bendEdge == .bendEdge {
				// Java compares the two freshly allocated KVector instances by
				// identity, so this branch is unreachable in the source too.
				 = 2
			}
			.Position = .Position.Add(geometry.Point{
				X: .X /  * ,
				Y: -.Y /  * ,
			})
		} else {
			.Position.X += .NextDouble() - 0.5
			.Position.Y += .NextDouble() - 0.5
			.Position.X += .NextDouble() - 0.5
			.Position.Y += .NextDouble() - 0.5
		}
	}
}

type eadesModel struct {
	abstractForceModel
	maxIterations   int
	springLength    float64
	repulsionFactor float64
}

func ( *eadesModel) ( *FGraph,  core.ProgressMonitor) {
	.initialize()
	.maxIterations = .Iterations
	.springLength = .SpacingNodeNode
	.repulsionFactor = .Repulsion
	.run(, func( int) bool { return  < .maxIterations }, .displacement)
}

func ( *eadesModel) (,  *Particle) geometry.Point {
	avoidSamePosition(.random, , )
	 := .Position.Sub(.Position)
	 := forceVectorLength()
	 := math.Max(0, -.Radius()-.Radius())
	 := .graph.Connection(, )
	var  float64
	if  > 0 {
		 = -eadesAttractive(, .springLength) * float64()
	} else {
		 = eadesRepulsive(, .repulsionFactor) * float64(.Priority)
	}
	 := float64( / )
	return .Scale()
}

func eadesRepulsive(,  float64) float64 {
	if  > 0 {
		 := float64( * )
		return  / 
	}
	return  * 100
}

func eadesAttractive(,  float64) float64 {
	if  > 0 {
		return math.Log( / )
	}
	return -100
}

func ( *eadesModel) (
	 core.ProgressMonitor,
	 func(int) bool,
	 func(*Particle, *Particle) geometry.Point,
) {
	runForceIterations(&.abstractForceModel, , , , nil)
}

type fruchtermanReingoldModel struct {
	abstractForceModel
	temperature float64
	threshold   float64
	k           float64
}

func ( *fruchtermanReingoldModel) ( *FGraph,  core.ProgressMonitor) {
	.initialize()
	.temperature = .Temperature
	.threshold = .temperature / float64(.Iterations)
	,  := 0.0, 0.0
	for ,  := range .Nodes {
		 += .Size.X
		 += .Size.Y
	}
	 :=  * 
	 := .SpacingNodeNode * 0.01
	.k = math.Sqrt(/float64(2*len(.Nodes))) * 
	runForceIterations(
		&.abstractForceModel,
		,
		func(int) bool { return .temperature > 0 },
		.displacement,
		func() { .temperature -= .threshold },
	)
}

func ( *fruchtermanReingoldModel) (,  *Particle) geometry.Point {
	avoidSamePosition(.random, , )
	 := .Position.Sub(.Position)
	 := forceVectorLength()
	 := math.Max(0, -.Radius()-.Radius())
	 := frRepulsive(, .k) * float64(.Priority)
	if  := .graph.Connection(, );  > 0 {
		// Same-type conversions here and below are deliberate. The Go spec
		// otherwise permits fused floating-point evaluation across statements,
		// while GWT's generated JavaScript rounds these intermediate products.
		 := float64(frAttractive(, .k) * float64())
		 -= 
	}
	 := float64( * .temperature)
	 := float64( / )
	return .Scale()
}

func forceVectorLength( geometry.Point) float64 {
	// Explicit conversions preserve the two multiplications as separately
	// rounded operations before the addition, matching KVector.length in GWT.
	 := float64(.X * .X)
	 := float64(.Y * .Y)
	return math.Sqrt( + )
}

func frRepulsive(,  float64) float64 {
	 := float64( * )
	if  > 0 {
		return  / 
	}
	return float64( * 100)
}

func frAttractive(,  float64) float64 {
	 := float64( * )
	return  / 
}

func runForceIterations(
	 *abstractForceModel,
	 core.ProgressMonitor,
	 func(int) bool,
	 func(*Particle, *Particle) geometry.Point,
	 func(),
) {
	.Begin("Component Layout", 1)
	 := 0
	for () && !.Canceled() {
		.iterationDone()
		if  != nil {
			()
		}
		 := .graph.Particles()
		for ,  := range  {
			for ,  := range  {
				if  !=  {
					.Displacement = .Displacement.Add((, ))
				}
			}
		}
		for ,  := range  {
			.Displacement = .Displacement.Bound(
				-.displacementBound, -.displacementBound,
				.displacementBound, .displacementBound,
			)
			.Position = .Position.Add(.Displacement)
			.Displacement = geometry.Point{}
		}
		++
	}
	.Done()
}