#include "bingo_fingerprint_table.h"
#include "bingo_container_set.h"

#include <iostream>

using namespace bingo;

FingerprintTable::FingerprintTable(int fp_size, const Array<int>& borders, int mt_size) : _table(100), _fp_size(fp_size), _mt_size(mt_size)
{
    _table.resize(borders.size() - 1);

    profTimerStart(tfp, "FingerprintTable constructing");

    _max_cell_count = 100;

    for (int i = 0; i < _table.size(); i++)
    {
        {
            profTimerStart(tfp, "FingerprintTable element pushing");
            _table[i].setParams(_fp_size, mt_size, borders[i], borders[i + 1] - 1);
        }
    }
}

BingoAddr FingerprintTable::create(BingoPtr<FingerprintTable>& ptr, int fp_size, int mt_size)
{
    Array<int> borders;

    borders.push(0);
    borders.push(fp_size * 8 + 1);

    ptr.allocate();
    new (ptr.ptr()) FingerprintTable(fp_size, borders, mt_size);

    return (BingoAddr)ptr;
}

void FingerprintTable::load(BingoPtr<FingerprintTable>& ptr, BingoAddr offset)
{
    ptr = BingoPtr<FingerprintTable>(offset);
}

void FingerprintTable::add(const byte* fingerprint, int id)
{
    int fp_bit_count = bitGetOnesCount(fingerprint, _fp_size);

    for (int i = 0; i < _table.size(); i++)
    {
        if ((fp_bit_count >= _table[i].getMinBorder()) && (fp_bit_count <= _table[i].getMaxBorder()))
        {
            if (_table[i].add(fingerprint, id))
            {
                if (_table[i].getMinBorder() == _table[i].getMaxBorder() || _table[i].getContCount() > 1 || _table.size() >= _max_cell_count)
                    _table[i].buildContainer();
                else
                {
                    _table.resize(_table.size() + 1);
                    for (int j = _table.size() - 2; j >= i + 1; j--)
                        _table[j + 1] = _table[j];

                    _table[i + 1].setParams(_fp_size, _mt_size, -1, -1);
                    _table[i].splitSet(_table[i + 1]);
                }
            }

            break;
        }
    }
}

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

    int query_bit_number = bitGetOnesCount(query, _fp_size);

    QS_DEF(Array<SimResult>, cell_sim_indices);
    for (int i = 0; i < _table.size(); i++)
    {
        if (sim_coef.calcUpperBound(query_bit_number, _table[i].getMinBorder(), _table[i].getMaxBorder()) < min_coef)
            continue;

        cell_sim_indices.clear();
        _table[i].findSimilar(query, sim_coef, min_coef, cell_sim_indices);

        sim_fp_indices.concat(cell_sim_indices);
    }
}

void FingerprintTable::optimize()
{
    for (int i = 0; i < _table.size(); i++)
        _table[i].optimize();
}

int FingerprintTable::getCellCount() const
{
    return _table.size();
}

int FingerprintTable::getCellSize(int cell_idx) const
{
    if (cell_idx >= _table.size())
        throw Exception("FingerprintTable: Incorrect cell index");

    return _table[cell_idx].getContCount();
}

void FingerprintTable::getCellsInterval(const byte* query, SimCoef& sim_coef, double min_coef, int& min_cell, int& max_cell)
{
    min_cell = -1;
    max_cell = -1;
    int query_bit_count = bitGetOnesCount(query, _fp_size);

    for (int i = 0; i < _table.size(); i++)
    {
        if ((min_cell == -1) && (sim_coef.calcUpperBound(query_bit_count, _table[i].getMinBorder(), _table[i].getMaxBorder()) > min_coef))
            min_cell = i;

        if ((min_cell != -1) && (sim_coef.calcUpperBound(query_bit_count, _table[i].getMinBorder(), _table[i].getMaxBorder()) > min_coef))
            max_cell = i;
    }
}

int FingerprintTable::firstFitCell(int query_bit_count, int min_cell, int max_cell) const
{
    int first_cell = -1;

    for (int i = min_cell; i <= max_cell; i++)
    {
        if (query_bit_count >= _table[i].getMinBorder() && query_bit_count <= _table[i].getMaxBorder())
            first_cell = i;
    }

    return first_cell;
}

int FingerprintTable::nextFitCell(int query_bit_count, int first_fit_cell, int min_cell, int max_cell, int idx) const
{
    int next_idx;

    if (first_fit_cell == idx)
        next_idx = first_fit_cell + 1;
    else if (first_fit_cell < idx)
        next_idx = first_fit_cell - (idx - first_fit_cell);
    else
        next_idx = first_fit_cell + (first_fit_cell - idx) + 1;

    if (next_idx < min_cell || next_idx > max_cell)
    {
        if (idx < min_cell || idx > max_cell)
            return -1;
        else
            return nextFitCell(query_bit_count, first_fit_cell, min_cell, max_cell, next_idx);
    }

    return next_idx;
}

int FingerprintTable::getSimilar(const byte* query, SimCoef& sim_coef, double min_coef, Array<SimResult>& sim_fp_indices, int cell_idx, int cont_idx)
{
    if (cell_idx >= _table.size())
        throw Exception("FingerprintTable: Incorrect cell index");

    int query_bit_number = bitGetOnesCount(query, _fp_size);

    if (sim_coef.calcUpperBound(query_bit_number, _table[cell_idx].getMinBorder(), _table[cell_idx].getMaxBorder()) < min_coef)
        return 0;

    _table[cell_idx].getSimilar(query, sim_coef, min_coef, sim_fp_indices, cont_idx);

    return sim_fp_indices.size();
}

FingerprintTable::~FingerprintTable()
{
}