// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.

package clock

import (
	"testing"

	"github.com/google/go-cmp/cmp"
)

func TestTick(t *testing.T) {
	type Expectation struct {
		Samples                []uint64
		Panic                  interface{}
		MonotonicityViolations int
	}

	const logicalClockSize = 0xFFFF
	tests := []struct {
		Name        string
		Ticks       int
		PT          []uint64
		Expectation Expectation
	}{
		{
			Name:        "monotonous physical time",
			Ticks:       3,
			PT:          []uint64{1, 2, 3},
			Expectation: Expectation{Samples: []uint64{65536, 131072, 196608}},
		},
		{
			Name:  "physical time freeze",
			Ticks: 3,
			PT:    []uint64{1, 1, 2},
			Expectation: Expectation{
				Samples:                []uint64{65536, 65537, 131072},
				MonotonicityViolations: 1,
			},
		},
		{
			Name:  "physical time going backwards",
			Ticks: 5,
			PT:    []uint64{1, 2, 1, 2, 5},
			Expectation: Expectation{
				Samples:                []uint64{65536, 131072, 131073, 131074, 327680},
				MonotonicityViolations: 2,
			},
		},
		{
			Name:  "logical time overflow",
			Ticks: logicalClockSize + 10,
			PT: func() []uint64 {
				r := make([]uint64, logicalClockSize+10)
				for i := range r {
					r[i] = 1
				}
				return r
			}(),
			Expectation: Expectation{
				Samples: func() []uint64 {
					r := make([]uint64, logicalClockSize+1)
					for i := range r {
						r[i] = uint64(65535 + i + 1)
					}
					return r
				}(),
				MonotonicityViolations: 65535,
				Panic:                  "logical clock overflow",
			},
		},
	}

	for _, test := range tests {
		t.Run(test.Name, func(t *testing.T) {
			if len(test.PT) != test.Ticks {
				t.Fatalf("%d ticks require %d physical time samples", test.Ticks, test.Ticks)
				return
			}

			var o int
			clock := &HLC{
				physicalTime: func() uint64 {
					v := test.PT[o]
					o++
					return v
				},
			}

			var act Expectation
			clock.ReportBackwardsTime(func(diff uint64) {
				act.MonotonicityViolations++
			})

			func() {
				defer func() {
					act.Panic = recover()
				}()
				for i := 0; i < test.Ticks; i++ {
					act.Samples = append(act.Samples, clock.Tick())
				}
			}()
			if diff := cmp.Diff(test.Expectation, act); diff != "" {
				t.Errorf("unexpected tick (-want +got):\n%s", diff)
			}
		})
	}
}
