package syncUtil

import (
	"context"
	"fmt"
	"sync"
	"testing"
	"time"
)

func TestMutex(t *testing.T) {
	mu := NewMutex()
	mu.Lock()
	defer mu.UnLock()
	if mu.TryLock() {
		t.Errorf("cannot fetch mutex !!!")
	}
}

func TestMutexTryLockTimeout(t *testing.T) {
	fmt.Println("start")
	mu := NewMutex()
	mu.Lock()
	go func() {
		time.Sleep(20 * time.Second)
		mu.UnLock()
	}()
	// if !mu.TryLockTimeout(500 * time.Microsecond) {
	// 	t.Errorf("cannot fetch mutex in 500us !!!")
	// }
	if !mu.TryLockTimeout(15 * time.Second) {
		t.Errorf("should fetch mutex in 5ms !!!")
	}
	mu.UnLock()
}

func TestMutexUnlockTwice(t *testing.T) {
	mu := NewMutex()
	mu.Lock()
	defer func() {
		if x := recover(); x != nil {
			if x != "unlock of unlocked mutex" {
				t.Errorf("unexpect panic")
			}
		} else {
			t.Errorf("should panic after unlock twice")
		}
	}()
	mu.UnLock()
	mu.UnLock()
}

func TestMutexTryLockContext(t *testing.T) {
	mu := NewMutex()
	ctx, cancel := context.WithCancel(context.Background())
	mu.Lock()
	go func() {
		time.Sleep(10 * time.Millisecond)
		cancel()
	}()
	if mu.TryLockContext(ctx) {
		t.Errorf("cannot fetch mutex !!!")
	}
}

func BenchmarkMutex(b *testing.B) {
	mu := NewMutex()
	a := 0
	c := 0
	b.ReportAllocs()
	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			mu.Lock()
			a++
			mu.UnLock()
			mu.Lock()
			c = a
			mu.UnLock()
		}
	})
	_ = a
	_ = c
}

func TestMutexGroup(t *testing.T) {
	mu := NewMutexGroup()
	mu.Lock("g")
	defer mu.UnLock("g")
	if mu.TryLock("g") {
		t.Errorf("cannot fetch mutex !!!")
	}
}

func TestMutexGroupMutliWaitLock(t *testing.T) {
	var (
		wg sync.WaitGroup
		mu = NewMutexGroup()
		cn = 3
	)

	for i := 0; i < cn; i++ {
		wg.Add(1)
		go func() {
			mu.Lock("h")
			time.Sleep(1e7)
			mu.UnLock("h")
			wg.Done()
		}()
	}
	wg.Wait()

	for i := 0; i < cn; i++ {
		wg.Add(1)
		go func() {
			mu.Lock("g")
			time.Sleep(1e7)
			mu.UnLockAndFree("g")
			wg.Done()
		}()
	}
	wg.Wait()
}

func TestMutexGroupUnLockAndFree(t *testing.T) {
	var (
		wg sync.WaitGroup
		mu = NewMutexGroup()
		mg = mu.(*mutexGroup)
	)

	for j := 1; j < 5; j++ {
		for i := 0; i < j; i++ {
			wg.Add(1)
			go func() {
				mu.Lock("h")
				time.Sleep(1e6)
				mu.UnLockAndFree("h")
				wg.Done()
			}()
		}
		wg.Wait()
		mg.mu.Lock()
		if _, ok := mg.group["h"]; ok {
			t.Error("h mutex exist after UnLockAndFree")
		}
		mg.mu.Unlock()
	}
}

func TestMutexGroupTryLockFailedAndUnLockAndFree(t *testing.T) {
	var (
		wg sync.WaitGroup
		mu = NewMutexGroup()
		mg = mu.(*mutexGroup)
	)

	for j := 1; j < 5; j++ {
		for i := 0; i < j; i++ {
			wg.Add(1)
			go func() {
				if mu.TryLock("h") {
					time.Sleep(1e6)
					mu.UnLockAndFree("h")
				}
				wg.Done()
			}()
		}
		wg.Wait()
		mg.mu.Lock()
		if _, ok := mg.group["h"]; ok {
			t.Error("h mutex exist after UnLockAndFree")
		}
		mg.mu.Unlock()
	}
}

func TestMutexGroupTryLockTimeout(t *testing.T) {
	mu := NewMutexGroup()
	mu.Lock("g")
	go func() {
		time.Sleep(1 * time.Millisecond)
		mu.UnLock("g")
	}()
	if mu.TryLockTimeout("g", 500*time.Microsecond) {
		t.Errorf("cannot fetch mutex in 500us !!!")
	}
	if !mu.TryLockTimeout("g", 5*time.Millisecond) {
		t.Errorf("should fetch mutex in 5ms !!!")
	}
	mu.UnLock("g")
}

func TestMutexGroupTryLockContext(t *testing.T) {
	mu := NewMutexGroup()
	ctx, cancel := context.WithCancel(context.Background())
	mu.Lock("g")
	go func() {
		time.Sleep(10 * time.Millisecond)
		cancel()
	}()
	if mu.TryLockContext("g", ctx) {
		t.Errorf("cannot fetch mutex !!!")
	}
}

func BenchmarkMutexGroup(b *testing.B) {
	mu := NewMutexGroup()
	a := 0
	c := 0
	b.ReportAllocs()
	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			mu.Lock("g")
			a++
			mu.UnLock("g")
			mu.Lock("g")
			c = a
			mu.UnLock("g")
		}
	})
	_ = a
	_ = c
}
