// SingleCharMidOp.cpp
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2006

#include "StdAfx.h"
#include "Ceda/Core/cxUtils/PseudoRandom.h"
#include "Ceda/Core/cxUtils/CedaAssert.h"
#include "Ceda/Core/cxUtils/Tracer.h"
#include "SingleCharMidOp.h"


xchar GetCycledLowercaseChar();

extern bool gp_debug;

using namespace ceda;


///////////////////////////////////////////////////////////////////////////////////////////////////
// SingleCharMidOp

SingleCharMidOp::SingleCharMidOp() : 
    e(-1),
    si(-1), sq(-1),
    di(-1), dq(-1),
    c('?')
{
}

bool SingleCharMidOp::operator==(const SingleCharMidOp& rhs) const
{ 
    return m_opid == rhs.m_opid &&
           e == rhs.e &&
           si == rhs.si &&
           sq == rhs.sq &&
           di == rhs.di &&
           dq == rhs.dq &&
           c == rhs.c;
}

enum EOpType
{
    OT_MOVE,
    OT_INSERT,
    OT_DELETE,
};

void SingleCharMidOp::SetRandom(RandomInitSettings ris, const MidEffectsDocSet& ds, Opid opid)
{
    cxAssert(opid.id >= 0);
    cxAssert(opid.t >= 0);
    m_opid = opid;

    ssize_t numDocs = ds.size();
    cxAlwaysAssert(numDocs > 0);
    
    // Choose type of operation : 0 = move, 1 = insert, 2 = delete operation
    EOpType type;
    if (ds.HasCharsThatExist())
    {
        type = (EOpType) GetUniformDistInteger_ssize_t(0,3);
    }
    else
    {
        type = OT_INSERT;
    }

    if (type == OT_INSERT)
    {
        si = -1;
        sq = -1;
        c = GetCycledLowercaseChar();
    }
    else
    {
        while(1)
        {
            // Choose a source document that is non empty
            si = GetUniformDistInteger_ssize_t(0,numDocs);
            if (ds[si].sizeq() > 0)
            {
                // Pick a source character
                sq = GetUniformDistInteger_ssize_t(0,ds[si].sizeq());

                if (ds[si].CharExists(sq)) break;
            }
        }
        c = ds[si][sq];
    }
    
    if (type == OT_DELETE)
    {
        di = -1;
        dq = -1;
    }
    else
    {
        // Choose a destination document
        di = GetUniformDistInteger_ssize_t(0,numDocs);

        // Pick a destination position.  Note that there is no actual extraction so we don't have to
        // worry about self-interference.
        dq = GetUniformDistInteger_ssize_t(0,ds[di].sizeq() + 1);
    }

    e = 0; 

    if (type == OT_MOVE)
    {
        // Extraction must be in post-insertion coords
        if (di == si && dq <= sq) ++sq;
    }
}

void SingleCharMidOp::Do(MidEffectsDocSet& ds) const
{
    if (si == -1)
    {
        // Pure insert operation
        cxAssert(di != -1);
        cxAssert(dq != -1);
        ds[di].Insert(dq,c,true);
    }
    else if (di == -1)
    {
        // Pure delete operation
        cxAlwaysAssert(c == ds[si][sq]);
        cxAlwaysAssert(ds[si].IsPresent(sq));

        ds[si].SetDeleted(sq, true);
    }
    else
    {
        // Move operation
                
        ds[di].Insert(dq,c,e == 0);
        cxAlwaysAssert(c == ds[si][sq]);
        cxAlwaysAssert(ds[si].IsPresent(sq));
        
        if (e == 0)
        {
            cxAlwaysAssert(c == ds[si].Remove(sq));

            // Move the deleted status to the new location
            if (ds[si].IsDeleted(sq))
            {
                ds[si].SetDeleted(sq, false);
                ds[di].SetDeleted(dq, true);
            }
        }
    }
}

void SingleCharMidOp::Undo(MidEffectsDocSet& ds) const
{
    cxAlwaysAssert(0);
}

xostream& operator<<(xostream& os, const SingleCharMidOp& O)
{
    if (O.si == -1) 
    {
        os << 'I';
        os << '\'' << O.c << '\'';
        if (O.di)
        {
            os << '{' << O.di << '}';
        }
        os << O.dq;
    }
    else if (O.di == -1) 
    {
        os << 'D';
        os << '\'' << O.c << '\'';
        if (O.si)
        {
            os << '{' << O.si << '}';
        }
        os << O.sq;
    }
    else 
    {
        os << 'M';
        os << '\'' << O.c << '\'';
        if (O.e)
        {
            os << '<' << O.e << '>';
        }
        if (O.si)
        {
            os << '{' << O.si << '}';
        }
        os << O.sq;
        os << "->";
        if (O.di)
        {
            os << '{' << O.di << '}';
        }
        os << O.dq;
    }
        
    return os;
}

void IT(SingleCharMidOp& O1, const SingleCharMidOp& O2)
{
    cxAlwaysAssert(O1.GetId() != O2.GetId());

    SingleCharMidOp copyO2 = O2;
    DualIT(O1, copyO2);
}

void ET(SingleCharMidOp& O1, const SingleCharMidOp& O2)
{
    cxAlwaysAssert(O1.GetId() != O2.GetId());
    SingleCharMidOp copyO2 = O2;
    Transpose(copyO2, O1);
}

void DualIT(SingleCharMidOp& O1, SingleCharMidOp& O2)
{
    cxAlwaysAssert(O1.GetId() != O2.GetId());

    // O1, O2 are not pure delete operations
    if (O1.di != -1 && O1.di == O2.di)
    {
        if (O2.dq < O1.dq || O2.dq == O1.dq && O2.GetId() < O1.GetId()) ++O1.dq; else ++O2.dq;
    }
    
    // O1 is not a pure delete, O1 is not a pure insert
    if (O1.di != -1 && O1.di == O2.si && O1.dq <= O2.sq) ++O2.sq;
    
    // O2 is not a pure delete, O2 is not a pure insert
    if (O2.di != -1 && O2.di == O1.si && O2.dq <= O1.sq) ++O1.sq;
    
    // O1, O2 are not pure insert operations
    if (O1.si != -1 && O1.si == O2.si && O1.sq == O2.sq)
    {
        // O1 is enabled move op
        if (O1.IsMove() && O1.e == 0) { O2.si = O1.di; O2.sq = O1.dq; }
        
        // O2 is enabled move op
        if (O2.IsMove() && O2.e == 0) { O1.si = O2.di; O1.sq = O2.dq; }
        
        if (O1.IsMove() && O2.IsMove())
        {
            if (O2.e < O1.e || O2.e == O1.e && O2.GetId() < O1.GetId()) ++O1.e; else ++O2.e;
        }
    }
}

void Transpose(SingleCharMidOp& O1, SingleCharMidOp& O2)
{
    cxAlwaysAssert(O1.GetId() != O2.GetId());
    ssize_t prev2dq = O2.dq;
    if (O2.di != -1 && O2.di == O1.si && O2.dq <= O1.sq) ++O1.sq;
    if (O1.di != -1 && O1.di == O2.di) { if (O1.dq < O2.dq) --O2.dq; else ++O1.dq; }
    if (O1.si != -1 && O1.si == O2.si && O1.sq == O2.sq || O1.di != -1 && O1.di == O2.si && O1.dq == O2.sq)
    {
        if (O1.IsMove() && O1.e == 0) { O2.si = O1.si; O2.sq = O1.sq; }
        if (O1.IsMove() && O2.IsMove())
        {
            if (O1.e < O2.e) --O2.e; else ++O1.e;
            cxAlwaysAssert(O2.e >= 0);
        }
        if (O2.IsMove() && O2.e == 0) { O1.si = O2.di; O1.sq = prev2dq; }
    }
    if (O1.di != -1 && O1.di == O2.si && O1.dq < O2.sq) --O2.sq;
}

void Merge(SingleCharMidOp& O1, const SingleCharMidOp& O2)
{
    cxAssert(0);
}


