export type WindupMember < ElementType , MetadataType > = ElementType | Windup < ElementType , MetadataType > ; export type Windup < ElementType , MetadataType > = [ Array < WindupMember < ElementType , MetadataType > > , Array < WindupMember < ElementType , MetadataType > > , MetadataType ] ; export function isPlayedWindup < ElementType , MetadataType > ( windup ) { return windup . length === 2 ; } export function memberIsWindup < ElementType , MetadataType > ( member ) { if ( ! Array . isArray ( member ) ) { return false ; } if ( member . length !== 3 ) { return false ; } if ( ! Array . isArray ( member [ 0 ] ) || ! Array . isArray ( member [ 1 ] ) ) { return false ; } return true ; } export function windupFromString < MetadataType > ( str , metadata ) { return [ [ ] , str . split ( "" ) , metadata ] ; } export function newWindup < ElementType , MetadataType > ( arg , metadata ) { return [ [ ] , arg , metadata ] ; } export function isUnplayed < ElementType , MetadataType > ( windup ) { const [ played , remaining ] = windup ; if ( played . length > 0 ) { return false ; } return remaining . reduce ( ( unplayed , member ) => { if ( memberIsWindup ( member ) ) if ( memberIsWindup ( windup ) && unplayed ) { return isUnplayed < ElementType , MetadataType > ( member ) ; } return unplayed ; } , true ) ; } export function isFinished < ElementType , MetadataType > ( [ _played , remaining ] ) { return remaining . length === 0 ; } export function fastForward < ElementType , MetadataType > ( windup ) { const forwardedWindup = next ( windup ) ; if ( isFinished ( forwardedWindup ) ) { return forwardedWindup ; } return fastForward ( forwardedWindup ) ; } export function rewind < ElementType , MetadataType > ( windup ) { if ( isUnplayed ( windup ) ) { return windup ; } const [ played , remaining , metadata ] = windup ; type MemberType = WindupMember < ElementType , MetadataType > ; const mapRewind = ( member ) : MemberType => { if ( memberIsWindup ( member ) ) { return rewind ( member ) ; } return member ; } ; return [ [ ] , [ ... played . map ( mapRewind ) , ... remaining . map ( mapRewind ) ] , metadata ] ; } export function windupAsString < MetadataType > ( windup ) { const [ played , remaining ] = windup ; type MemberType = WindupMember < string , MetadataType > ; const stringify = ( member ) : MemberType => { if ( memberIsWindup ( member ) ) { return windupAsString ( member ) ; } return member ; } ; return [ played . map ( stringify ) . join ( "" ) , remaining . map ( stringify ) . join ( "" ) ] . join ( "" ) ; } export function lastPlayedMember < ElementType , MetadataType > ( [ played , remaining ] ) { const playedFromRemaining = remaining . reduce ( ( playedEl , member ) => { if ( memberIsWindup ( member ) ) { if ( ! isUnplayed ( member ) ) { return lastPlayedElement ( member ) ; } } return playedEl ; } , undefined ) ; if ( playedFromRemaining ) { return playedFromRemaining ; } const last = played [ played . length - 1 ] ; return last ; } export function lastPlayedElement < ElementType , MetadataType > ( windup ) { const lastPlayed = lastPlayedMember ( windup ) ; if ( memberIsWindup ( lastPlayed ) ) { return lastPlayedElement ( lastPlayed ) ; } return lastPlayed ; } export type PlayedWindup < ElementType , MetadataType > = [ Array < ElementType > , MetadataType ] ; export function playedElements < ElementType , MetadataType > ( [ played , remaining ] ) { const playedTransformed = played . map ( member => { if ( memberIsWindup ( member ) ) { const [ , , metadata ] = member ; return [ playedElements ( member ) , metadata ] as PlayedWindup < ElementType , MetadataType > ; } return member ; } ) ; const [ firstRemaning ] = remaining ; if ( memberIsWindup ( firstRemaning ) && ! isUnplayed ( firstRemaning ) ) { const [ , , metadata ] = firstRemaning ; return [ ... playedTransformed , [ playedElements ( firstRemaning ) , metadata ] as PlayedWindup < ElementType , MetadataType > ] ; } return playedTransformed ; } export function nextElement < ElementType , MetadataType > ( [ _played , remaining ] ) { const [ nextVal ] = remaining ; if ( memberIsWindup ( nextVal ) ) { return nextElement ( nextVal ) ; } return nextVal ; } export function next < ElementType , MetadataType > ( windup ) { if ( isFinished ( windup ) ) { return windup ; } const [ played , remaining , metadata ] = windup ; const [ firstRemaining , ... restRemaining ] = remaining ; if ( memberIsWindup ( firstRemaining ) ) { const nextFirstRemaining = next ( firstRemaining ) ; if ( isFinished ( nextFirstRemaining ) ) { return [ [ ... played , nextFirstRemaining ] , restRemaining , metadata ] ; } return [ played , [ nextFirstRemaining , ... restRemaining ] , metadata ] ; } return [ [ ... played , firstRemaining ] , restRemaining , metadata ] ; }