#include "codedblock.hh"
#include "matrix.hh"
#include "morepacket.hh"
#include <click/straccum.hh>

CLICK_DECLS

/* do you want to process the data blocks not just the coefficients? */
#define PROCESS_DATA 0

GaloisField<8> gf256;


/**
 * withData => uniquiefy p and take its data
 */
CodedBlock::CodedBlock(Packet *p, bool withData) {
    static int WARNINGS = 0;
    // read more_block
    more_block *mb = (more_block *)p->data();
    _k = mb->_k;

    if(withData){
        if(p->shared() && WARNINGS < 5) {
            ++WARNINGS;
            click_chatter("Warning: uniqueify in CodedBlock() is expensive.");
        }
        WritablePacket *q = p->uniqueify();
        q->pull(sizeof(more_block));

        // store coeffs in packet
        _coeff = (uchar *)q->data();
        q->pull(_k);
        _datasize = mb->_datasize;
        assert(_datasize <= (int)q->length());
        //q->take(q->length() - _datasize);
        _data = q->data();
        q->push(_k + sizeof(more_block));
        _p = q; // NOTE: NOT A CLONE
    } else {
        _coeff = new uchar[_k];
        memcpy(_coeff, (mb + 1), _k);
        _datasize = 0;
        _data = 0;
        _p = 0;
    }
    update();
}

/**
 * setup the underlying packet
 * if p is given then it should point at data
 */
void CodedBlock::setup(int k, int size, WritablePacket *p) {
    _k = k;
    _datasize = size;
    if(size > 0) {
        if(p == 0) {
            p = Packet::make(MORE_HEADROOM
                            + sizeof(more_pkt)
                            + sizeof(more_data)
                            + sizeof(more_block)
                            + _k, 0, _datasize, 0);
            // clear contents
            memset(p->data(), 0, _datasize);
        } else {
            massert(p->length() >= (uint)_datasize, "%d >= %d", p->length(), (uint)_datasize);
        }
        _data = p->data();

        p->push(_k);

        _coeff = (uchar *)(p->data());

        p->push(sizeof(more_block));
        more_block *mb = (more_block *)p->data();
        mb->_datasize = _datasize;
        mb->_k = _k;
        _p = p;
    } else {
        _coeff = new uchar[_k];
        _data = 0;
        _p = 0;
    }
}


/**
 * Source a block given the packet
 * encapsulate a data block into a CodedBlock
 * NOTE: we cannot adjust _datasize here.
 * _datasize should always be the MTU in batch
 */
CodedBlock::CodedBlock(int k, int slot, int datasize, WritablePacket * p) {
    massert(slot < k, "%d < %d", slot, k);

    if(p) {
        uint16_t sz = p->length();
        p = p->push(sizeof(uint16_t));
        // the first word (uint16_t) is the size of the data
        *(uint16_t *)(p->data()) = sz;
    }
    setup(k, datasize, p);
    memset(_coeff, 0, _k);
    _coeff[slot] = 1;
    _first = slot;
    _degree = 1;
}

/**
 * copy coefficients only
 */
CodedBlock::CodedBlock(const CodedBlock &cb) :
        _k(cb._k),
        _first(cb._first),
        _degree(cb._degree),
        _datasize(0),
        _data(0),
        _p(0)
{
    _coeff = new uchar[_k];
    memcpy(_coeff, cb._coeff, _k);
}

CodedBlock::~CodedBlock() {
    // datafull = we store coeffs in Packet
    if(!_data)
        delete[] _coeff;

    if(_p)
        _p->kill();
}



/**
 * Decapsulate the data block
 */
Packet *CodedBlock::sink() const {
#if PROCESS_DATA
    assert(_p);
    Packet * p = _p; _p = 0;
    // move over to the actual datablock
    p->pull(p->length() - _datasize + sizeof(uint16_t));
    // read actual inner datasize
    uint len = *(uint16_t *)_data;
    // trim to the inner datablock
    p->take(p->length() - len);
    return p;
#else
    Packet * p = Packet::make(0);
//    p->set_timestamp_anno(Timestamp::now());
    return p;
#endif
}


/**
 * this += a * cb
 * in the finite field
 */
void CodedBlock::add(const CodedBlock &cb, uchar a) {
    massert(a != 0, "%d", a);
    // combine the encoding vectors
    int i = cb._first;
    uchar *d = _coeff + i;
    uchar *s = cb._coeff + i;

    //click_chatter("%s + %d * \n%s = \n", print().c_str(), (uint)a, cb.print().c_str());
    for(; i < _k; ++i, ++d, ++s) {
        *d ^= __field_mul(*s, a);
    }
    update();
    //click_chatter("%s\n", print().c_str());

    if(_datasize == 0)
        // no data, we're done
        return;

    massert(_datasize == cb._datasize, "%d == %d", _datasize, cb._datasize);

    // if we have a packet, it must not be shared
    assert(!_p || !_p->shared());

#if PROCESS_DATA
    // now do the same thing on the data
    d = _data;
    s = cb._data;

    for(i = cb._datasize; i > 0; --i, ++s, ++d) {
        *d ^= __field_mul(*s, a);
    }
#endif
}


/**
 *  this /= a
 */
void CodedBlock::div(uchar a) {
    massert(a != 0, "%d", a);
    if (a == 1)
        return;
    a = __field_inv(a);
    uchar *d = _coeff + _first;
    for(int i = _first; i < _k; ++i, ++d) {
        *d = __field_mul(*d, a);
    }
    assert(!_p || !_p->shared());
#if PROCESS_DATA
    d = _data;
    for(int i = _datasize; i > 0; --i, ++d) {
        *d = __field_mul(*d, a);
    }
#endif
}

/**
 * do GE on block using the PartialGEMatrix
 */
void CodedBlock::add_all(const PartialGEMatrix &m) {
    for(int i = _first; i < _k; ++i) {
        if(_coeff[i] && m[i]) {
            add(*m[i], _coeff[i]);
        }
    }
}

String CodedBlock::print() const {
    StringAccum sa;
    for (int i = 0; i < _k; ++i ) {
        if(i == _first)
            sa << ">";
        else
            sa << " ";
        sa << (uint)_coeff[i];
    }
    sa << " " << _datasize;
    return sa.take_string();
}


CLICK_ENDDECLS
ELEMENT_PROVIDES(CodedBlock)

