import { AuxOpType , AuxOp , TagOp , ValueOp , BotOp , CertificateOp , TagMaskOp , InsertOp , } from './AuxOpTypes' ; import { Weave , WeaveNode , iterateCausalGroup , Atom , SiteStatus , addAtom , first , iterateChildren , idEquals , AtomId , atom , VersionVector , } from '@casual-simulation/causal-trees/core2' ; import { isEqual } from 'lodash' ; import { splice } from '../utils' ; import { convertToString , hasValue } from '../bots/BotCalculations' ; param weave param id export function findBotNode ( weave , id ) { return first ( findBotNodes ( weave , id ) ) || null ; } param weave param id export function * findBotNodes ( weave , id ) { for ( let root of weave . roots ) { if ( root . atom . value . type === AuxOpType . Bot && root . atom . value . id === id ) { const firstAtom = first ( iterateCausalGroup ( root ) ) ; if ( ! firstAtom || firstAtom . atom . value . type !== AuxOpType . Delete ) { yield root as WeaveNode < BotOp > ; } } } } param bot param tag export function findTagNode ( bot , tag ) { for ( let node of iterateCausalGroup ( bot ) ) { if ( node . atom . value . type === AuxOpType . Tag && node . atom . value . name === tag ) { return node as WeaveNode < TagOp > ; } } return null ; } param weave param botId param tag export function * findTagMaskNodes ( weave , botId , tag ) { for ( let root of weave . roots ) { if ( root . atom . value . type === AuxOpType . TagMask && root . atom . value . botId === botId && root . atom . value . name === tag ) { const firstAtom = first ( iterateCausalGroup ( root ) ) ; if ( ! firstAtom || firstAtom . atom . value . type !== AuxOpType . Delete ) { yield root as WeaveNode < TagMaskOp > ; } } } } param tag export function findValueNode ( tag ) { for ( let node of iterateCausalGroup ( tag ) ) { if ( node . atom . value . type === AuxOpType . Value ) { return node as WeaveNode < ValueOp > ; } } return null ; } param tag param value export function findValueNodeByValue ( tag , value ) { for ( let node of iterateCausalGroup ( tag ) ) { if ( node . atom . value . type === AuxOpType . Value && isEqual ( node . atom . value . value , value ) ) { return node as WeaveNode < ValueOp > ; } } return null ; } param value param version param index export function findEditPosition ( value , version , index ) ; param value param version param index param deleteCount export function findEditPosition ( value , version , index , deleteCount ) ; param value param version param index param deleteCount export function findEditPosition ( value , version , index , deleteCount ? ) { if ( value . atom . value . type !== AuxOpType . Value ) { throw new Error ( 'Invalid Argument. The weave node must be a value node.' ) ; } const children = [ value , ... iterateCausalGroup ( value ) ] ; const filtered = children . filter ( ( c ) => { const timestamp = version [ c . atom . id . site ] || - 1 ; return c . atom . id . timestamp <= timestamp ; } ) ; const edits = calculateOrderedEdits ( filtered ) ; if ( edits . length <= 0 && typeof deleteCount === 'undefined' ) { return { index : 0 , node : value , } ; } if ( typeof deleteCount === 'number' ) { return [ ... findMultipleEditPositions ( index , deleteCount , edits , 'right' ) , ] ; } else { return findSingleEditPosition ( index , edits ) ; } } param index param deleteCount param edits param afinity export function * findMultipleEditPositions ( index , deleteCount , edits , afinity ) { let count = 0 ; let remaining = deleteCount ; for ( let edit of edits ) { let reachedStart = afinity === 'left' ? count + edit . text . length >= index : count + edit . text . length > index ; if ( reachedStart ) { const relativeIndex = Math . abs ( count - index ) ; const countUntilEnd = edit . text . length - relativeIndex ; let relativeIndexOffset = 0 ; for ( let i = 0 , b = 0 ; i < relativeIndex && b < edit . marked . length ; ) { let normalChar = edit . text [ i ] ; let markedChar = edit . marked [ b ] ; if ( normalChar === markedChar ) { i += 1 ; } else { relativeIndexOffset += 1 ; } b += 1 ; } const finalOffset = relativeIndex + relativeIndexOffset ; const nodeDeleteCount = Math . min ( countUntilEnd , remaining ) ; const textAfter = edit . marked . slice ( finalOffset , edit . marked . length ) ; let removedCharacterCount = 0 ; let deleteCountOffset = 0 ; let afterIndex = 0 ; for ( ; afterIndex < textAfter . length ; afterIndex ++ ) { if ( textAfter [ afterIndex ] === '\0' ) { removedCharacterCount += 1 ; } else { break ; } } let extraCharactersIndex = 0 ; for ( ; afterIndex < textAfter . length ; afterIndex ++ ) { if ( extraCharactersIndex >= nodeDeleteCount ) { break ; } if ( textAfter [ afterIndex ] === '\0' ) { deleteCountOffset += 1 ; } else { extraCharactersIndex += 1 ; } } yield { index : finalOffset + removedCharacterCount + edit . offset , count : nodeDeleteCount + deleteCountOffset , node : edit . node , } ; remaining -= nodeDeleteCount ; count += relativeIndex ; } else { count += edit . text . length ; } if ( remaining <= 0 ) { break ; } } } param index param edits export function findSingleEditPosition ( index , edits ) { return first ( findMultipleEditPositions ( index , 1 , edits , 'left' ) ) ; } param nodes param preserveEmptyEdits export function calculateOrderedEdits ( nodes , preserveEmptyEdits = false ) { let segments = [ ] as TextSegmentInfo [ ] ; for ( let node of nodes ) { const atomValue = node . atom . value ; if ( atomValue . type === AuxOpType . Value ) { let text = convertToString ( atomValue . value ) ; let segment = { text : text , node : node as WeaveNode < ValueOp > , offset : 0 , totalLength : atomValue . value . length , } ; segments . push ( segment ) ; } else if ( atomValue . type === AuxOpType . Insert ) { const segment = { text : atomValue . text , node : node as WeaveNode < InsertOp > , offset : 0 , totalLength : atomValue . text . length , } ; let count = 0 ; let lastCausalGroupIndex = 0 ; let isInCausalGroup = false ; let lastCauseTime = - 1 ; let added = false ; for ( let i = 0 ; i < segments . length ; i ++ ) { const lastSegment = segments [ i ] ; if ( ! idEquals ( lastSegment . node . atom . id , node . atom . cause ) ) { if ( isInCausalGroup ) { if ( lastSegment . node . atom . id . timestamp < lastCauseTime ) { isInCausalGroup = false ; } else { lastCausalGroupIndex = i ; } } continue ; } isInCausalGroup = true ; lastCauseTime = lastSegment . node . atom . id . timestamp ; lastCausalGroupIndex = i ; if ( atomValue . index <= 0 ) { segments . splice ( i , 0 , segment ) ; added = true ; break ; } else if ( atomValue . index - count < + lastSegment . text . length ) { const first = { text : lastSegment . text . slice ( 0 , atomValue . index - count ) , node : lastSegment . node , offset : lastSegment . offset + 0 , totalLength : lastSegment . totalLength , } ; const second = { text : lastSegment . text . slice ( atomValue . index - count ) , offset : lastSegment . offset + ( atomValue . index - count ) , node : lastSegment . node , totalLength : lastSegment . totalLength , } ; segments . splice ( i , 1 , first , segment , second ) ; added = true ; break ; } count += lastSegment . text . length ; } if ( ! added ) { segments . splice ( lastCausalGroupIndex + 1 , 0 , segment ) ; } } else if ( atomValue . type === AuxOpType . Delete ) { let lastSegment = segments . find ( ( s ) => idEquals ( s . node . atom . id , node . atom . cause ) ) ; if ( lastSegment ) { for ( let i = atomValue . start ; i < atomValue . end ; i ++ ) { lastSegment . text = splice ( lastSegment . text , i , 1 , '\0' ) ; } } } } return segments . map ( ( s ) => ( { text : s . text . replace ( /\0/g , '' ) , marked : s . text , offset : s . offset , node : s . node , } ) ) . filter ( ( s ) => preserveEmptyEdits || s . text . length > 0 ) ; } param weave param value export function calculateFinalEditValue ( value ) { const children = [ value , ... iterateCausalGroup ( value ) ] ; const edits = calculateOrderedEdits ( children ) ; return edits . map ( ( e ) => e . text ) . join ( '' ) ; } /**
 * Defines an interface that represents a segment of text that has been derived from a node.
 */ export interface TextSegment { /**
     * The text of the edit.
     */ text : string ; /**
     * The index offset that this segment starts at.
     * Useful when the node was split into two segments.
     */ offset : number ; /**
     * The text that includes null characters to indicate characters that were deleted.
     */ marked : string ; /**
     * The node that the edit text was produced from.
     */ node : WeaveNode < ValueOp | InsertOp > ; } /**
 * Defines an interface that contains extra information about a text segment.
 */ export interface TextSegmentInfo { /**
     * The text of the edit.
     */ text : string ; /**
     * The total length of text sequences.
     */ totalLength : number ; /**
     * The index offset that this segment starts at.
     * Useful when the node was split into two segments.
     */ offset : number ; /**
     * The node that the edit text was produced from.
     */ node : WeaveNode < ValueOp | InsertOp > ; } export interface EditPosition { node : WeaveNode < AuxOp > ; index : number ; count ? : number ; }