// VectorDeltaWriter.cpp
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2010

@import "Stdafx.h"
#include "Ceda/Core/cxUtils/Tracer.h"
@import "Ceda/Core/cxObject/PathNavigator.h"
@import "Ceda/Core/cxObject/VectorField.h"
@import "Ceda/Core/cxObject/WCSpace.h"
@import "Ceda/Core/cxPersistStore/IPersistStore.h"
@import "VectorDeltaWriter.h"
@import "DeltaWriter.h"

namespace ceda
{

const bool tf_VectorDeltaWriter = false;

///////////////////////////////////////////////////////////////////////////////////////////////////
// VectorDeltaWriter

void VectorDeltaWriter::Init(const VectorOps& vectorOps, const VectorTime& rhv)
{
    cxAssert(gfnHaveLockedCSpace());
    cxAssert(m_fids.empty());
    
    if (tf_VectorDeltaWriter)
    {
        Tracer() << "VectorDeltaWriter::Init()\n";
    }
    IndentTrace indent;

    // Initialise m_fids
    for (VectorOps::MAP::const_iterator i = vectorOps.m_map.begin() ; i != vectorOps.m_map.end() ; ++i)
    {
        if (tf_VectorDeltaWriter)
        {
            Tracer() << "m_fids: insert " << i->first << '\n';
        }
        m_fids.insert(i->first);
    }
}

bool VectorDeltaWriter::HaveChangesToSend() const
{
    cxAssert(gfnHaveLockedCSpace());
    return !m_fids.empty();
}

/*

At the base vector time the field already contains real characters intersperced with tombstones.

   ..........*********...........................******......***********........****
   
Since the base vector time we have  
    1)  additional insertions of real characters
    2)  additional insertions of tombstones
    3)  conversion of existing characters to tombstones.
    
This is recorded using

    1)  list of insertion intervals (s,t,q,n) since base vector time
    2)  list of deletion intervals (s,t,q,n) since base vector time

Delta is sent as:

    1)  list of insertion intervals
    2)  list of deletion intervals.  This may delete characters that existed before the base 
        vector time, and also delete characters inserted since the base vector time.
    3)  a single string of the inserted characters.
    
However we would rather not send tombstone characters (we haven't even recorded them!).  
Is that possible?

In theory the receiver can determine the intersection of 1) and 2) (which are both expressed in
the same post insertion q coords), and therefore determine the sections of the string that contain
tombstones.

So we use this approach...


To gather the delta we iterate through the insertion intervals

    [----)   [---------------)         [----)    [------------)    [---)
    
A subset of these need to be sent (based on (s,t)).  E.g    
    
             [---------------)                   [------------)    [---)

For each of thsse we want to send exactly those bytes that are currently present in the field

We can show the intervals from the PtoQ map as well:


             [---------------)                   [------------)    [---)          insertions
             
        [-------)  [---)          [---) [--)        [--)   [-----------------)     PtoQ
            |
            |
     this interval in the PtoQ map means these characters haven't been deleted

We want to gather the characters in the *intersection*.  We can record p as we go so we know what
parts need to be copied from

It is instructive to review the code in:

    template <class T>
    void gfnIntersection(IntervalSet<T>& AB, const IntervalSet<T>& A, const IntervalSet<T>& B)
*/

/*

    struct InsertionsInVectorField
    {
        FieldId m_fid;
        VectorInsertions m_intervals;
        VectorOfByte m_buffer;      // Bytes to be inserted.
    };




Format:

    vectordelta:
        { FieldId, VectorInsertions, VectorOfByte },
        null_fid
*/

void VectorDeltaWriter::WriteNextDelta(
    DeltaWriter& dw, Archive& ar, const VectorOps& vo, const VectorTime& rhv)
{
    cxAssert(gfnHaveLockedCSpace());

    if (tf_VectorDeltaWriter)
    {
        Tracer() << "VectorDeltaWriter::WriteNextDelta()\n";
    }
    IndentTrace indent;

    /*
    m_fids represents the set of fieldids of fields of type xvector<T> for which elements
    have been inserted since the last delta was sent.
    
    We will be sending insertion information about each of the fields.
    */
    
    int k = 0;
    for (std::set<FieldId>::const_iterator i = m_fids.begin() ; 
         i != m_fids.end() ; 
         ++i)
    {
        const FieldId& fid = *i;
        
        if (tf_VectorDeltaWriter)
        {
            Tracer() << "fid = " << fid << '\n';
        }

        // It can be assumed that fid identifies a field of type xvector<T> that is known to
        // have had one or more elements inserted since the last delta was sent for this 
        // session.
        
        VectorInsertions dst_insertions;
        VectorDeletions dst_deletions;
        VectorOfByte dst_buffer;

        cxAssert(dst_insertions.empty());
        cxAssert(dst_deletions.empty());
        cxAssert(dst_buffer.empty());

        /*        
        1)  Bind to the persistent VectorInsertions recorded by the VectorOps in the map of
            type map<FieldId, VectorInsertions>

        2)  Bind to the field in the working set, and therefore access the xvector<T> as well 
            as the PtoQ map.
        
        3)  From the VectorInsertions and the underestimate rhv of the remote HB vector time 
            we can scan through the intervals ordered by q-position and test whether 
            t < rhv(s).  So we can find the set of intervals in q-space that need to be sent.
            
            This allows for recording the intervals in the delta, noting that we do not need
            to record element values in these intervals.
            
        4)  For each interval in q-space to be sent we use the field itself (and the PtoQ map)
            to determine which elements are currently present in that field, allowing the
            elements values for the field to be sent as part of the delta.
        */
        
        // Bind to the persistent VectorInsertions recorded by the VectorOps in the map of
        // type map<FieldId, VectorInsertions>
        VectorOps::MAP::const_iterator idm = vo.m_map.find(fid);
        cxAssert(idm != vo.m_map.end());
        const VectorInsertions& src_insertions = idm->second.m_insertions;
        const VectorDeletions& src_deletions = idm->second.m_deletions;

        // Bind to the field in the working set, and therefore access the xvector<T> as well 
        // as the PtoQ map.
        ptr<IPersistable> po = gfnBindObjectGivenOid(fid.oid);
        cxAssert(po);
        gfnSetTouched(po);
        
        ReflectionByteCode rbc;
        const void* addr = gfnNavigatePathToField(po, fid.path, rbc, false);
        
        AssertTypeIsVector(rbc);
        
        const VectorOfByte& pr = * (const VectorOfByte*) addr;
        const VectorField& vf = * (const VectorField*) addr;
        const PtoQMap& PToQ = vf.m_ptoq;
        
        // From the VectorInsertions and the underestimate rhv of the remote HB vector time 
        // we can scan through the intervals ordered by q-position and test whether 
        // t < rhv(s).  So we can find the set of intervals in q-space that need to be sent.
        // Note that this corresponds to an RFactor and there is no need to adjust the 
        // q-positions.
        // This allows for recording the intervals in the delta, noting that we do not need 
        // record element values in these intervals.
        {
            VectorCreateNode* last = NULL;
            const VectorCreateNode* r = src_insertions.m_c.m_first;

            while(r)
            {
                cxAssert(r->m_u < src_insertions.m_u2);
                
                //Tracer() << "rhv = " << rhv << '\n';
                //Tracer() << "r->m_opid.s = " << r->m_opid.s << '\n';
                //Tracer() << "r->m_opid.t = " << r->m_opid.t << '\n';

                if (!rhv.ExtentContains(r->m_opid))
                {
                    // Interval r needs to be sent
                    // Add a copy of r to dst_insertions
                    VectorCreateNode* copy = new VectorCreateNode(*r);
                    dst_insertions.m_c.PushBack(last,copy);
                    
                    // For each interval in q-space to be sent we use the field itself (and the
                    // PtoQ map) to determine which elements are currently present in that field,
                    // allowing for these elements to be sent as part of the delta.
                    
                    // Interval is [q1,q2)
                    int q1 = r->m_q;
                    int q2 = q1 + r->m_n;
                    
                    // todo: this is inefficient.  Instead we should iterate through the intervals
                    // recorded in the PtoQ map that correspond to the range [q1,q2)
                    for (int q = q1 ; q < q2 ; ++q)
                    {
                        if (PToQ.Exists(q))
                        {
                            int p = PToQ.GetP(q);
                            cxAssert(0 <= p && p < pr.size());
                            dst_buffer.push_back(pr[p]);
                        }
                    }
                }
                r = r->m_next;
            }
        
            if (dst_insertions.empty())
            {
                cxAssert(dst_buffer.empty());
            }
        }
        
        // Calculate dst_deletions
        {
            if (tf_VectorDeltaWriter)
            {
                Tracer() << "src_deletions = " << src_deletions << '\n';
            }
            
            VectorDeletions::Node* last = NULL;
            const VectorDeletions::Node* r = src_deletions.m_first;

            while(r)
            {
                //Tracer() << "rhv = " << rhv << '\n';
                //Tracer() << "r->m_opid.s = " << r->m_opid.s << '\n';
                //Tracer() << "r->m_opid.t = " << r->m_opid.t << '\n';

                if (!rhv.ExtentContains(r->m_opid))
                {
                    // Interval r needs to be sent
                    // Add a copy of r to dst_deletions
                    VectorDeletions::Node* copy = new VectorDeletions::Node(*r);
                    dst_deletions.PushBack(last,copy);
                }
                r = r->m_next;
            }
        }
                
        if (!dst_insertions.empty() || !dst_deletions.empty())
        {
            dst_insertions.m_u2 = src_insertions.m_u2;
            cxAssert(dst_insertions.m_u2 >= 0);
            
            // Write entry to the archive
            dw.Write(ar,fid);
            dw.Write(ar,dst_insertions);
            dw.Write(ar,dst_buffer);
            dw.Write(ar,dst_deletions);
            
            if (tf_VectorDeltaWriter)
            {
                Tracer() << "dst_insertions = " << dst_insertions << '\n';
                Tracer() << "dst_buffer = " << dst_buffer << '\n';
                Tracer() << "dst_deletions = " << dst_deletions << '\n';
            }
        }
    }
    dw.Write(ar,FieldId());     // Terminate archive with null FieldId

    m_fids.clear();
}



} // namespace ceda

