#include "bingo_container_set.h"
#include "bingo_multibit_tree.h"

using namespace bingo;

ContainerSet::ContainerSet()
{
    _inc_count = 0;
    _inc_total_ones_count = 0;
}

void ContainerSet::setParams(int fp_size, int container_size, int min_ones_count, int max_ones_count)
{
    profTimerStart(t, "cs_set_params");
    _fp_size = fp_size;
    _min_ones_count = min_ones_count;
    _max_ones_count = max_ones_count;
    _container_size = container_size;

    _increment.allocate(_container_size * _fp_size);
    _indices.allocate(_container_size);
}

int ContainerSet::getContCount() const
{
    return _set.size() + 1;
}

int ContainerSet::getMinBorder() const
{
    return _min_ones_count;
}

int ContainerSet::getMaxBorder() const
{
    return _max_ones_count;
}

bool ContainerSet::add(const byte* fingerprint, int id, int fp_ones_count)
{
    if (_inc_count == _container_size)
        throw Exception("ContainerSet: Increment is full");

    byte* inc = _increment.ptr();
    int* indices = _indices.ptr();

    memcpy(inc + _inc_count * _fp_size, fingerprint, _fp_size);
    indices[_inc_count] = id;
    _inc_total_ones_count += (fp_ones_count == -1 ? bitGetOnesCount(fingerprint, _fp_size) : fp_ones_count);
    _inc_count++;

    if (_inc_count == _container_size)
        return true;

    return false;
}

void ContainerSet::buildContainer()
{
    profIncCounter("trees_count", 1);

    MultibitTree& cont = _set.push<int>(_fp_size);

    cont.build(_increment, _indices, _container_size, _min_ones_count, _max_ones_count);
    _increment.allocate(_container_size * _fp_size);
    _indices.allocate(_container_size);

    _inc_count = 0;
}

void ContainerSet::splitSet(ContainerSet& new_set)
{
    if (_set.size() > 0)
        throw Exception("ContainerSet: Set with built containers can't be splited");

    int new_border = (_inc_total_ones_count / _inc_count) + 1;

    int inc_count_cur = 0;

    new_set._inc_count = 0;

    _inc_total_ones_count = 0;
    new_set._inc_total_ones_count = 0;
    for (int i = 0; i < _inc_count; i++)
    {
        int ones_count = bitGetOnesCount(_increment.ptr() + i * _fp_size, _fp_size);

        if (ones_count < new_border)
        {
            memcpy(_increment.ptr() + inc_count_cur * _fp_size, _increment.ptr() + i * _fp_size, _fp_size);
            _indices[inc_count_cur] = (int)_indices[i];
            inc_count_cur++;
            _inc_total_ones_count += ones_count;
        }
        else
        {
            memcpy(new_set._increment.ptr() + new_set._inc_count * _fp_size, _increment.ptr() + i * _fp_size, _fp_size);
            new_set._indices[new_set._inc_count] = (int)_indices[i];
            new_set._inc_count++;
            new_set._inc_total_ones_count += ones_count;
        }
    }

    new_set._min_ones_count = new_border;
    new_set._max_ones_count = _max_ones_count;

    _max_ones_count = new_border - 1;

    _inc_count = inc_count_cur;
}

void ContainerSet::findSimilar(const byte* query, SimCoef& sim_coef, double min_coef, Array<SimResult>& sim_indices)
{
    sim_indices.clear();

    static int idx = 0;

    // int query_bit_number = bitGetOnesCount(query, _fp_size);

    QS_DEF(Array<SimResult>, cell_sim_indices);
    for (int i = 0; i < _set.size(); i++)
    {
        MultibitTree& container = _set[i];
        cell_sim_indices.clear();
        container.findSimilar(query, sim_coef, min_coef, cell_sim_indices);

        sim_indices.concat(cell_sim_indices);
    }

    cell_sim_indices.clear();
    _findSimilarInc(query, sim_coef, min_coef, cell_sim_indices);
    sim_indices.concat(cell_sim_indices);

    idx++;
}

void ContainerSet::optimize()
{
    if (_inc_count < _container_size / 10)
        return;

    profIncCounter("trees_count", 1);

    MultibitTree& cont = _set.push<int>(_fp_size);
    cont.build(_increment, _indices, _inc_count, _min_ones_count, _max_ones_count);
    _increment.allocate(_container_size * _fp_size);
    _indices.allocate(_container_size);
    _inc_count = 0;
}

int ContainerSet::getSimilar(const byte* query, SimCoef& sim_coef, double min_coef, Array<SimResult>& sim_fp_indices, int cont_idx)
{
    profTimerStart(cs_s, "getSimilar");

    if (cont_idx >= getContCount())
        throw Exception("ContainerSet: Incorrect container index");

    if (cont_idx == _set.size())
    {
        {
            profTimerStart(cs_s, "inc_findSimilar");
            _findSimilarInc(query, sim_coef, min_coef, sim_fp_indices);
            profIncCounter("inc_findSimilar_count", sim_fp_indices.size());
        }

        return sim_fp_indices.size();
    }

    MultibitTree& container = _set[cont_idx];

    {
        profTimerStart(cs_s, "set_findSimilar");
        container.findSimilar(query, sim_coef, min_coef, sim_fp_indices);
        profIncCounter("set_findSimilar_count", sim_fp_indices.size());
    }

    return sim_fp_indices.size();
}

int ContainerSet::_findSimilarInc(const byte* query, SimCoef& sim_coef, double min_coef, Array<SimResult>& sim_indices)
{
    byte* inc = _increment.ptr();
    int* indices = _indices.ptr();

    sim_indices.clear();

    int query_bit_number = bitGetOnesCount(query, _fp_size);

    for (int i = 0; i < _inc_count; i++)
    {
        byte* fp = inc + i * _fp_size;
        int fp_bit_number = bitGetOnesCount(fp, _fp_size);

        double coef = sim_coef.calcCoef(fp, query, query_bit_number, fp_bit_number);
        if (coef < min_coef)
            continue;

        sim_indices.push(SimResult(indices[i], (float)coef));
    }

    return sim_indices.size();
}