// Copyright 2020 The Libc Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build !(linux && (amd64 || arm64 || loong64 || ppc64le || s390x || riscv64 || 386 || arm))

package libc // import "modernc.org/libc"

import (
	"encoding/hex"
	"os"
	"path/filepath"
	"runtime"
	"testing"
	"time"
	"unsafe"

	_ "modernc.org/cc/v4"
	_ "modernc.org/ccgo/v4/lib"
	_ "modernc.org/fileutil/ccgo"
	ctime "modernc.org/libc/time"
)

func TestXfmod(t *testing.T) {
	x := 1.3518643030646695
	y := 6.283185307179586
	if g, e := Xfmod(nil, x, y), 1.3518643030646695; g != e {
		t.Fatal(g, e)
	}
}

func TestSwap(t *testing.T) {
	if g, e := X__builtin_bswap16(nil, 0x1234), uint16(0x3412); g != e {
		t.Errorf("%#04x %#04x", g, e)
	}
	if g, e := X__builtin_bswap32(nil, 0x12345678), uint32(0x78563412); g != e {
		t.Errorf("%#04x %#04x", g, e)
	}
	if g, e := X__builtin_bswap64(nil, 0x123456789abcdef0), uint64(0xf0debc9a78563412); g != e {
		t.Errorf("%#04x %#04x", g, e)
	}
}

var (
	valist       [256]byte
	formatString [256]byte
	srcString    [256]byte
	testPrintfS1 = [...]byte{'X', 'Y', 0}
)

func TestPrintf(t *testing.T) {
	isWindows = true
	i := uint64(0x123456789abcdef)
	j := uint64(0xf123456789abcde)
	k := uint64(0x23456789abcdef1)
	l := uint64(0xef123456789abcd)
	for itest, test := range []struct {
		fmt    string
		args   []interface{}
		result string
	}{
		{
			"%I64x %I32x %I64x %I32x",
			[]interface{}{int64(i), int32(j), int64(k), int32(l)},
			"123456789abcdef 789abcde 23456789abcdef1 6789abcd",
		},
		{
			"%llx %x %llx %x",
			[]interface{}{int64(i), int32(j), int64(k), int32(l)},
			"123456789abcdef 789abcde 23456789abcdef1 6789abcd",
		},
		{
			"%.1s\n",
			[]interface{}{uintptr(unsafe.Pointer(&testPrintfS1[0]))},
			"X\n",
		},
		{
			"%.2s\n",
			[]interface{}{uintptr(unsafe.Pointer(&testPrintfS1[0]))},
			"XY\n",
		},
	} {
		copy(formatString[:], test.fmt+"\x00")
		b := printf(uintptr(unsafe.Pointer(&formatString[0])), VaList(uintptr(unsafe.Pointer(&valist[0])), test.args...))
		if g, e := string(b), test.result; g != e {
			t.Errorf("%v: %q %q", itest, g, e)
		}
	}
}

func TestStrtod(t *testing.T) {
	tls := NewTLS()
	defer tls.Close()

	for itest, test := range []struct {
		s      string
		result float64
	}{
		{"+0", 0},
		{"+1", 1},
		{"+2", 2},
		{"-0", 0},
		{"-1", -1},
		{"-2", -2},
		{".5", .5},
		{"0", 0},
		{"1", 1},
		{"1.", 1},
		{"1.024e3", 1024},
		{"16", 16},
		{"2", 2},
		{"32", 32},
	} {
		copy(srcString[:], test.s+"\x00")
		if g, e := Xstrtod(tls, uintptr(unsafe.Pointer(&srcString[0])), 0), test.result; g != e {
			t.Errorf("%v: %q: %v %v", itest, test.s, g, e)
		}
	}
}

func TestParseZone(t *testing.T) {
	for itest, test := range []struct {
		in, out string
		off     int
	}{
		{"America/Los_Angeles", "America/Los_Angeles", 0},
		{"America/Los_Angeles+12", "America/Los_Angeles", 43200},
		{"America/Los_Angeles-12", "America/Los_Angeles", -43200},
		{"UTC", "UTC", 0},
		{"UTC+1", "UTC", 3600},
		{"UTC+10", "UTC", 36000},
		{"UTC-1", "UTC", -3600},
		{"UTC-10", "UTC", -36000},
	} {
		out, off := parseZone(test.in)
		if g, e := out, test.out; g != e {
			t.Errorf("%d: %+v %v %v", itest, test, g, e)
		}
		if g, e := off, test.off; g != e {
			t.Errorf("%d: %+v %v %v", itest, test, g, e)
		}
	}
}

func TestRint(t *testing.T) {
	tls := NewTLS()
	for itest, test := range []struct {
		x, y float64
	}{
		{-1.1, -1.0},
		{-1.0, -1.0},
		{-0.9, -1.0},
		{-0.51, -1.0},
		{-0.49, 0},
		{-0.1, 0},
		{-0, 0},
		{0.1, 0},
		{0.49, 0},
		{0.51, 1},
		{0.9, 1},
		{1, 1},
		{1.1, 1},
	} {
		if g, e := Xrint(tls, test.x), test.y; g != e {
			t.Errorf("#%d: x %v, got %v, expected %v", itest, test.x, g, e)
		}
	}
}

var testMemsetBuf [67]byte

func TestMemset(t *testing.T) {
	v := 0
	for start := 0; start < len(testMemsetBuf); start++ {
		for n := 0; n < len(testMemsetBuf)-start; n++ {
			for x := range testMemsetBuf {
				testMemsetBuf[x] = byte(v)
				v++
			}
			for x := start; x < start+n; x++ {
				testMemsetBuf[x] = byte(v)
			}
			e := testMemsetBuf
			Xmemset(nil, uintptr(unsafe.Pointer(&testMemsetBuf[start])), int32(v), size_t(n))
			if testMemsetBuf != e {
				t.Fatalf("start %v, v %#x n %v, exp\n%s\ngot\n%s", start, byte(v), n, hex.Dump(e[:]), hex.Dump(testMemsetBuf[:]))
			}
		}
	}
}

const testGetentropySize = 100

var testGetentropyBuf [testGetentropySize]byte

func TestGetentropy(t *testing.T) {
	Xgetentropy(NewTLS(), uintptr(unsafe.Pointer(&testGetentropyBuf[0])), testGetentropySize)
	t.Logf("\n%s", hex.Dump(testGetentropyBuf[:]))
}

func TestReallocArray(t *testing.T) {
	const size = 16
	tls := NewTLS()
	p := Xmalloc(tls, size)
	if p == 0 {
		t.Fatal()
	}

	for i := 0; i < size; i++ {
		(*RawMem)(unsafe.Pointer(p))[i] = byte(i ^ 0x55)
	}

	q := Xreallocarray(tls, p, 2, size)
	if q == 0 {
		t.Fatal()
	}

	for i := 0; i < size; i++ {
		if g, e := (*RawMem)(unsafe.Pointer(q))[i], byte(i^0x55); g != e {
			t.Fatal(i, g, e)
		}
	}
}

func mustCString(s string) uintptr {
	r, err := CString(s)
	if err != nil {
		panic("CString failed")
	}

	return r
}

var testSnprintfBuf [3]byte

func TestSnprintf(t *testing.T) {
	testSnprintfBuf = [3]byte{0xff, 0xff, 0xff}
	p := uintptr(unsafe.Pointer(&testSnprintfBuf[0]))
	if g, e := Xsnprintf(nil, p, 0, 0, 0), int32(0); g != e {
		t.Fatal(g, e)
	}

	if g, e := testSnprintfBuf, [3]byte{0xff, 0xff, 0xff}; g != e {
		t.Fatal(g, e)
	}

	if g, e := Xsnprintf(nil, p, 0, mustCString(""), 0), int32(0); g != e {
		t.Fatal(g, e)
	}

	if g, e := testSnprintfBuf, [3]byte{0xff, 0xff, 0xff}; g != e {
		t.Fatal(g, e)
	}

	s := mustCString("12")
	if g, e := Xsnprintf(nil, p, 0, s, 0), int32(2); g != e {
		t.Fatal(g, e)
	}

	if g, e := testSnprintfBuf, [3]byte{0xff, 0xff, 0xff}; g != e {
		t.Fatal(g, e)
	}

	if g, e := Xsnprintf(nil, p, 1, s, 0), int32(2); g != e {
		t.Fatal(g, e)
	}

	if g, e := testSnprintfBuf, [3]byte{0x00, 0xff, 0xff}; g != e {
		t.Fatal(g, e)
	}

	testSnprintfBuf = [3]byte{0xff, 0xff, 0xff}
	if g, e := Xsnprintf(nil, p, 2, s, 0), int32(2); g != e {
		t.Fatal(g, e)
	}

	if g, e := testSnprintfBuf, [3]byte{'1', 0x00, 0xff}; g != e {
		t.Fatal(g, e)
	}

	testSnprintfBuf = [3]byte{0xff, 0xff, 0xff}
	if g, e := Xsnprintf(nil, p, 3, s, 0), int32(2); g != e {
		t.Fatal(g, e)
	}

	if g, e := testSnprintfBuf, [3]byte{'1', '2', 0x00}; g != e {
		t.Fatal(g, e)
	}
}

var testFdopenBuf [100]byte

func TestFdopen(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("not implemented on Windows")
	}

	const s = "foobarbaz\n"
	tempdir := t.TempDir()
	f, err := os.Create(filepath.Join(tempdir, "test_fdopen"))
	if err != nil {
		t.Fatal(err)
	}

	if _, err := f.Write([]byte(s)); err != nil {
		t.Fatal(err)
	}

	if _, err := f.Seek(0, os.SEEK_SET); err != nil {
		t.Fatal(err)
	}

	tls := NewTLS()

	defer tls.Close()

	p := Xfdopen(tls, int32(f.Fd()), mustCString("r"))

	bp := uintptr(unsafe.Pointer(&testFdopenBuf))
	if g, e := Xfread(tls, bp, 1, size_t(len(testFdopenBuf)), p), size_t(len(s)); g != e {
		t.Fatal(g, e)
	}

	if g, e := string(GoBytes(bp, len(s))), s; g != e {
		t.Fatalf("%q %q", g, e)
	}
}

func TestSync(t *testing.T) {
	tls := NewTLS()
	X__sync_synchronize(tls)
	tls.Close()
}

func TestProbes(t *testing.T) {
	p := NewPerfCounter([]string{"a", "b", "c"})
	p.Inc(1)
	t.Logf("====\n%s\n----", p)
	c := NewStackCapture(100)
	for i := 0; i < 20; i++ {
		c.Record()
		if i%3 == 0 {
			c.Record()
		}
	}
	t.Logf("====\n%s\n----", c)
}

var _time ctime.Time_t

func TestGmtime(t *testing.T) {
	tls := NewTLS()
	_time = ctime.Time_t(time.Now().Unix())
	p := Xgmtime(tls, uintptr(unsafe.Pointer(&_time)))
	t.Logf("%v %+v", _time, (*ctime.Tm)(unsafe.Pointer(p)))
	tls.Close()
}

func TestBsearch(t *testing.T) {
	tls := NewTLS()
	defer tls.Close()

	data := []int32{10, 20, 30, 40, 50}
	base := uintptr(unsafe.Pointer(&data[0]))
	width := Tsize_t(4)
	nel := Tsize_t(len(data))

	cmp := func(tls *TLS, p1, p2 uintptr) int32 {
		v1 := *(*int32)(unsafe.Pointer(p1))
		v2 := *(*int32)(unsafe.Pointer(p2))
		if v1 < v2 {
			return -1
		}
		if v1 > v2 {
			return 1
		}
		return 0
	}

	cmpPtr := *(*uintptr)(unsafe.Pointer(&struct {
		f func(*TLS, uintptr, uintptr) int32
	}{cmp}))

	// Test finding existing elements
	for _, v := range data {
		key := v
		keyPtr := uintptr(unsafe.Pointer(&key))
		res := Xbsearch(tls, keyPtr, base, nel, width, cmpPtr)
		if res == 0 {
			t.Errorf("bsearch failed to find %d", v)
			continue
		}
		found := *(*int32)(unsafe.Pointer(res))
		if found != v {
			t.Errorf("bsearch found %d, expected %d", found, v)
		}
	}

	// Test non-existing elements
	keys := []int32{5, 25, 55}
	for _, v := range keys {
		key := v
		keyPtr := uintptr(unsafe.Pointer(&key))
		res := Xbsearch(tls, keyPtr, base, nel, width, cmpPtr)
		if res != 0 {
			t.Errorf("bsearch found %d, expected 0", *(*int32)(unsafe.Pointer(res)))
		}
	}
}
