package log

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

const asciiLimit = 128

type trimmer interface {
	Trim(s string, start, end int) (int, int)
}

func newTrimmer(trimChars string, trimLeft, trimRight bool) (t trimmer, err error) {
	if t, err = newASCIITrimmer(trimChars, trimLeft, trimRight); err == errOnlyASCII {
		t, err = newUTF8Trimmer(trimChars, trimLeft, trimRight)
	}
	return t, err
}

type asciiTrimmer struct {
	chars       [127]byte
	left, right bool
}

var errOnlyASCII = errors.New("only trimming of ASCII characters is supported")

func newASCIITrimmer(trimChars string, trimLeft, trimRight bool) (trimmer, error) {
	t := asciiTrimmer{
		left:  trimLeft,
		right: trimRight,
	}
	for _, chr := range []byte(trimChars) {
		if chr >= asciiLimit {
			return t, errOnlyASCII
		}
		t.chars[chr] = 1
	}
	return t, nil
}

func (t asciiTrimmer) Trim(s string, start, end int) (int, int) {
	if t.left {
		for ; start < end && s[start] < asciiLimit && t.chars[s[start]] != 0; start++ {
		}
	}
	if t.right {
		for ; start < end && s[end-1] < asciiLimit && t.chars[s[end-1]] != 0; end-- {
		}
	}
	return start, end
}

type utf8trimmer struct {
	fn          func(rune) bool
	left, right bool
}

func newUTF8Trimmer(trimChars string, trimLeft, trimRight bool) (trimmer, error) {
	return utf8trimmer{
		// Function that returns true when the rune is not in trimChars.
		fn: func(r rune) bool {
			return !strings.ContainsRune(trimChars, r)
		},
		left:  trimLeft,
		right: trimRight,
	}, nil
}

func (t utf8trimmer) Trim(s string, start, end int) (int, int) {
	if t.left {
		// Find first character not in trimChars.
		pos := strings.IndexFunc(s[start:end], t.fn)
		if pos == -1 {
			return end, end
		}
		start += pos
	}
	if t.right {
		// Find last character not in trimChars.
		pos := strings.LastIndexFunc(s[start:end], t.fn)
		if pos == -1 {
			return start, start
		}
		// End must point to the following character, need to take into account
		// that the last character can be more than 1-byte wide.
		_, width := utf8.DecodeRuneInString(s[start+pos:])
		end = start + pos + width
	}
	return start, end
}
