/* Copyright 2009-2015 Yorba Foundation
 *
 * This software is licensed under the GNU LGPL (version 2.1 or later).
 * See the COPYING file in this distribution.
 */

//
// DataObject
//
// Object IDs are incremented for each DataObject, and therefore may be used to compare
// creation order.  This behavior may be relied upon elsewhere.  Object IDs may be recycled when
// DataObjects are reconstituted by a proxy.
//
// Ordinal IDs are supplied by DataCollections to record the ordering of the object being added
// to the collection.  This value is primarily only used by DataCollection, but may be used
// elsewhere to resolve ordering questions (including stabilizing a sort).
//

// Have to inherit from Object due to ContainerSource and this bug:
// https://bugzilla.gnome.org/show_bug.cgi?id=615904
public abstract class DataObject : Object {
    public const int64 INVALID_OBJECT_ID = -1;
    
    private static int64 object_id_generator = 0;
    
#if TRACE_DTORS
    // because calling to_string() in a destructor is dangerous, stash to_string()'s result in
    // this variable for reporting
    protected string dbg_to_string = null;
#endif
    
    private int64 object_id = INVALID_OBJECT_ID;
    private DataCollection member_of = null;
    private int64 ordinal = DataCollection.INVALID_OBJECT_ORDINAL;
    
    // NOTE: Supplying an object ID should *only* be used when reconstituting the object (generally
    // only done by DataSources).
    public DataObject(int64 object_id = INVALID_OBJECT_ID) {
        this.object_id = (object_id == INVALID_OBJECT_ID) ? object_id_generator++ : object_id;
    }
    
    public virtual void notify_altered(Alteration alteration) {
        if (member_of != null)
            member_of.internal_notify_altered(this, alteration);
    }
    
    // There is no membership_changed signal as it's expensive (esp. at startup) and not needed
    // at this time.  The notify_membership_changed mechanism is still in place for subclasses.
    //
    // This is called after the change has occurred (i.e., after the DataObject has been added
    // to the DataCollection, or after it has been remove from the same).  It is also called after
    // the DataCollection has reported the change on its own signals, so it and its children can
    // properly integrate the DataObject into its pools.
    //
    // This is only called by DataCollection.
    public virtual void notify_membership_changed(DataCollection? collection) {
    }
    
    // Generally, this is only called by DataCollection.  No signal is bound to this because
    // it's not needed currently and affects performance.
    public virtual void notify_collection_property_set(string name, Value? old, Value val) {
    }
    
    // Generally, this is only called by DataCollection.  No signal is bound to this because
    // it's not needed currently and affects performance.
    public virtual void notify_collection_property_cleared(string name) {
    }
    
    public abstract string get_name();
    
    public abstract string to_string();
    
    public DataCollection? get_membership() {
        return member_of;
    }
    
    public bool has_membership() {
        return member_of != null;
    }
    
    // This method is only called by DataCollection.  It's called after the DataObject has been
    // assigned to a DataCollection.
    public void internal_set_membership(DataCollection collection, int64 ordinal) {
        assert(member_of == null);
        
        member_of = collection;
        this.ordinal = ordinal;
        
#if TRACE_DTORS
        dbg_to_string = to_string();
#endif
    }
    
    // This method is only called by SourceHoldingTank (to give ordinality to its unassociated
    // members).  DataCollections should call internal_set_membership.
    public void internal_set_ordinal(int64 ordinal) {
        assert(member_of == null);
        
        this.ordinal = ordinal;
    }
    
    // This method is only called by DataCollection.  It's called after the DataObject has been
    // assigned to a DataCollection.
    public void internal_clear_membership() {
        member_of = null;
        ordinal = DataCollection.INVALID_OBJECT_ORDINAL;
    }
    
    // This method is only called by DataCollection, DataSet, and SourceHoldingTank.
    public inline int64 internal_get_ordinal() {
        return ordinal;
    }
    
    public inline int64 get_object_id() {
        return object_id;
    }
    
    public Value? get_collection_property(string name, Value? def = null) {
        if (member_of == null)
            return def;
        
        Value? result = member_of.get_property(name);
        
        return (result != null) ? result : def;
    }
    
    public void set_collection_property(string name, Value val, ValueEqualFunc? value_equals = null) {
        if (member_of != null)
            member_of.set_property(name, val, value_equals);
    }
    
    public void clear_collection_property(string name) {
        if (member_of != null)
            member_of.clear_property(name);
    }
}

