package nostr

import (
	"encoding/hex"
	"strconv"
	"strings"
	"sync"
	"unsafe"

	jsoniter "github.com/json-iterator/go"
	"golang.org/x/exp/constraints"
)

const MAX_LOCKS = 50

var (
	namedMutexPool = make([]sync.Mutex, MAX_LOCKS)
	json           = jsoniter.ConfigFastest
)

//go:noescape
//go:linkname memhash runtime.memhash
func memhash(p unsafe.Pointer, h, s uintptr) uintptr

func namedLock(name string) (unlock func()) {
	sptr := unsafe.StringData(name)
	idx := uint64(memhash(unsafe.Pointer(sptr), 0, uintptr(len(name)))) % MAX_LOCKS
	namedMutexPool[idx].Lock()
	return namedMutexPool[idx].Unlock
}

func similar[E constraints.Ordered](as, bs []E) bool {
	if len(as) != len(bs) {
		return false
	}

	for _, a := range as {
		for _, b := range bs {
			if b == a {
				goto next
			}
		}
		// didn't find a B that corresponded to the current A
		return false

	next:
		continue
	}

	return true
}

func similarID(as, bs []ID) bool {
	if len(as) != len(bs) {
		return false
	}

	for _, a := range as {
		for _, b := range bs {
			if b == a {
				goto next
			}
		}
		// didn't find a B that corresponded to the current A
		return false

	next:
		continue
	}

	return true
}

func similarPublicKey(as, bs []PubKey) bool {
	if len(as) != len(bs) {
		return false
	}

	for _, a := range as {
		for _, b := range bs {
			if b == a {
				goto next
			}
		}
		// didn't find a B that corresponded to the current A
		return false

	next:
		continue
	}

	return true
}

// Escaping strings for JSON encoding according to RFC8259.
// Also encloses result in quotation marks "".
func escapeString(dst []byte, s string) []byte {
	dst = append(dst, '"')
	for i := 0; i < len(s); i++ {
		c := s[i]
		switch {
		case c == '"':
			// quotation mark
			dst = append(dst, []byte{'\\', '"'}...)
		case c == '\\':
			// reverse solidus
			dst = append(dst, []byte{'\\', '\\'}...)
		case c >= 0x20:
			// default, rest below are control chars
			dst = append(dst, c)
		case c == 0x08:
			dst = append(dst, []byte{'\\', 'b'}...)
		case c < 0x09:
			dst = append(dst, []byte{'\\', 'u', '0', '0', '0', '0' + c}...)
		case c == 0x09:
			dst = append(dst, []byte{'\\', 't'}...)
		case c == 0x0a:
			dst = append(dst, []byte{'\\', 'n'}...)
		case c == 0x0c:
			dst = append(dst, []byte{'\\', 'f'}...)
		case c == 0x0d:
			dst = append(dst, []byte{'\\', 'r'}...)
		case c < 0x10:
			dst = append(dst, []byte{'\\', 'u', '0', '0', '0', 0x57 + c}...)
		case c < 0x1a:
			dst = append(dst, []byte{'\\', 'u', '0', '0', '1', 0x20 + c}...)
		case c < 0x20:
			dst = append(dst, []byte{'\\', 'u', '0', '0', '1', 0x47 + c}...)
		}
	}
	dst = append(dst, '"')
	return dst
}

func subIdToSerial(subId string) int64 {
	n := strings.Index(subId, ":")
	if n < 0 || n > len(subId) {
		return -1
	}
	serialId, _ := strconv.ParseInt(subId[0:n], 10, 64)
	return serialId
}

func isLowerHex(thing string) bool {
	for _, charNumber := range thing {
		if (charNumber >= 48 && charNumber <= 57) || (charNumber >= 97 && charNumber <= 102) {
			continue
		}
		return false
	}
	return true
}

func extractSubID(jsonStr string) string {
	// look for "EVENT" pattern
	start := strings.Index(jsonStr, `"EVENT"`)
	if start == -1 {
		return ""
	}

	// move to the next quote
	offset := strings.Index(jsonStr[start+7:], `"`)
	if offset == -1 {
		return ""
	}

	start += 7 + offset + 1

	// find the ending quote
	end := strings.Index(jsonStr[start:], `"`)

	// get the contents
	return jsonStr[start : start+end]
}

func extractEventID(jsonStr string) ID {
	// look for "id" pattern
	start := strings.Index(jsonStr, `"id"`)
	if start == -1 {
		return [32]byte{}
	}

	// move to the next quote
	offset := strings.IndexByte(jsonStr[start+4:], '"')
	start += 4 + offset + 1

	// get 64 characters of the id
	var id [32]byte
	hex.Decode(id[:], unsafe.Slice(unsafe.StringData(jsonStr[start:start+64]), 64))
	return id
}

func extractEventPubKey(jsonStr string) PubKey {
	// look for "pubkey" pattern
	start := strings.Index(jsonStr, `"pubkey"`)
	if start == -1 {
		return PubKey{}
	}

	// move to the next quote
	offset := strings.IndexByte(jsonStr[start+8:], '"')
	start += 8 + offset + 1

	// get 64 characters of the pubkey
	var pk [32]byte
	hex.Decode(pk[:], unsafe.Slice(unsafe.StringData(jsonStr[start:start+64]), 64))
	return pk
}

func extractDTag(jsonStr string) string {
	// look for ["d", pattern
	start := strings.Index(jsonStr, `["d"`)
	if start == -1 {
		return ""
	}

	// move to the next quote
	offset := strings.IndexByte(jsonStr[start+4:], '"')
	start += 4 + offset + 1

	// find the ending quote
	end := strings.IndexByte(jsonStr[start:], '"')
	if end == -1 {
		return ""
	}

	// get the contents
	return jsonStr[start : start+end]
}

func extractTimestamp(jsonStr string) Timestamp {
	// look for "created_at": pattern
	start := strings.Index(jsonStr, `"created_at"`)
	if start == -1 {
		return 0
	}

	// move to the next number
	offset := strings.IndexAny(jsonStr[start+12:], "9876543210")
	if offset == -1 {
		return 0
	}
	start += 12 + offset

	// find the end
	end := strings.IndexAny(jsonStr[start:], ",} ")
	if end == -1 {
		return 0
	}

	// get the contents
	ts, _ := strconv.ParseInt(jsonStr[start:start+end], 10, 64)
	return Timestamp(ts)
}
