// SPDX-License-Identifier: Unlicense OR BSD-3-Clause

package segmenter

import (
	ucd 
)

// Apply the Line Breaking Rules and returns the computed break opportunity
// See https://unicode.org/reports/tr14/#BreakingRules
func ( *cursor) () breakOpportunity {
	// start by attributing the break class for the current rune
	.ruleLB1()

	 := .updateNumSequence()

	, , ,  := .prevPrevLine, .prevLine, .line, .nextLine

	// LB4 and LB5
	// BK !
	// CR !
	// LF !
	// NL !
	// (CR × LF is actually handled in rule LB6)
	if &(ucd.LB_BK|ucd.LB_LF|ucd.LB_NL) != 0 ||
		( == ucd.LB_CR && .r != '\n') {
		return breakMandatory
	}

	// LB6 : × ( BK | CR | LF | NL )
	// LB7
	// × SP
	// × ZW
	if &(ucd.LB_BK|ucd.LB_CR|ucd.LB_LF|ucd.LB_NL|ucd.LB_SP|ucd.LB_ZW) != 0 {
		return breakProhibited
	}

	// LB 8
	// there is a catch here : prevLine or beforeSpace are not always
	// computed at index i-1, because of rules LB9 and LB10
	// however, rule LB8 and LB8a applies before LB9 and LB10, meaning
	// we need to use the real class
	if .beforeSpaceLineRaw == ucd.LB_ZW { // rule LB8 : ZW SP* ÷
		return breakAllowed
	} else if .prevLineRaw == ucd.LB_ZWJ { // rule LB8a : ZWJ ×
		return breakProhibited
	}

	// rule LB9 : "Do not break a combining character sequence"
	// where X is any line break class except BK, CR, LF, NL, SP, or ZW.
	// see also [endIteration]
	if &(ucd.LB_CM|ucd.LB_ZWJ) != 0 && &(ucd.LB_BK|ucd.LB_CR|ucd.LB_LF|ucd.LB_NL|ucd.LB_SP|ucd.LB_ZW) == 0 {
		return breakProhibited
	}

	// LB11
	// × WJ
	// WJ ×
	if  == ucd.LB_WJ ||  == ucd.LB_WJ {
		return breakProhibited
	}

	// LB12 : GL ×
	// LB12a : [^SP BA HY HH] × GL
	if  == ucd.LB_GL ||
		&(ucd.LB_SP|ucd.LB_BA|ucd.LB_HY|ucd.LB_HH) == 0 &&  == ucd.LB_GL {
		return breakProhibited
	}

	// rule LB13
	// × CL
	// × CP
	// × EX
	// × SY
	if &(ucd.LB_CL|ucd.LB_CP|ucd.LB_EX|ucd.LB_SY) != 0 {
		return breakProhibited
	}

	 := .beforeSpacesLine

	// LB14 : OP SP* ×
	if  == ucd.LB_OP {
		return breakProhibited
	}

	// LB15a Do not break after an unresolved initial punctuation that lies at the start of the line, after a space, after opening punctuation, or after an unresolved quotation mark, even after spaces.
	// (sot | BK | CR | LF | NL | OP | QU | GL | SP | ZW) [\p{Pi}&QU] SP* ×
	 := .prevBeforeSpacesLine
	if (.beforeSpacesIndex == 0 || &(ucd.LB_BK|ucd.LB_CR|ucd.LB_LF|ucd.LB_NL|ucd.LB_OP|ucd.LB_QU|ucd.LB_GL|ucd.LB_SP|ucd.LB_ZW) != 0) &&
		(ucd.LookupGeneralCategory(.beforeSpaces) == ucd.Pi &&  == ucd.LB_QU) {
		return breakProhibited
	}
	// LB15b Do not break before an unresolved final punctuation that lies at the end of the line, before a space, before a prohibited break, or before an unresolved quotation mark, even after spaces.
	// × [\p{Pf}&QU] ( SP | GL | WJ | CL | QU | CP | EX | IS | SY | BK | CR | LF | NL | ZW | eot)
	if (.generalCategory == ucd.Pf &&  == ucd.LB_QU) && (&(ucd.LB_SP|ucd.LB_GL|ucd.LB_WJ|ucd.LB_CL|ucd.LB_QU|ucd.LB_CP|ucd.LB_EX|ucd.LB_IS|ucd.LB_SY|ucd.LB_BK|ucd.LB_CR|ucd.LB_LF|ucd.LB_NL|ucd.LB_ZW) != 0 || .index == .len-1) {
		return breakProhibited
	} else if  == ucd.LB_SP &&  == ucd.LB_IS &&  == ucd.LB_NU {
		// LB15c Break before a decimal mark that follows a space, for instance, in ‘subtract .5’.
		// SP ÷ IS NU
		return breakAllowed
	} else if  == ucd.LB_IS {
		// LB15d Otherwise, do not break before ‘;’, ‘,’, or ‘.’, even after spaces.
		// × IS
		return breakProhibited
	}

	// LB16 : (CL | CP) SP* × NS
	if &(ucd.LB_CL|ucd.LB_CP) != 0 &&  == ucd.LB_NS {
		return breakProhibited
	}

	// LB17 : B2 SP* × B2
	if  == ucd.LB_B2 &&  == ucd.LB_B2 {
		return breakProhibited
	}

	// LB18 : SP ÷
	if  == ucd.LB_SP {
		return breakAllowed
	}

	// LB19
	// × [ QU - \p{Pi} ]
	// [ QU - \p{Pf} ] ×
	if ( == ucd.LB_QU && .generalCategory != ucd.Pi) || ( == ucd.LB_QU && .prevGeneralCategory != ucd.Pf) {
		return breakProhibited
	}
	// LB 19a
	// [^$EastAsian] × QU
	// × QU ( [^$EastAsian] | eot )
	// QU × [^$EastAsian]
	// ( sot | [^$EastAsian] ) QU ×
	if ( == ucd.LB_QU && !ucd.IsLargeEastAsian(.prev)) ||
		( == ucd.LB_QU && (.index == .len-1 || !ucd.IsLargeEastAsian(.next))) ||
		( == ucd.LB_QU && !ucd.IsLargeEastAsian(.r)) ||
		((.isPreviousSot || !ucd.IsLargeEastAsian(.prevPrev)) &&  == ucd.LB_QU) {
		return breakProhibited
	}

	// LB20
	// ÷ CB
	// CB ÷
	if  == ucd.LB_CB ||  == ucd.LB_CB {
		return breakAllowed
	}
	// LB20a Do not break after a word-initial hyphen.
	// ( sot | BK | CR | LF | NL | SP | ZW | CB | GL ) ( HY | HH ) × ( AL | HL )
	if (.isPreviousSot || &(ucd.LB_BK|ucd.LB_CR|ucd.LB_LF|ucd.LB_NL|ucd.LB_SP|ucd.LB_ZW|ucd.LB_CB|ucd.LB_GL) != 0) &&
		&(ucd.LB_HY|ucd.LB_HH) != 0 && &(ucd.LB_AL|ucd.LB_HL) != 0 {
		return breakProhibited
	}

	// LB21
	// × BA
	// × HH
	// × HY
	// × NS
	// BB ×
	if &(ucd.LB_BA|ucd.LB_HH|ucd.LB_HY|ucd.LB_NS) != 0 ||  == ucd.LB_BB {
		return breakProhibited
	}
	// LB21a : HL (HY | HH) × [^HL]
	if .prevPrevLine == ucd.LB_HL && &(ucd.LB_HY|ucd.LB_HH) != 0 &&  != ucd.LB_HL {
		return breakProhibited
	}
	// LB21b : SY × HL
	if  == ucd.LB_SY &&  == ucd.LB_HL {
		return breakProhibited
	}

	// LB22 : × IN
	if  == ucd.LB_IN {
		return breakProhibited
	}

	// LB23
	// (AL | HL) × NU
	if &(ucd.LB_AL|ucd.LB_HL) != 0 &&  == ucd.LB_NU {
		return breakProhibited
	}
	// NU × (AL | HL)
	if  == ucd.LB_NU && &(ucd.LB_AL|ucd.LB_HL) != 0 {
		return breakProhibited
	}
	// LB23a
	// PR × (ID | EB | EM)
	if  == ucd.LB_PR && &(ucd.LB_ID|ucd.LB_EB|ucd.LB_EM) != 0 {
		return breakProhibited
	}
	// (ID | EB | EM) × PO
	if &(ucd.LB_ID|ucd.LB_EB|ucd.LB_EM) != 0 &&  == ucd.LB_PO {
		return breakProhibited
	}

	// LB24
	// (PR | PO) × (AL | HL)
	if &(ucd.LB_PR|ucd.LB_PO) != 0 && &(ucd.LB_AL|ucd.LB_HL) != 0 {
		return breakProhibited
	}
	// (AL | HL) × (PR | PO)
	if &(ucd.LB_AL|ucd.LB_HL) != 0 && &(ucd.LB_PR|ucd.LB_PO) != 0 {
		return breakProhibited
	}

	// LB25
	// (PR | PO) × ( OP | HY )? NU
	if &(ucd.LB_PR|ucd.LB_PO) != 0 && ( == ucd.LB_NU ||
		&(ucd.LB_OP|ucd.LB_HY) != 0 && .nextLine == ucd.LB_NU) {
		return breakProhibited
	}
	// ( OP | HY | IS ) × NU
	if &(ucd.LB_OP|ucd.LB_HY|ucd.LB_IS) != 0 &&  == ucd.LB_NU {
		return breakProhibited
	}
	// NU × (NU | SY | IS)
	if  == ucd.LB_NU && &(ucd.LB_NU|ucd.LB_SY|ucd.LB_IS) != 0 {
		return breakProhibited
	}
	// NU (NU | SY | IS)* × (NU | SY | IS | CL | CP )
	if  {
		return breakProhibited
	}

	// LB26
	// JL × (JL | JV | H2 | H3)
	if  == ucd.LB_JL && &(ucd.LB_JL|ucd.LB_JV|ucd.LB_H2|ucd.LB_H3) != 0 {
		return breakProhibited
	}
	// (JV | H2) × (JV | JT)
	if &(ucd.LB_JV|ucd.LB_H2) != 0 && &(ucd.LB_JV|ucd.LB_JT) != 0 {
		return breakProhibited
	}
	// (JT | H3) × JT
	if &(ucd.LB_JT|ucd.LB_H3) != 0 &&  == ucd.LB_JT {
		return breakProhibited
	}

	// LB27
	// (JL | JV | JT | H2 | H3) × PO
	if &(ucd.LB_JL|ucd.LB_JV|ucd.LB_JT|ucd.LB_H2|ucd.LB_H3) != 0 &&  == ucd.LB_PO {
		return breakProhibited
	}
	// PR × (JL | JV | JT | H2 | H3)
	if  == ucd.LB_PR && &(ucd.LB_JL|ucd.LB_JV|ucd.LB_JT|ucd.LB_H2|ucd.LB_H3) != 0 {
		return breakProhibited
	}

	// LB28a Do not break inside the orthographic syllables of Brahmic scripts.
	// AP × (AK | [◌] | AS)
	if  == ucd.LB_AP && (.r == 0x25CC || &(ucd.LB_AK|ucd.LB_AS) != 0) ||
		// (AK | [◌] | AS) × (VF | VI)
		(.isPrevDottedCircle || &(ucd.LB_AK|ucd.LB_AS) != 0) && &(ucd.LB_VF|ucd.LB_VI) != 0 ||
		// (AK | [◌] | AS) VI × (AK | [◌])
		(.isPrevPrevDottedCircle || &(ucd.LB_AK|ucd.LB_AS) != 0) &&  == ucd.LB_VI && ( == ucd.LB_AK || .r == 0x25CC) ||
		// (AK | [◌] | AS) × (AK | [◌] | AS) VF
		(.isPrevDottedCircle || &(ucd.LB_AK|ucd.LB_AS) != 0) && (.r == 0x25CC || &(ucd.LB_AK|ucd.LB_AS) != 0) &&  == ucd.LB_VF {
		return breakProhibited
	}
	// LB28 : (AL | HL) × (AL | HL)
	if &(ucd.LB_AL|ucd.LB_HL) != 0 && &(ucd.LB_AL|ucd.LB_HL) != 0 {
		return breakProhibited
	}

	// LB29 : IS × (AL | HL)
	if  == ucd.LB_IS && &(ucd.LB_AL|ucd.LB_HL) != 0 {
		return breakProhibited
	}

	// LB30a
	// (RI RI)* RI × RI
	if .isPrevLinebreakRIOdd && .line == ucd.LB_RI {
		return breakProhibited
	}

	// LB30b
	// EB × EM
	if .prevLine == ucd.LB_EB && .line == ucd.LB_EM {
		return breakProhibited
	}
	// [\p{Extended_Pictographic}&\p{Cn}] × EM
	if .isPrevNonAssignedExtendedPic && .line == ucd.LB_EM {
		return breakProhibited
	}

	// LB 30
	// (AL | HL | NU) × [OP-[\p{ea=F}\p{ea=W}\p{ea=H}]]
	if .prevLine&(ucd.LB_AL|ucd.LB_HL|ucd.LB_NU) != 0 &&
		.line == ucd.LB_OP && !ucd.IsLargeEastAsian(.r) {
		return breakProhibited
	}
	// [CP-[\p{ea=F}\p{ea=W}\p{ea=H}]] × (AL | HL | NU)
	if .prevLine == ucd.LB_CP && !ucd.IsLargeEastAsian(.prev) &&
		.line&(ucd.LB_AL|ucd.LB_HL|ucd.LB_NU) != 0 {
		return breakProhibited
	}

	return breakEmpty
}

// breakOpportunity is a convenient enum,
// mapped to the LineBreak and MandatoryBreak properties,
// avoiding too many bit operations
type breakOpportunity uint8

const (
	breakEmpty      breakOpportunity = iota // not specified
	breakProhibited                         // no break
	breakAllowed                            // direct break (can always break here)
	breakMandatory                          // break is mandatory (implies breakAllowed)
)

// apply rule LB1 to resolve break classses AI, SG, XX, SA and CJ.
// We use the default values specified in https://unicode.org/reports/tr14/#BreakingRules.
func ( *cursor) () {
	switch .line {
	case ucd.LB_AI, ucd.LB_SG, 0:
		.line = ucd.LB_AL
	case ucd.LB_SA:
		if .generalCategory == ucd.Mn || .generalCategory == ucd.Mc {
			.line = ucd.LB_CM
		} else {
			.line = ucd.LB_AL
		}
	case ucd.LB_CJ:
		.line = ucd.LB_NS
	}
}

type numSequenceState uint8

const (
	noNumSequence numSequenceState = iota // we are not in a sequence
	inNumSequence                         // we are in NU (NU | SY | IS)*
	seenCloseNum                          // we are at NU (NU | SY | IS)* (CL | CP)?
)

// update the `numSequence` state used for rule LB25
// and returns true if we matched one
func ( *cursor) () bool {
	// note that rule LB9 also apply : (CM|ZWJ) do not change
	// the flag
	if .line&(ucd.LB_CM|ucd.LB_ZWJ) != 0 {
		return false
	}

	switch .numSequence {
	case noNumSequence:
		if .line == ucd.LB_NU { // start a sequence
			.numSequence = inNumSequence
		}
		return false
	case inNumSequence:
		switch .line {
		case ucd.LB_NU, ucd.LB_SY, ucd.LB_IS:
			// NU (NU | SY | IS)* × (NU | SY | IS) : the sequence continue
			return true
		case ucd.LB_CL, ucd.LB_CP:
			// NU (NU | SY | IS)* × (CL | CP)
			.numSequence = seenCloseNum
			return true
		case ucd.LB_PO, ucd.LB_PR:
			// NU (NU | SY | IS)* × (PO | PR) : close the sequence
			.numSequence = noNumSequence
			return true
		default:
			.numSequence = noNumSequence
			return false
		}
	case seenCloseNum:
		.numSequence = noNumSequence // close the sequence anyway
		if .line&(ucd.LB_PO|ucd.LB_PR) != 0 {
			// NU (NU | SY | IS)* (CL | CP) × (PO | PR)
			return true
		}
		return false
	default:
		panic("exhaustive switch")
	}
}

// startIteration updates the cursor properties, setting the current
// rune to text[i].
// Some properties depending on the context are rather
// updated in the previous `endIteration` call.
func ( *cursor) ( []rune,  int) {
	.index = 

	.prevPrev = .prev
	.prev = .r
	if  < len() {
		.r = []
	} else {
		.r = paragraphSeparator
	}
	if  == len() {
		.next = 0
	} else if  == len()-1 {
		// we fill in the last element of `attrs` by assuming
		// there's a paragraph separators off the end of text
		.next = paragraphSeparator
	} else {
		.next = [+1]
	}

	// query general unicode properties for the current rune
	.prevGeneralCategory = .generalCategory
	.generalCategory = ucd.LookupGeneralCategory(.r)

	.isExtentedPic = ucd.IsExtendedPictographic(.r)
	.indicConjunctBreak = ucd.LookupIndicConjunctBreak(.r)

	// prevPrevLine and prevLine are handled in endIteration
	.line = .nextLine
	.nextLine = ucd.LookupLineBreak(.next)

	.prevGrapheme = .grapheme
	.grapheme = ucd.LookupGraphemeBreak(.r)

	if .word != ucd.WB_ExtendFormat {
		.prevPrevWord = .prevWord
		.prevWord = .word
		.prevWordNoExtend =  - 1
	}
	.word = ucd.LookupWordBreak(.r)
}

// end the current iteration, computing some of the properties
// required for the next rune and respecting rule LB9 and LB10
func ( *cursor) () {
	// start by handling rule LB9 and LB10
	if .line&(ucd.LB_CM|ucd.LB_ZWJ) != 0 {
		 := .prevLine&(ucd.LB_BK|ucd.LB_CR|ucd.LB_LF|ucd.LB_NL|ucd.LB_SP|ucd.LB_ZW) != 0
		if .index == 0 ||  { // Rule LB10
			.prevLine = ucd.LB_AL
		} // else rule LB9 : ignore the rune for prevLine and prevPrevLine

	} else { // regular update
		.isPreviousSot = .index == 0

		// keep track of the rune before the spaces
		if .line != ucd.LB_SP {
			.beforeSpaces = .r
			.prevBeforeSpaces = .prev
			.beforeSpacesIndex = .index

			.beforeSpacesLine = .line
			.prevBeforeSpacesLine = .prevLine
		}

		.prevPrevLine = .prevLine
		.prevLine = .line

		.isPrevPrevDottedCircle = .isPrevDottedCircle
		.isPrevDottedCircle = .r == 0x25CC

		.isPrevNonAssignedExtendedPic = .isExtentedPic && .generalCategory == ucd.Unassigned
	}

	.prevLineRaw = .line

	// keep track of the rune before the spaces
	if .prevLine != ucd.LB_SP {
		.beforeSpaceLineRaw = .line
	}

	// update RegionalIndicator parity used for LB30a
	if .line == ucd.LB_RI {
		.isPrevLinebreakRIOdd = !.isPrevLinebreakRIOdd
	} else if .line&(ucd.LB_CM|ucd.LB_ZWJ) == 0 { // beware of the rule LB9: (CM|ZWJ) ignore the update
		.isPrevLinebreakRIOdd = false
	}
}