// Copyright 2023 CloudWeGo Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package test

import (
	"fmt"
	"hash/fnv"
	"math/rand"
	"net"
	"os"
	"runtime/debug"
	"strconv"
	"strings"
	"sync/atomic"
	"time"
)

const (
	UnixUserPortStart = 1023
	UnixUserPortEnd   = 49151
)

func hashInt(n int) uint32 {
	h := fnv.New32a()
	_, _ = h.Write([]byte(strconv.Itoa(n)))
	return h.Sum32()
}

// Go test may start multiple process for testing, so it's easy to get same port with same starting port
var curPort uint32 = UnixUserPortStart + hashInt(os.Getpid())%200*200

// GetLocalAddress return a local address starting from 1024
// This API ensures no repeated addr returned in one UNIX OS
func GetLocalAddress() string {
	for {
		time.Sleep(time.Millisecond * time.Duration(1+rand.Intn(10)))
		port := atomic.AddUint32(&curPort, 1+uint32(rand.Intn(10)))
		addr := "127.0.0.1:" + strconv.Itoa(int(port))
		if !IsAddressInUse(addr) {
			trace := strings.Split(string(debug.Stack()), "\n")
			if len(trace) > 6 {
				println(fmt.Sprintf("%s: GetLocalAddress = %v", trace[6], addr))
			}
			return addr
		}
	}
}

// tells if a net address is already in use.
func IsAddressInUse(address string) bool {
	ln, err := net.Listen("tcp", address)
	if err != nil {
		return true
	}
	ln.Close()
	return false
}

// WaitServerStart waits for server to start for at most 1 second
func WaitServerStart(addr string) {
	for begin := time.Now(); time.Since(begin) < time.Second; {
		if _, err := net.Dial("tcp", addr); err == nil {
			println("server is up at", addr)
			return
		}
		time.Sleep(time.Millisecond * 10)
	}
}
