// Copyright 2015 go-swagger maintainers
//
// 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 mangling

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

type (
	lexemKind uint8

	nameLexem struct {
		original          string
		matchedInitialism string
		kind              lexemKind
	}
)

const (
	lexemKindCasualName lexemKind = iota
	lexemKindInitialismName
)

func newInitialismNameLexem(original, matchedInitialism string) nameLexem {
	return nameLexem{
		kind:              lexemKindInitialismName,
		original:          original,
		matchedInitialism: matchedInitialism,
	}
}

func newCasualNameLexem(original string) nameLexem {
	return nameLexem{
		kind:     lexemKindCasualName,
		original: trim(original), // TODO: save on calls to trim
	}
}

// WriteTitleized writes the titleized lexeme to a bytes.Buffer.
//
// If the first letter cannot be capitalized, it doesn't write anything and return false,
// so the caller may attempt some workaround strategy.
func (l nameLexem) WriteTitleized(w *bytes.Buffer, alwaysUpper bool) bool {
	if l.kind == lexemKindInitialismName {
		w.WriteString(l.matchedInitialism)

		return true
	}

	if len(l.original) == 0 {
		return true
	}

	if len(l.original) == 1 {
		// identifier is too short: casing will depend on the context
		firstByte := l.original[0]
		switch {
		case 'A' <= firstByte && firstByte <= 'Z':
			// safe
			w.WriteByte(firstByte)

			return true
		case alwaysUpper && 'a' <= firstByte && firstByte <= 'z':
			w.WriteByte(firstByte - 'a' + 'A')

			return true
		default:

			// not a letter: skip and let the caller decide
			return false
		}
	}

	if firstByte := l.original[0]; firstByte < utf8.RuneSelf {
		// ASCII
		switch {
		case 'A' <= firstByte && firstByte <= 'Z':
			// already an upper case letter
			w.WriteString(l.original)

			return true
		case 'a' <= firstByte && firstByte <= 'z':
			w.WriteByte(firstByte - 'a' + 'A')
			w.WriteString(l.original[1:])

			return true
		default:
			// not a good candidate: doesn't start with a letter
			return false
		}
	}

	// unicode
	firstRune, idx := utf8.DecodeRuneInString(l.original)
	if !unicode.IsLetter(firstRune) || !unicode.IsUpper(unicode.ToUpper(firstRune)) {
		// not a good candidate: doesn't start with a letter
		// or a rune for which case doesn't make sense (e.g. East-Asian runes etc)
		return false
	}

	rest := l.original[idx:]
	w.WriteRune(unicode.ToUpper(firstRune))
	w.WriteString(strings.ToLower(rest))

	return true
}

// WriteLower is like write titleized but it writes a lower-case version of the lexeme.
//
// Similarly, there is no writing if the casing of the first rune doesn't make sense.
func (l nameLexem) WriteLower(w *bytes.Buffer, alwaysLower bool) bool {
	if l.kind == lexemKindInitialismName {
		w.WriteString(lower(l.matchedInitialism))

		return true
	}

	if len(l.original) == 0 {
		return true
	}

	if len(l.original) == 1 {
		// identifier is too short: casing will depend on the context
		firstByte := l.original[0]
		switch {
		case 'a' <= firstByte && firstByte <= 'z':
			// safe
			w.WriteByte(firstByte)

			return true
		case alwaysLower && 'A' <= firstByte && firstByte <= 'Z':
			w.WriteByte(firstByte - 'A' + 'a')

			return true
		default:

			// not a letter: skip and let the caller decide
			return false
		}
	}

	if firstByte := l.original[0]; firstByte < utf8.RuneSelf {
		// ASCII
		switch {
		case 'a' <= firstByte && firstByte <= 'z':
			// already a lower case letter
			w.WriteString(l.original)

			return true
		case 'A' <= firstByte && firstByte <= 'Z':
			w.WriteByte(firstByte - 'A' + 'a')
			w.WriteString(l.original[1:])

			return true
		default:
			// not a good candidate: doesn't start with a letter
			return false
		}
	}

	// unicode
	firstRune, idx := utf8.DecodeRuneInString(l.original)
	if !unicode.IsLetter(firstRune) || !unicode.IsLower(unicode.ToLower(firstRune)) {
		// not a good candidate: doesn't start with a letter
		// or a rune for which case doesn't make sense (e.g. East-Asian runes etc)
		return false
	}

	rest := l.original[idx:]
	w.WriteRune(unicode.ToLower(firstRune))
	w.WriteString(rest)

	return true
}

func (l nameLexem) GetOriginal() string {
	return l.original
}

func (l nameLexem) IsInitialism() bool {
	return l.kind == lexemKindInitialismName
}
