// Copyright 2019-2021, University of Colorado Boulder

/**
 * ReferenceIO uses reference identity for toStateObject/fromStateObject
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Chris Klusendorf (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */

import ValidatorDef from '../../../axon/js/ValidatorDef.js';
import CouldNotYetDeserializeError from '../CouldNotYetDeserializeError.js';
import tandemNamespace from '../tandemNamespace.js';
import IOType from './IOType.js';

// {Map.<cacheKey:string|*, IOType>} - Cache each parameterized ReferenceIO so that it is only created once
const cache = new Map();

/**
 * @param {IOType} parameterType
 * @returns {IOType}
 */
const ReferenceIO = parameterType => {
  assert && assert( parameterType, 'ReferenceIO needs parameterType' );

  const cacheKey = parameterType;

  if ( !cache.has( cacheKey ) ) {

    assert && assert( typeof parameterType.typeName === 'string', 'type name should be a string' );
    cache.set( cacheKey, new IOType( `ReferenceIO<${parameterType.typeName}>`, {
      isValidValue: value => ValidatorDef.isValueValid( value, parameterType.validator ),
      documentation: 'Uses reference identity for serializing and deserializing, and validates based on its parameter IO Type.',
      parameterTypes: [ parameterType ],

      /**
       * Return the json that ReferenceIO is wrapping.  This can be overridden by subclasses, or types can use ReferenceIO type
       * directly to use this implementation.
       */
      toStateObject( phetioObject ) {
        return {
          phetioID: phetioObject.tandem.phetioID
        };
      },

      /**
       * Decodes the object from a state, used in PhetioStateEngine.setState.  This can be overridden by subclasses, or types can
       * use ReferenceIO type directly to use this implementation.
       * @param {{phetioID:string}} stateObject
       * @returns {PhetioObject}
       * @throws CouldNotYetDeserializeError
       */
      fromStateObject( stateObject ) {
        assert && assert( stateObject && typeof stateObject.phetioID === 'string', 'phetioID should be a string' );
        if ( phet.phetio.phetioEngine.hasPhetioObject( stateObject.phetioID ) ) {
          return phet.phetio.phetioEngine.getPhetioObject( stateObject.phetioID );
        }
        else {
          throw new CouldNotYetDeserializeError();
        }
      },

      /**
       * References should be using fromStateObject to get a copy of the PhET-iO element.
       */
      applyState( coreObject ) {
        assert && assert( false, `ReferenceIO is meant to be used as DataType serialization (see fromStateObject) for phetioID: ${coreObject.tandem.phetioID}` );
      }
    } ) );
  }

  return cache.get( cacheKey );
};

tandemNamespace.register( 'ReferenceIO', ReferenceIO );
export default ReferenceIO;
@@ -1573,32 +1573,182 @@
 hetioObject ) %7B%0A
+        assert && assert( phetioObject.phetioState === false, %60ReferenceIO instance should be phetioState: false: $%7BphetioObject.tandem.phetioID%7D%60 );%0A
         return %7B

Assert that PhetioObjects with IO Type ReferenceIO are phetioState: false, see https://github.com/phetsims/tandem/issues/241