// Copyright 2018 The CUE 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 strings implements simple functions to manipulate UTF-8 encoded
// strings.package strings.
//
// Some of the functions in this package are specifically intended as field
// constraints. For instance, MaxRunes as used in this CUE program
//
//	import "strings"
//
//	myString: strings.MaxRunes(5)
//
// specifies that the myString should be at most 5 code points.
package strings

import (
	"fmt"
	"strings"
	"unicode"
	"unicode/utf8"
)

// ByteAt reports the ith byte of the underlying strings or byte.
func ByteAt(b []byte, i int) (byte, error) {
	if i < 0 || i >= len(b) {
		return 0, fmt.Errorf("index out of range")
	}
	return b[i], nil
}

// ByteSlice reports the bytes of the underlying string data from the start
// index up to but not including the end index.
func ByteSlice(b []byte, start, end int) ([]byte, error) {
	if start < 0 || start > end || end > len(b) {
		return nil, fmt.Errorf("index out of range")
	}
	return b[start:end], nil
}

// Runes returns the Unicode code points of the given string.
func Runes(s string) []rune {
	return []rune(s)
}

// MinRunes reports whether the number of runes (Unicode codepoints) in a string
// is at least a certain minimum. MinRunes can be used a field constraint to
// except all strings for which this property holds.
func MinRunes(s string, min int) bool {
	// TODO: CUE strings cannot be invalid UTF-8. In case this changes, we need
	// to use the following conversion to count properly:
	// s, _ = unicodeenc.UTF8.NewDecoder().String(s)
	return utf8.RuneCountInString(s) >= min
}

// MaxRunes reports whether the number of runes (Unicode codepoints) in a string
// exceeds a certain maximum. MaxRunes can be used a field constraint to
// except all strings for which this property holds
func MaxRunes(s string, max int) bool {
	// See comment in MinRunes implementation.
	return utf8.RuneCountInString(s) <= max
}

// ToTitle returns a copy of the string s with all Unicode letters that begin
// words mapped to their title case.
func ToTitle(s string) string {
	// Use a closure here to remember state.
	// Hackish but effective. Depends on Map scanning in order and calling
	// the closure once per rune.
	prev := ' '
	return strings.Map(
		func(r rune) rune {
			if unicode.IsSpace(prev) {
				prev = r
				return unicode.ToTitle(r)
			}
			prev = r
			return r
		},
		s)
}

// ToCamel returns a copy of the string s with all Unicode letters that begin
// words mapped to lower case.
func ToCamel(s string) string {
	// Use a closure here to remember state.
	// Hackish but effective. Depends on Map scanning in order and calling
	// the closure once per rune.
	prev := ' '
	return strings.Map(
		func(r rune) rune {
			if unicode.IsSpace(prev) {
				prev = r
				return unicode.ToLower(r)
			}
			prev = r
			return r
		},
		s)
}

// SliceRunes returns a string of the underlying string data from the start index
// up to but not including the end index.
func SliceRunes(s string, start, end int) (string, error) {
	runes := []rune(s)
	if start < 0 || start > end || end > len(runes) {
		return "", fmt.Errorf("index out of range")
	}
	return string(runes[start:end]), nil
}
