import { strict as assert } from "assert" ; import { FrugalList , FrugalList_push , FrugalList_removeFirst , FrugalList_forEach } from "@tiny-calc/frugallist" ; import { HandleTable } from "@tiny-calc/handletable" ; import { GraphNode , IGraphShapeConsumer , IGraphShapeProducer , IGraphShapeReader , IGraphShapeWriter } from "./types" ; export class GraphShape implements IGraphShapeProducer , IGraphShapeReader , IGraphShapeWriter { private readonly nodeToChildren = new HandleTable < GraphNode [ ] , GraphNode > ( ) ; private readonly nodeToParents : FrugalList < GraphNode > [ ] = [ ] ; private consumers : FrugalList < IGraphShapeConsumer > ; public constructor ( ) { const root = this . createNode ( ) ; assert . equal ( root , GraphNode . root ) ; } public openGraph ( consumer ) { this . consumers = FrugalList_push ( this . consumers , consumer ) ; return this ; } public closeGraph ( consumer ) { this . consumers = FrugalList_removeFirst ( this . consumers , consumer ) ; } public getChildCount ( parent ) { return this . nodeToChildren . get ( parent ) . length ; } public getChild ( parent , index ) { return this . nodeToChildren . get ( parent ) [ index ] ; } public createNode ( ) { return this . nodeToChildren . add ( [ ] ) ; } public deleteNode ( node ) { FrugalList_forEach ( this . nodeToParents [ node ] , parent => { const parentChildren = this . nodeToChildren . get ( parent ) ; const parentIndex = parentChildren . lastIndexOf ( node ) ; parentChildren . splice ( parentIndex , 1 ) ; } ) ; this . nodeToParents [ node ] = undefined ; this . nodeToChildren . delete ( node ) ; } public spliceChildren ( parent , start , removeCount , ... toInsert ) { const children = this . nodeToChildren . get ( parent ) ; for ( let i = start ; i < start + removeCount ; i ++ ) { this . removeParent ( children [ i ] , parent ) ; } for ( const child of toInsert ) { this . addParent ( child , parent ) ; } children . splice ( start , removeCount , ... toInsert ) ; this . invalidateShape ( parent , start , removeCount , toInsert . length ) ; } protected forEachConsumer ( callback ) { FrugalList_forEach ( this . consumers , callback ) ; } private invalidateShape ( parent , start , removedCount , insertCount ) { this . forEachConsumer ( ( consumer ) => { consumer . childrenChanged ( parent , start , removedCount , insertCount , this ) ; } ) ; } private addParent ( child , parent ) { this . nodeToParents [ child ] = FrugalList_push ( this . nodeToParents [ child ] , parent ) ; } private removeParent ( child , parent ) { this . nodeToParents [ child ] = FrugalList_removeFirst ( this . nodeToParents [ child ] , parent ) ; } }