/**
 * A class which attaches a value to an external object.
 *
 * #### Notes
 * Attached properties are used to extend the state of an object with
 * semantic data from an unrelated class. They also encapsulate value
 * creation, coercion, and notification.
 *
 * Because attached property values are stored in a hash table, which
 * in turn is stored in a WeakMap keyed on the owner object, there is
 * non-trivial storage overhead involved in their use. The pattern is
 * therefore best used for the storage of rare data.
 */ export class AttachedProperty < T , U > { param options constructor ( options ) { this . name = options . name ; this . _create = options . create ; this . _coerce = options . coerce || null ; this . _compare = options . compare || null ; this . _changed = options . changed || null ; } /**
     * The human readable name for the property.
     */ readonly name : string ; param owner returns get ( owner ) { let value ; let map = Private . ensureMap ( owner ) ; if ( this . _pid in map ) { value = map [ this . _pid ] ; } else { value = map [ this . _pid ] = this . _createValue ( owner ) ; } return value ; } param owner param value set ( owner , value ) { let oldValue ; let map = Private . ensureMap ( owner ) ; if ( this . _pid in map ) { oldValue = map [ this . _pid ] ; } else { oldValue = map [ this . _pid ] = this . _createValue ( owner ) ; } let newValue = this . _coerceValue ( owner , value ) ; this . _maybeNotify ( owner , oldValue , ( map [ this . _pid ] = newValue ) ) ; } param owner coerce ( owner ) { let oldValue ; let map = Private . ensureMap ( owner ) ; if ( this . _pid in map ) { oldValue = map [ this . _pid ] ; } else { oldValue = map [ this . _pid ] = this . _createValue ( owner ) ; } let newValue = this . _coerceValue ( owner , oldValue ) ; this . _maybeNotify ( owner , oldValue , ( map [ this . _pid ] = newValue ) ) ; } /**
     * Get or create the default value for the given owner.
     */ private _createValue ( owner ) { let create = this . _create ; return create ( owner ) ; } /**
     * Coerce the value for the given owner.
     */ private _coerceValue ( owner , value ) { let coerce = this . _coerce ; return coerce ? coerce ( owner , value ) : value ; } /**
     * Compare the old value and new value for equality.
     */ private _compareValue ( oldValue , newValue ) { let compare = this . _compare ; return compare ? compare ( oldValue , newValue ) : oldValue === newValue ; } /**
     * Run the change notification if the given values are different.
     */ private _maybeNotify ( owner , oldValue , newValue ) { let changed = this . _changed ; if ( changed && ! this . _compareValue ( oldValue , newValue ) ) { changed ( owner , oldValue , newValue ) ; } } private _pid = Private . nextPID ( ) ; private _create : ( owner : T ) => U ; private _coerce : ( ( owner : T , value : U ) => U ) | null ; private _compare : ( ( oldValue : U , newValue : U ) => boolean ) | null ; private _changed : ( ( owner : T , oldValue : U , newValue : U ) => void ) | null ; } /**
 * The namespace for the `AttachedProperty` class statics.
 */ export namespace AttachedProperty { /**
     * The options object used to initialize an attached property.
     */ export interface IOptions < T , U > { /**
         * The human readable name for the property.
         *
         * #### Notes
         * By convention, this should be the same as the name used to define
         * the public accessor for the property value.
         *
         * This **does not** have an effect on the property lookup behavior.
         * Multiple properties may share the same name without conflict.
         */ name : string ; /**
         * A factory function used to create the default property value.
         *
         * #### Notes
         * This will be called whenever the property value is required,
         * but has not yet been set for a given owner.
         */ create : ( owner : T ) => U ; /**
         * A function used to coerce a supplied value into the final value.
         *
         * #### Notes
         * This will be called whenever the property value is changed, or
         * when the property is explicitly coerced. The return value will
         * be used as the final value of the property.
         *
         * This will **not** be called for the initial default value.
         */ coerce ? : ( owner : T , value : U ) => U ; /**
         * A function used to compare two values for equality.
         *
         * #### Notes
         * This is called to determine if the property value has changed.
         * It should return `true` if the given values are equivalent, or
         * `false` if they are different.
         *
         * If this is not provided, it defaults to the `===` operator.
         */ compare ? : ( oldValue : U , newValue : U ) => boolean ; /**
         * A function called when the property value has changed.
         *
         * #### Notes
         * This will be invoked when the property value is changed and the
         * comparator indicates that the old value is not equal to the new
         * value.
         *
         * This will **not** be called for the initial default value.
         */ changed ? : ( owner : T , oldValue : U , newValue : U ) => void ; } param owner export function clearData ( owner ) { Private . ownerData . delete ( owner ) ; } } /**
 * The namespace for the module implementation details.
 */ namespace Private { /**
     * A typedef for a mapping of property id to property value.
     */ export type PropertyMap = { [ key ] : any ; } ; /**
     * A weak mapping of property owner to property map.
     */ export const ownerData = new WeakMap < any , PropertyMap > ( ) ; /**
     * A function which computes successive unique property ids.
     */ export const nextPID = ( ( ) => { let id = 0 ; return ( ) => { let rand = Math . random ( ) ; let stem = `${ rand }` . slice ( 2 ) ; return `pid-${ stem }-${ id ++ }` ; } ; } ) ( ) ; /**
     * Lookup the data map for the property owner.
     *
     * This will create the map if one does not already exist.
     */ export function ensureMap ( owner ) { let map = ownerData . get ( owner ) ; if ( map ) { return map ; } map = Object . create ( null ) as PropertyMap ; ownerData . set ( owner , map ) ; return map ; } }