// Copyright 2022 The Go 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 goexperiment.jsonv2

package json

import (
	"encoding/binary"
	"math/bits"
)

// stringCache is a cache for strings converted from a []byte.
type stringCache = [256]string // 256*unsafe.Sizeof(string("")) => 4KiB

// makeString returns the string form of b.
// It returns a pre-allocated string from c if present, otherwise
// it allocates a new string, inserts it into the cache, and returns it.
func makeString(c *stringCache, b []byte) string {
	const (
		minCachedLen = 2   // single byte strings are already interned by the runtime
		maxCachedLen = 256 // large enough for UUIDs, IPv6 addresses, SHA-256 checksums, etc.
	)
	if c == nil || len(b) < minCachedLen || len(b) > maxCachedLen {
		return string(b)
	}

	// Compute a hash from the fixed-width prefix and suffix of the string.
	// This ensures hashing a string is a constant time operation.
	var h uint32
	switch {
	case len(b) >= 8:
		lo := binary.LittleEndian.Uint64(b[:8])
		hi := binary.LittleEndian.Uint64(b[len(b)-8:])
		h = hash64(uint32(lo), uint32(lo>>32)) ^ hash64(uint32(hi), uint32(hi>>32))
	case len(b) >= 4:
		lo := binary.LittleEndian.Uint32(b[:4])
		hi := binary.LittleEndian.Uint32(b[len(b)-4:])
		h = hash64(lo, hi)
	case len(b) >= 2:
		lo := binary.LittleEndian.Uint16(b[:2])
		hi := binary.LittleEndian.Uint16(b[len(b)-2:])
		h = hash64(uint32(lo), uint32(hi))
	}

	// Check the cache for the string.
	i := h % uint32(len(*c))
	if s := (*c)[i]; s == string(b) {
		return s
	}
	s := string(b)
	(*c)[i] = s
	return s
}

// hash64 returns the hash of two uint32s as a single uint32.
func hash64(lo, hi uint32) uint32 {
	// If avalanche=true, this is identical to XXH32 hash on an 8B string:
	//	var b [8]byte
	//	binary.LittleEndian.PutUint32(b[:4], lo)
	//	binary.LittleEndian.PutUint32(b[4:], hi)
	//	return xxhash.Sum32(b[:])
	const (
		prime1 = 0x9e3779b1
		prime2 = 0x85ebca77
		prime3 = 0xc2b2ae3d
		prime4 = 0x27d4eb2f
		prime5 = 0x165667b1
	)
	h := prime5 + uint32(8)
	h += lo * prime3
	h = bits.RotateLeft32(h, 17) * prime4
	h += hi * prime3
	h = bits.RotateLeft32(h, 17) * prime4
	// Skip final mix (avalanche) step of XXH32 for performance reasons.
	// Empirical testing shows that the improvements in unbiased distribution
	// do not outweigh the extra cost in computational complexity.
	const avalanche = false
	if avalanche {
		h ^= h >> 15
		h *= prime2
		h ^= h >> 13
		h *= prime3
		h ^= h >> 16
	}
	return h
}
