package bytes

import (
	"bytes"
	"reflect"
	"unsafe"
)

var (
	_ Bytes = &Buffer{}
	_ Bytes = bytesType{}
)

// Bytes defines a standard way of retrieving the content of a
// byte buffer of some-kind.
type Bytes interface {
	// Bytes returns the byte slice content
	Bytes() []byte

	// String returns byte slice cast directly to string, this
	// will cause an allocation but comes with the safety of
	// being an immutable Go string
	String() string

	// StringPtr returns byte slice cast to string via the unsafe
	// package. This comes with the same caveats of accessing via
	// .Bytes() in that the content is liable change and is NOT
	// immutable, despite being a string type
	StringPtr() string
}

type bytesType []byte

func (b bytesType) Bytes() []byte {
	return b
}

func (b bytesType) String() string {
	return string(b)
}

func (b bytesType) StringPtr() string {
	return BytesToString(b)
}

// ToBytes casts the provided byte slice as the simplest possible
// Bytes interface implementation
func ToBytes(b []byte) Bytes {
	return bytesType(b)
}

// Copy returns a new copy of slice b, does NOT maintain nil values
func Copy(b []byte) []byte {
	p := make([]byte, len(b))
	copy(p, b)
	return p
}

// BytesToString returns byte slice cast to string via the "unsafe" package
func BytesToString(b []byte) string {
	return *(*string)(unsafe.Pointer(&b))
}

// StringToBytes returns the string cast to string via the "unsafe" and "reflect" packages
func StringToBytes(s string) []byte {
	// thank you to https://github.com/valyala/fasthttp/blob/master/bytesconv.go
	var b []byte

	// Get byte + string headers
	bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	sh := (*reflect.StringHeader)(unsafe.Pointer(&s))

	// Manually set bytes to string
	bh.Data = sh.Data
	bh.Len = sh.Len
	bh.Cap = sh.Len

	return b
}

// // InsertByte inserts the supplied byte into the slice at provided position
// func InsertByte(b []byte, at int, c byte) []byte {
// 	return append(append(b[:at], c), b[at:]...)
// }

// // Insert inserts the supplied byte slice into the slice at provided position
// func Insert(b []byte, at int, s []byte) []byte {
// 	return append(append(b[:at], s...), b[at:]...)
// }

// ToUpper offers a faster ToUpper implementation using a lookup table
func ToUpper(b []byte) {
	for i := 0; i < len(b); i++ {
		c := &b[i]
		*c = toUpperTable[*c]
	}
}

// ToLower offers a faster ToLower implementation using a lookup table
func ToLower(b []byte) {
	for i := 0; i < len(b); i++ {
		c := &b[i]
		*c = toLowerTable[*c]
	}
}

// HasBytePrefix returns whether b has the provided byte prefix
func HasBytePrefix(b []byte, c byte) bool {
	return (len(b) > 0) && (b[0] == c)
}

// HasByteSuffix returns whether b has the provided byte suffix
func HasByteSuffix(b []byte, c byte) bool {
	return (len(b) > 0) && (b[len(b)-1] == c)
}

// HasBytePrefix returns b without the provided leading byte
func TrimBytePrefix(b []byte, c byte) []byte {
	if HasBytePrefix(b, c) {
		return b[1:]
	}
	return b
}

// TrimByteSuffix returns b without the provided trailing byte
func TrimByteSuffix(b []byte, c byte) []byte {
	if HasByteSuffix(b, c) {
		return b[:len(b)-1]
	}
	return b
}

// Compare is a direct call-through to standard library bytes.Compare()
func Compare(b, s []byte) int {
	return bytes.Compare(b, s)
}

// Contains is a direct call-through to standard library bytes.Contains()
func Contains(b, s []byte) bool {
	return bytes.Contains(b, s)
}

// TrimPrefix is a direct call-through to standard library bytes.TrimPrefix()
func TrimPrefix(b, s []byte) []byte {
	return bytes.TrimPrefix(b, s)
}

// TrimSuffix is a direct call-through to standard library bytes.TrimSuffix()
func TrimSuffix(b, s []byte) []byte {
	return bytes.TrimSuffix(b, s)
}

// Equal is a direct call-through to standard library bytes.Equal()
func Equal(b, s []byte) bool {
	return bytes.Equal(b, s)
}

// EqualFold is a direct call-through to standard library bytes.EqualFold()
func EqualFold(b, s []byte) bool {
	return bytes.EqualFold(b, s)
}

// Fields is a direct call-through to standard library bytes.Fields()
func Fields(b []byte) [][]byte {
	return bytes.Fields(b)
}

// FieldsFunc is a direct call-through to standard library bytes.FieldsFunc()
func FieldsFunc(b []byte, fn func(rune) bool) [][]byte {
	return bytes.FieldsFunc(b, fn)
}

// HasPrefix is a direct call-through to standard library bytes.HasPrefix()
func HasPrefix(b, s []byte) bool {
	return bytes.HasPrefix(b, s)
}

// HasSuffix is a direct call-through to standard library bytes.HasSuffix()
func HasSuffix(b, s []byte) bool {
	return bytes.HasSuffix(b, s)
}

// Index is a direct call-through to standard library bytes.Index()
func Index(b, s []byte) int {
	return bytes.Index(b, s)
}

// IndexByte is a direct call-through to standard library bytes.IndexByte()
func IndexByte(b []byte, c byte) int {
	return bytes.IndexByte(b, c)
}

// IndexAny is a direct call-through to standard library bytes.IndexAny()
func IndexAny(b []byte, s string) int {
	return bytes.IndexAny(b, s)
}

// IndexRune is a direct call-through to standard library bytes.IndexRune()
func IndexRune(b []byte, r rune) int {
	return bytes.IndexRune(b, r)
}

// IndexFunc is a direct call-through to standard library bytes.IndexFunc()
func IndexFunc(b []byte, fn func(rune) bool) int {
	return bytes.IndexFunc(b, fn)
}

// LastIndex is a direct call-through to standard library bytes.LastIndex()
func LastIndex(b, s []byte) int {
	return bytes.LastIndex(b, s)
}

// LastIndexByte is a direct call-through to standard library bytes.LastIndexByte()
func LastIndexByte(b []byte, c byte) int {
	return bytes.LastIndexByte(b, c)
}

// LastIndexAny is a direct call-through to standard library bytes.LastIndexAny()
func LastIndexAny(b []byte, s string) int {
	return bytes.LastIndexAny(b, s)
}

// LastIndexFunc is a direct call-through to standard library bytes.LastIndexFunc()
func LastIndexFunc(b []byte, fn func(rune) bool) int {
	return bytes.LastIndexFunc(b, fn)
}

// Replace is a direct call-through to standard library bytes.Replace()
func Replace(b, s, r []byte, c int) []byte {
	return bytes.Replace(b, s, r, c)
}

// ReplaceAll is a direct call-through to standard library bytes.ReplaceAll()
func ReplaceAll(b, s, r []byte) []byte {
	return bytes.ReplaceAll(b, s, r)
}

// Split is a direct call-through to standard library bytes.Split()
func Split(b, s []byte) [][]byte {
	return bytes.Split(b, s)
}

// SplitAfter is a direct call-through to standard library bytes.SplitAfter()
func SplitAfter(b, s []byte) [][]byte {
	return bytes.SplitAfter(b, s)
}

// SplitN is a direct call-through to standard library bytes.SplitN()
func SplitN(b, s []byte, c int) [][]byte {
	return bytes.SplitN(b, s, c)
}

// SplitAfterN is a direct call-through to standard library bytes.SplitAfterN()
func SplitAfterN(b, s []byte, c int) [][]byte {
	return bytes.SplitAfterN(b, s, c)
}

// NewReader is a direct call-through to standard library bytes.NewReader()
func NewReader(b []byte) *bytes.Reader {
	return bytes.NewReader(b)
}