"use strict" ;

import Identifiable from '../data/Identifiable'

import ObjectOrder    from './ObjectOrder'
import ObjectListener from './ObjectListener'
import ObjectReceiver from './ObjectReceiver'
import ObjectScope    from './ObjectScope'
import ObjectStrategy from './ObjectStrategy'

/**
 * The objects that form the backbone of your application and that are managed by the <b>IoC container</b> are called <b>object definitions</b>. An <b>object definition</b> is an object that is instantiated, assembled, and otherwise managed by the <b>IoC factory</b>.
 * <p>The <b>object definition</b> contains the information called configuration metadata which is needed for the container to know the followings :
 * <ul>
 * <li>How to create an object</li>
 * <li>Object's lifecycle details</li>
 * <li>Object's dependencies</li>
 * </ul>
 * </p>
 * @name ObjectDefinition
 * @class
 * @memberof system.ioc
 * @implements system.data.Identifiable
 * @extends system.data.Identifiable
 * @throws ReferenceError if the <code>id</code> and <code>type</code> arguments are <code>null</code> or <code>undefined</code>.
 */
class ObjectDefinition extends Identifiable
{
    /**
     * @constructor
     * @param {string} id - The unique index of the object definition register in the container.
     * @param {string|Function} type - The type of the object (the function reference of the class name).
     * @param {boolean} [singleton=false] - Indicates if the object definition scope is 'singleton' or not.
     * @param {boolean} [lazyInit=false] - Indicates if the object definition scope is 'lazyInit' or not.
     * @param {boolean} [lazyType=false] - Indicates if the object definition check or not the type of the new object.
     */
    constructor( id , type , singleton = false , lazyInit = false , lazyType=false )
    {
        super( id )
        
        if ( id === null || id === undefined )
        {
            throw new ReferenceError( this + " constructor failed, the 'id' value passed in argument not must be empty or 'null' or 'undefined'.") ;
        }
        
        if ( type === null || type === undefined )
        {
            throw new ReferenceError( this + " constructor failed, the 'type' passed in argument not must be empty or 'null' or 'undefined'.") ;
        }

        Object.defineProperties( this ,
        {
            /**
             * The constructor arguments values of this object in a Array list.
             * @name constructorArguments
             * @memberof system.ioc.ObjectDefinition
             * @instance
             */
            constructorArguments : { value : null , enumerable : true , writable : true } ,
            
            /**
             * Determinates the name of the method invoked when the object is destroyed.
             * @name destroyMethodName
             * @memberof system.ioc.ObjectDefinition
             * @instance
             */
            destroyMethodName : { value : null , enumerable : true , writable : true } ,
            
            /**
             * Indicates if the object definition is a singleton and the type of the object is Identifiable if the object must be populated with the id of the definition when is instanciated.
             * @name identify
             * @memberof system.ioc.ObjectDefinition
             * @instance
             * @type boolean
             */
            identify : { value : false , enumerable : true , writable : true } ,
            
            /**
             * Determinates the name of the method invoked when the object is created.
             * @name initMethodName
             * @memberof system.ioc.ObjectDefinition
             * @instance
             * @type string
             */
            initMethodName : { value : null , enumerable : true , writable : true } ,
            
            /**
             * Indicates if the object definition lock this Lockable object during the population of the properties and the initialization of the methods defines in the object definition.
             * @name lock
             * @memberof system.ioc.ObjectDefinition
             * @instance
             * @type boolean
             */
            lock : { value : false , enumerable : true , writable : true } ,
            
            /**
             * Sets the Array representation of all properties of this definition.
             * @name properties
             * @memberof system.ioc.ObjectDefinition
             * @instance
             */
            properties : { value : null , enumerable : true , writable : true } ,
            
            /**
             * Indicates the type of the object (the function reference of the class name).
             * @name type
             * @memberof system.ioc.ObjectDefinition
             * @instance
             */
            type : { value : type , enumerable : true , writable : true } ,
            
            /**
             * @private
             */
            _afterListeners  : { value : null , writable : true } ,
            _beforeListeners : { value : null , writable : true } ,
            _dependsOn       : { value : null , writable : true } ,
            _generates       : { value : null , writable : true } ,
            _lazyInit        : { value : lazyInit && singleton , writable : true } ,
            _lazyType        : { value : lazyType === true , writable : true } ,
            _singleton       : { value : singleton === true , writable : true } ,
            _scope           : { value : singleton === true ? ObjectScope.SINGLETON : ObjectScope.PROTOTYPE , writable : true } ,
            _strategy        : { value : null , writable : true }
        });
    }
    
    /**
     * The <code>Array</code> of all listener definitions of this object definition register after the object initialization.
     * @memberof system.ioc.ObjectDefinition
     * @member
     * @readonly
     * @type Array
     */
    get afterListeners()
    {
        return this._afterListeners ;
    }

    /**
     * Returns the Array of all receiver definitions of this object definition register after the object initialization.
     * @return the Array of all receiver definitions of this object definition register after the object initialization.
     * @name afterReceivers
     * @memberof system.ioc.ObjectDefinition
     * @member
     * @readonly
     * @type Array
     */
    get afterReceivers()
    {
        return this._afterReceivers ;
    }
    
    /**
     * Returns the Array of all listener definitions of this object definition register before the object initialization.
     * @return the Array of all listener definitions of this object definition register before the object initialization.
     * @name beforeListeners
     * @memberof system.ioc.ObjectDefinition
     * @member
     * @readonly
     * @type Array
     */
    get beforeListeners()
    {
        return this._beforeListeners ;
    }
    
    /**
     * Returns the Array of all receiver definitions of this object definition register before the object initialization.
     * @return the Array of all receiver definitions of this object definition register before the object initialization.
     * @name beforeReceivers
     * @memberof system.ioc.ObjectDefinition
     * @member
     * @readonly
     * @type Array
     */
    get beforeReceivers()
    {
        return this._beforeReceivers ;
    }
    
    /**
     * Defines the "dependsOn" collection.
     * @name dependsOn
     * @memberof system.ioc.ObjectDefinition
     * @member
     */
    get dependsOn()
    {
        return this._dependsOn ;
    }
    
    set dependsOn( ar )
    {
        this._dependsOn = ( ar instanceof Array && ar.length > 0 ) ? ar.filter( this._filterStrings ) : null ;
    }
    
    /**
     * Defines the "generates" collection.
     * @name generates
     * @memberof system.ioc.ObjectDefinition
     * @member
     */
    get generates() { return this._generates ; }
    set generates( ar )
    {
        this._generates = ( ar instanceof Array && ar.length > 0 ) ? ar.filter( this._filterStrings ) : null ;
    }
    
    /**
     * Indicates if the object lazily initialized. Only applicable to a singleton object. If <code>false</code>, it will get instantiated on startup by object factories that perform eager initialization of singletons.
     * @name lazyInit
     * @memberof system.ioc.ObjectDefinition
     * @member
     */
    get lazyInit() { return this._lazyInit; }
    set lazyInit( flag )
    {
        this._lazyInit = ((flag instanceof Boolean) || (typeof(flag) === 'boolean')) ? flag : false ;
    }
    
    /**
     * Indicates if the object lazily typed. If <code>false</code>, it will the object is instantiated without type checking.
     * @name lazyType
     * @memberof system.ioc.ObjectDefinition
     * @member
     */
    get lazyType() { return this._lazyType; }
    set lazyType( value )
    {
        this._lazyType = value === true ;
    }
    
    
    /**
     * Sets the Array of all receiver definition of this object definition.
     * @param ar the Array of all receiver definitions of the object.
     * @name listeners
     * @memberof system.ioc.ObjectDefinition
     * @instance
     */
    set listeners( ar )
    {
        this._afterListeners  = [] ;
        this._beforeListeners = [] ;
        if ( ar === null || !(ar instanceof Array) )
        {
            return ;
        }
        let l = ar.length ;
        if ( l > 0 )
        {
            for( let i = 0 ; i < l ; i++ )
            {
                let r = ar[i] ;
                if ( r instanceof ObjectListener )
                {
                    if( r.order === ObjectOrder.AFTER )
                    {
                        this._afterListeners.push( r ) ;
                    }
                    else
                    {
                        this._beforeListeners.push( r ) ;
                    }
                }
            }
        }
    }
    
    /**
     * Sets the Array of all receiver definition of this object definition.
     * @param ar the Array of all receiver definitions of the object.
     * @name receivers
     * @memberof system.ioc.ObjectDefinition
     * @instance
     */
    set receivers( ar )
    {
        this._afterReceivers  = [] ;
        this._beforeReceivers = [] ;
        if ( ar === null || !(ar instanceof Array) )
        {
            return ;
        }
        let l = ar.length ;
        if ( l > 0 )
        {
            for( var i = 0 ; i < l ; i++ )
            {
                let r = ar[i] ;
                if ( r instanceof ObjectReceiver )
                {
                    if( r.order === ObjectOrder.AFTER )
                    {
                        this._afterReceivers.push( r ) ;
                    }
                    else
                    {
                        this._beforeReceivers.push( r ) ;
                    }
                }
            }
        }
    }
    
    /**
     * Indicates if the object in a singleton else the object is a prototype (read only, use the scope property to change it).
     * @name singleton
     * @memberof system.ioc.ObjectDefinition
     * @instance
     * @type boolean
     * @type readonly
     */
    get singleton() { return this._singleton; }
    
    /**
     * Determinates the scope of the object.
     * @name scope
     * @memberof system.ioc.ObjectDefinition
     * @member
     */
    get scope() { return this._scope ; }
    set scope( scope )
    {
        this._scope = ObjectScope.validate( scope ) ? scope : ObjectScope.PROTOTYPE ;
        this._singleton = Boolean(this._scope === ObjectScope.SINGLETON) ;
    }
    
    /**
     * Determinates the factory stategy of this definition to create the object.
     * @name strategy
     * @memberof system.ioc.ObjectDefinition
     * @instance
     */
    get strategy() { return this._strategy ; }
    set strategy( strategy )
    {
        this._strategy = (strategy instanceof ObjectStrategy) ? strategy : null ;
    }
    
    /**
     * Returns the string representation of this instance.
     * @return the string representation of this instance.
     * @memberof system.ioc.ObjectDefinition
     * @name toString
     * @instance
     * @function
     */
    toString()
    {
        return "[ObjectDefinition]" ;
    }
    
    /**
     * @private
     * @memberof system.ioc.ObjectDefinition
     * @name _filterStrings
     * @instance
     * @function
     */
    _filterStrings( item )
    {
        return (typeof(item) === 'string' || item instanceof String) && item.length > 0 ;
    }
}

export default ObjectDefinition ;