package geo

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestMovement(t *testing.T) {
	t.Run("BerlinShanghai", func(t *testing.T) {
		berlin := Position{
			Name:     "Berlin",
			Time:     time.Date(2015, 5, 17, 11, 48, 46, 0, time.UTC),
			Lat:      52.5243700,
			Lng:      13.4105300,
			Altitude: 34}

		shanghai := Position{
			Name:     "Shanghai",
			Time:     time.Date(2015, 5, 17, 23, 14, 34, 0, time.UTC),
			Lat:      31.2222200,
			Lng:      121.4580600,
			Altitude: 4}

		result := NewMovement(berlin, shanghai)
		t.Log(result.String())

		// movement from 2015-05-17 23:14:34 to 2015-05-17 11:48:46 in 41148.000000 s,
		// Δ lat -21.302150, Δ lng  108.047530, dist 8396.511771 km, speed 734.602955 km/h
		assert.InEpsilon(t, 41148.00, result.Seconds(), 0.01)
		assert.InEpsilon(t, 11.43, result.Hours(), 0.01)
		assert.InEpsilon(t, -21.302150, result.DegLat(), 0.01)
		assert.InEpsilon(t, 108.047530, result.DegLng(), 0.01)
		assert.InEpsilon(t, 8396.511771, result.Km(), 0.01)
		assert.InEpsilon(t, 734.602955, result.Speed(), 0.1)
		assert.InEpsilon(t, 19, result.AverageAltitude(), 0.001)

		posEst1 := result.EstimatePosition(time.Date(2015, 5, 17, 12, 05, 22, 0, time.UTC))
		t.Log(posEst1.String())

		// 2015-05-17 12:05:22 @ 52.008745, 16.025854
		// estimate @ 31.325956, 120.931898, 4.000000 m
		assert.InEpsilon(t, 52.008745, posEst1.Lat, 0.01)
		assert.InEpsilon(t, 16.025854, posEst1.Lng, 0.01)
		assert.True(t, posEst1.Estimate)

		posEst2 := result.EstimatePosition(time.Date(2015, 5, 17, 18, 14, 34, 0, time.UTC))
		t.Log(posEst2.String())

		// 2015-05-17 18:14:34 @ 40.540746, 74.193174
		assert.InEpsilon(t, 40.540746, posEst2.Lat, 0.01)
		assert.InEpsilon(t, 74.193174, posEst2.Lng, 0.01)
		assert.True(t, posEst2.Estimate)

		posMid := result.Midpoint()
		t.Log(posMid.String())

		// midpoint @ 41.873295, 67.434295
		assert.InEpsilon(t, 41.873295, posMid.Lat, 0.01)
		assert.InEpsilon(t, 67.434295, posMid.Lng, 0.01)
	})
	t.Run("PositionBefore", func(t *testing.T) {
		timeEst := time.Date(2019, time.July, 21, 11, 56, 47, 0, time.UTC)

		pos1 := Position{
			Name: "Pos1",
			Time: time.Date(2019, time.July, 21, 15, 37, 44, 0, time.UTC),
			Lat:  48.2992,
			Lng:  8.92953}

		pos2 := Position{
			Name: "Pos2",
			Time: time.Date(2019, time.July, 21, 15, 37, 42, 0, time.UTC),
			Lat:  48.2992,
			Lng:  8.92954}

		result := NewMovement(pos1, pos2)

		t.Log(result.String())

		// from 2019-07-21 15:37:42 to 2019-07-21 15:37:44 in 2.000000 s
		// Δ lat 0.000000, Δ lng -0.000010, dist 0.000740 km, speed 1.331485 km/h
		assert.InEpsilon(t, 2.000000, result.Seconds(), 0.01)
		assert.Equal(t, 0.000000, result.DegLat(), 0.01)
		assert.InEpsilon(t, -0.000010, result.DegLng(), 0.01)
		assert.InEpsilon(t, 0.000740, result.Km(), 0.01)
		assert.InEpsilon(t, 1.331485, result.Speed(), 0.01)

		posEst := result.EstimatePosition(timeEst)

		t.Log(posEst.String())

		// 2019-07-21 11:56:47 @ 48.299200, 8.930116
		assert.InEpsilon(t, 48.299200, posEst.Lat, 0.01)
		assert.InEpsilon(t, 8.930116, posEst.Lng, 0.01)
		assert.True(t, posEst.Estimate)

		posMid := result.Midpoint()

		t.Log(posMid.String())

		// midpoint @ 48.299200, 8.929535
		assert.InEpsilon(t, 48.299200, posMid.Lat, 0.001)
		assert.InEpsilon(t, 8.929535, posMid.Lng, 0.01)
	})
	t.Run("TooFast", func(t *testing.T) {
		timeEst := time.Date(2019, time.July, 21, 11, 56, 47, 0, time.UTC)

		pos1 := Position{Name: "Pos1", Time: time.Date(2019, time.July, 21, 15, 37, 38, 0, time.UTC), Lat: 48.3032, Lng: 8.92772}
		pos2 := Position{Name: "Pos2", Time: time.Date(2019, time.July, 21, 15, 37, 42, 0, time.UTC), Lat: 48.2992, Lng: 8.92954}

		result := NewMovement(pos1, pos2)

		t.Log(result.String())

		// from 2019-07-21 15:37:38 to 2019-07-21 15:37:42 in 4.000000 s
		// Δ lat -0.004000, Δ lng 0.001820, dist 0.464707 km, speed 418.235966 km/h
		assert.InEpsilon(t, 4.0, result.Seconds(), 0.01)
		assert.InEpsilon(t, -0.004000, result.DegLat(), 0.01)
		assert.InEpsilon(t, 0.001820, result.DegLng(), 0.01)
		assert.InEpsilon(t, 0.464707, result.Km(), 0.01)
		assert.InEpsilon(t, 418.23596, result.Speed(), 0.1)

		posEst := result.EstimatePosition(timeEst)

		t.Log(posEst.String())

		// midpoint @ 48.301200, 8.928630
		assert.InEpsilon(t, 48.301200, posEst.Lat, 0.01)
		assert.InEpsilon(t, 8.928630, posEst.Lng, 0.01)
		assert.True(t, posEst.Estimate)

		posMid := result.Midpoint()

		t.Log(posMid.String())

		// midpoint @ 48.301200, 8.928630
		assert.InEpsilon(t, 48.301200, posMid.Lat, 0.01)
		assert.InEpsilon(t, 8.928630, posMid.Lng, 0.01)
	})
	t.Run("PositionBetween", func(t *testing.T) {
		timeEst := time.Date(2019, time.July, 21, 11, 56, 47, 0, time.UTC)

		pos1 := Position{Name: "Pos1", Time: time.Date(2019, time.July, 21, 15, 37, 44, 0, time.UTC), Lat: 48.2992, Lng: 8.92953}
		pos2 := Position{Name: "Pos2", Time: time.Date(2019, time.July, 20, 19, 33, 16, 0, time.UTC), Lat: 48.2994, Lng: 8.92914}

		result := NewMovement(pos1, pos2)

		t.Log(result.String())

		// movement from 2019-07-20 19:33:16 to 2019-07-21 15:37:44 in 72268.000000 s
		// Δ lat -0.000200, Δ lng 0.000390, dist 0.036426 km, speed 0.001815 km/
		assert.InEpsilon(t, 72268.000000, result.Seconds(), 0.01)
		assert.InEpsilon(t, -0.000200, result.DegLat(), 0.01)
		assert.InEpsilon(t, 00.000390, result.DegLng(), 0.01)
		assert.InEpsilon(t, 0.036426, result.Km(), 0.01)
		assert.InEpsilon(t, 0.001815, result.Speed(), 0.1)

		posEst := result.EstimatePosition(timeEst)

		t.Log(posEst.String())

		// 2019-07-21 11:56:47: lat 48.299237, lng 8.929458
		assert.InEpsilon(t, 48.299237, posEst.Lat, 0.01)
		assert.InEpsilon(t, 8.929458, posEst.Lng, 0.01)
		assert.True(t, posEst.Estimate)

		posMid := result.Midpoint()

		t.Log(posMid.String())

		// midpoint: lat 48.299300, lng 8.929335
		assert.InEpsilon(t, 48.299300, posMid.Lat, 0.01)
		assert.InEpsilon(t, 8.929335, posMid.Lng, 0.01)
	})
	t.Run("NotRealistic", func(t *testing.T) {
		timeEst := time.Date(2013, time.August, 10, 00, 05, 37, 0, time.UTC)

		time1 := time.Date(2013, time.August, 9, 17, 9, 0, 0, time.UTC)
		time2 := time.Date(2013, time.August, 9, 17, 8, 44, 0, time.UTC)

		pos1 := Position{Name: "Pos1", Time: time1, Lat: 52.6648, Lng: 13.3387}
		pos2 := Position{Name: "Pos2", Time: time2, Lat: 48.5193, Lng: 9.04933}

		result := NewMovement(pos1, pos2)

		t.Log(result.String())

		// movement from 2013-08-09 17:08:44 to 2013-08-09 17:09:00 in 16.000000 s
		// Δ lat 4.145500, Δ lng  4.289370, dist 551.290399 km, speed 124040.339691 km/h
		assert.InEpsilon(t, 16.000000, result.Seconds(), 0.01)
		assert.InEpsilon(t, 4.145500, result.DegLat(), 0.01)
		assert.InEpsilon(t, 4.289370, result.DegLng(), 0.01)
		assert.InEpsilon(t, 551.290399, result.Km(), 0.01)
		assert.InEpsilon(t, 124040.339691, result.Speed(), 0.1)
		assert.False(t, result.Realistic())

		posEst := result.EstimatePosition(timeEst)

		t.Log(posEst.String())

		// estimate @ 52.664800, 13.338700, alt 0.000000 m ± 275645 m
		assert.InEpsilon(t, 52.664800, posEst.Lat, 0.01)
		assert.InEpsilon(t, 13.338700, posEst.Lng, 0.01)
		assert.InEpsilon(t, 275645, posEst.Accuracy, 0.1)
		assert.True(t, posEst.Estimate)

		posMid := result.Midpoint()

		t.Log(posMid.String())

		// midpoint @ 50.592050, 11.194015, alt 0.000000 m ± 0 m
		assert.InEpsilon(t, 50.592050, posMid.Lat, 0.01)
		assert.InEpsilon(t, 11.194015, posMid.Lng, 0.01)
		assert.Equal(t, 275645, result.EstimateAccuracy(timeEst))
	})
}
