package syntax

import 

type graphemeBreakClass uint8

const (
	graphemeOther graphemeBreakClass = iota
	graphemeCR
	graphemeLF
	graphemeControl
	graphemeExtend
	graphemeZWJ
	graphemeRegionalIndicator
	graphemePrepend
	graphemeSpacingMark
	graphemeL
	graphemeV
	graphemeT
	graphemeLV
	graphemeLVT
)

type indicConjunctBreakClass uint8

const (
	indicNone indicConjunctBreakClass = iota
	indicConsonant
	indicExtend
	indicLinker
)

type graphemeProperties struct {
	breakClass           graphemeBreakClass
	indicClass           indicConjunctBreakClass
	extendedPictographic bool
}

// NextGraphemeClusterBoundary returns the first extended-grapheme boundary
// after start, or -1 when start is not a valid input position. It implements
// the Unicode 17 rules from UAX #29 with a small state machine, avoiding the
// backtracking and repeated character-class probes of an equivalent regexp.
func ( []rune,  int) int {
	if  < 0 ||  >= len() {
		return -1
	}

	 := graphemePropertiesFor([])
	 := graphemeForwardState{}
	.consume()

	for  :=  + 1;  < len(); ++ {
		 := graphemePropertiesFor([])
		if isGraphemeBoundaryForward(.breakClass, , ) {
			return 
		}
		.consume()
		 = 
	}
	return len()
}

// PreviousGraphemeClusterBoundary returns the first extended-grapheme boundary
// before end, or -1 when end is not a valid input position.
func ( []rune,  int) int {
	if  <= 0 ||  > len() {
		return -1
	}
	for  :=  - 1;  > 0; -- {
		if isGraphemeBoundary(, ) {
			return 
		}
	}
	return 0
}

type graphemeForwardState struct {
	regionalIndicatorCount  int
	extendedPictographicRun bool
	zwjAfterPictographic    bool
	indicConsonantRun       bool
	indicLinkerSeen         bool
}

func ( *graphemeForwardState) ( graphemeProperties) {
	if .breakClass == graphemeRegionalIndicator {
		.regionalIndicatorCount++
	} else {
		.regionalIndicatorCount = 0
	}

	if .breakClass == graphemeZWJ {
		.zwjAfterPictographic = .extendedPictographicRun
	} else {
		.zwjAfterPictographic = false
	}
	if .extendedPictographic {
		.extendedPictographicRun = true
	} else if .breakClass != graphemeExtend {
		.extendedPictographicRun = false
	}

	switch .indicClass {
	case indicConsonant:
		.indicConsonantRun = true
		.indicLinkerSeen = false
	case indicExtend:
		// Extend preserves a preceding consonant/linker run.
	case indicLinker:
		if .indicConsonantRun {
			.indicLinkerSeen = true
		}
	default:
		.indicConsonantRun = false
		.indicLinkerSeen = false
	}
}

func isGraphemeBoundaryForward( graphemeBreakClass,  graphemeProperties,  graphemeForwardState) bool {
	if  == graphemeCR && .breakClass == graphemeLF { // GB3
		return false
	}
	if isGraphemeControl() || isGraphemeControl(.breakClass) { // GB4, GB5
		return true
	}
	if hangulNoBreak(, .breakClass) { // GB6, GB7, GB8
		return false
	}
	if .breakClass == graphemeExtend || .breakClass == graphemeZWJ || .breakClass == graphemeSpacingMark { // GB9, GB9a
		return false
	}
	if  == graphemePrepend { // GB9b
		return false
	}
	if .indicClass == indicConsonant && .indicConsonantRun && .indicLinkerSeen { // GB9c
		return false
	}
	if .extendedPictographic && .zwjAfterPictographic { // GB11
		return false
	}
	if  == graphemeRegionalIndicator && .breakClass == graphemeRegionalIndicator && .regionalIndicatorCount%2 == 1 { // GB12, GB13
		return false
	}
	return true
}

func isGraphemeBoundary( []rune,  int) bool {
	 := graphemePropertiesFor([-1])
	 := graphemePropertiesFor([])
	if .breakClass == graphemeCR && .breakClass == graphemeLF { // GB3
		return false
	}
	if isGraphemeControl(.breakClass) || isGraphemeControl(.breakClass) { // GB4, GB5
		return true
	}
	if hangulNoBreak(.breakClass, .breakClass) { // GB6, GB7, GB8
		return false
	}
	if .breakClass == graphemeExtend || .breakClass == graphemeZWJ || .breakClass == graphemeSpacingMark { // GB9, GB9a
		return false
	}
	if .breakClass == graphemePrepend { // GB9b
		return false
	}
	if .indicClass == indicConsonant && hasIndicConjunctBefore(, ) { // GB9c
		return false
	}
	if .extendedPictographic && hasExtendedPictographicZWJBefore(, ) { // GB11
		return false
	}
	if .breakClass == graphemeRegionalIndicator && .breakClass == graphemeRegionalIndicator { // GB12, GB13
		 := 0
		for  :=  - 1;  >= 0 && graphemeClass([]) == graphemeRegionalIndicator; -- {
			++
		}
		return %2 == 0
	}
	return true
}

func hasIndicConjunctBefore( []rune,  int) bool {
	 := false
	for  :=  - 1;  >= 0; -- {
		switch indicClass([]) {
		case indicExtend:
			continue
		case indicLinker:
			 = true
			continue
		case indicConsonant:
			return 
		default:
			return false
		}
	}
	return false
}

func hasExtendedPictographicZWJBefore( []rune,  int) bool {
	 :=  - 1
	if  < 0 || graphemeClass([]) != graphemeZWJ {
		return false
	}
	for --;  >= 0 && graphemeClass([]) == graphemeExtend; -- {
	}
	return  >= 0 && unicode.Is(unicodeAliasExtended_Pictographic, [])
}

func isGraphemeControl( graphemeBreakClass) bool {
	return  == graphemeCR ||  == graphemeLF ||  == graphemeControl
}

func hangulNoBreak(,  graphemeBreakClass) bool {
	return  == graphemeL && ( == graphemeL ||  == graphemeV ||  == graphemeLV ||  == graphemeLVT) ||
		( == graphemeLV ||  == graphemeV) && ( == graphemeV ||  == graphemeT) ||
		( == graphemeLVT ||  == graphemeT) &&  == graphemeT
}

func indicClass( rune) indicConjunctBreakClass {
	if  < 0x300 {
		return indicNone
	}
	if unicode.Is(unicodeAliasIndic_Conjunct_Break_Consonant, ) {
		return indicConsonant
	}
	if unicode.Is(unicodeAliasIndic_Conjunct_Break_Linker, ) {
		return indicLinker
	}
	if unicode.Is(unicodeAliasIndic_Conjunct_Break_Extend, ) {
		return indicExtend
	}
	return indicNone
}

func graphemePropertiesFor( rune) graphemeProperties {
	if  <= unicode.MaxASCII {
		return graphemeProperties{breakClass: graphemeClass()}
	}
	return graphemeProperties{
		breakClass:           graphemeClass(),
		indicClass:           indicClass(),
		extendedPictographic:  >= 0xA9 && unicode.Is(unicodeAliasExtended_Pictographic, ),
	}
}

func graphemeClass( rune) graphemeBreakClass {
	if  <= unicode.MaxASCII {
		switch  {
		case '\r':
			return graphemeCR
		case '\n':
			return graphemeLF
		default:
			if  < ' ' ||  == 0x7F {
				return graphemeControl
			}
			return graphemeOther
		}
	}
	if  == 0x200D {
		return graphemeZWJ
	}
	if  >= 0x1F1E6 &&  <= 0x1F1FF {
		return graphemeRegionalIndicator
	}
	if  >= 0xAC00 &&  <= 0xD7A3 {
		if (-0xAC00)%28 == 0 {
			return graphemeLV
		}
		return graphemeLVT
	}
	if  >= 0x1100 &&  <= 0x115F ||  >= 0xA960 &&  <= 0xA97C {
		return graphemeL
	}
	if  >= 0x1160 &&  <= 0x11A7 ||  >= 0xD7B0 &&  <= 0xD7C6 {
		return graphemeV
	}
	if  >= 0x11A8 &&  <= 0x11FF ||  >= 0xD7CB &&  <= 0xD7FB {
		return graphemeT
	}
	if unicode.Is(unicodeAliasGrapheme_Cluster_Break_Control, ) {
		return graphemeControl
	}
	if unicode.Is(unicodeAliasGrapheme_Cluster_Break_Extend, ) {
		return graphemeExtend
	}
	if unicode.Is(unicodeAliasGrapheme_Cluster_Break_Prepend, ) {
		return graphemePrepend
	}
	if unicode.Is(unicodeAliasGrapheme_Cluster_Break_SpacingMark, ) {
		return graphemeSpacingMark
	}
	return graphemeOther
}