/**
 *
 * @file plugins/MatrixVisualizer/Common/Quadtree.cpp
 *
 * @copyright 2008-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 *
 * @author Camille Ordronneau
 *
 * @date 2024-07-17
 */
#include <assert.h>
#include <cmath>

#include "Quadtree.hpp"

/**
 * This constant is the precision where the algorithm stops slicing the matrix
 */
#define PRECISION 1.e-15

Quadtree::Quadtree(symbol_matrix_t *matrix) :
    Zoom(matrix) {
    // Fill correct colors
    move(0.f, 1.f, 0.f, 1.f);
}

Quadtree::~Quadtree() { }

void Quadtree::move(double x_start, double x_end, double y_start, double y_end) {
    // Check positions
    assert(x_end >= x_start);
    assert(y_end >= y_start);
    assert(y_end >= x_start);

    // Check for out of bounds
    assert(x_start >= 0.);
    assert(y_start >= 0.);
    assert(x_end <= 1.);
    assert(y_end <= 1.);

    sliced_matrix_t m;

    // Convert to column/row indexes
    m.start_col = x_start * m_matrix->m_colsnbr;
    m.end_col = x_end * m_matrix->m_colsnbr - 1;
    m.start_row = y_start * m_matrix->m_rowsnbr;
    m.end_row = y_end * m_matrix->m_rowsnbr - 1;
    m.start_cblk = 0;
    m.end_cblk = m_matrix->m_cblknbr - 1;
    m.m_matrix = m_matrix;

    this->start_col = m.start_col;
    this->start_row = m.start_row;
    this->end_col = m.end_col;
    this->end_row = m.end_row;

    int nb_cols = m.end_col - m.start_col + 1;
    int nb_rows = m.end_row - m.start_row + 1;

    float y_coeff = (float)DEFAULT_LEVEL / ((float)nb_rows);
    float x_coeff = (float)DEFAULT_LEVEL / ((float)nb_cols);

    // fill the color matrix with white color
    for (int i = 0; i < DEFAULT_LEVEL; ++i) {
        for (int j = 0; j < DEFAULT_LEVEL; ++j) {
            m_colors[i][j] = 1.f;
        }
    }

    // if the size of the matrix is less than DEFAULT_LEVEL, the zooming method is used
    if (nb_cols < DEFAULT_LEVEL || nb_rows < DEFAULT_LEVEL) {
        symbol_cblk_t *cblk;
        symbol_blok_t *blok;
        int start_cblk = 0;
        int end_cblk = 0;
        cblk = m_matrix->m_cblktab;
        for (int i = 0; i < m_matrix->m_cblknbr; ++i, cblk++) {
            if ((cblk->m_fcolnum <= start_col) && (start_col <= cblk->m_lcolnum)) {
                start_cblk = i;
                start_col = cblk->m_fcolnum;
            }
            if ((cblk->m_fcolnum <= start_row) && (start_row <= cblk->m_lcolnum)) {
                start_row = cblk->m_fcolnum;
            }
            if ((cblk->m_fcolnum <= end_col) && (end_col <= cblk->m_lcolnum)) {
                end_cblk = i + 1;
                end_col = cblk->m_lcolnum;
            }
            if ((cblk->m_fcolnum <= end_row) && (end_row <= cblk->m_lcolnum)) {
                end_row = cblk->m_lcolnum;
            }
        }

        nb_cols = end_col - start_col + 1;
        nb_rows = end_row - start_row + 1;

        x_coeff = (float)DEFAULT_LEVEL / ((float)nb_cols);
        y_coeff = (float)DEFAULT_LEVEL / ((float)nb_rows);

        cblk = m_matrix->m_cblktab + start_cblk;
        for (int i = start_cblk; i < end_cblk; ++i, cblk++) {
            int fbloknum = cblk[0].m_bloknum;
            int lbloknum = cblk[1].m_bloknum;

            // Get first block size in col from x to x_end
            int xbegin = (cblk->m_fcolnum - start_col) * x_coeff;
            int xend = (cblk->m_lcolnum + 1 - start_col) * x_coeff;

            float cblk_color = cblk->m_color;

            blok = m_matrix->m_bloktab + fbloknum;
            for (int j = fbloknum; j < lbloknum; ++j, blok++) {
                if ((blok->m_lrownum < start_row) || (blok->m_frownum > end_row)) {
                    continue;
                }

                // Get first block size in row from y to y_end
                int ybegin = (blok->m_frownum - start_row) * y_coeff;
                int yend = (blok->m_lrownum + 1 - start_row) * y_coeff;

                float color = blok->m_color == -1. ? cblk_color : blok->m_color;

                for (int m = xbegin; m < xend; m++) {
                    for (int n = ybegin; n < yend; n++) {
                        m_colors[m][n] = color;
                    }
                }
            }
        }
    }
    // else the quadtree method is used
    else {
        createTiles(m, x_coeff, y_coeff);
    }
}

void Quadtree::createTiles(sliced_matrix_t &m, float x_coeff, float y_coeff) {
    symbol_cblk_t *cblk;
    int start_cblk = m.start_cblk;
    int end_cblk = m.end_cblk;

    // Slice the matrix into 4 sliced matrixes
    for (int i = m.start_row; i < m.end_row; i += (m.end_row - m.start_row + 1) / 2) {
        for (int j = m.start_col; j < m.end_col; j += (m.end_col - m.start_col + 1) / 2) {
            sliced_matrix_t new_matrix;
            new_matrix.m_matrix = m.m_matrix;
            // Fist row and column of the new matrix
            new_matrix.start_row = i;
            new_matrix.start_col = j;

            // Calcute the last row and column of the new matrix
            if (i + ((m.end_row - m.start_row + 1) / 2) == m.end_row) {
                new_matrix.end_row = m.end_row;
            }
            else {
                new_matrix.end_row = i + ((m.end_row - m.start_row + 1) / 2) - 1;
            }

            if (j + ((m.end_col - m.start_col + 1) / 2) == m.end_col) {
                new_matrix.end_col = m.end_col;
            }
            else {
                new_matrix.end_col = j + ((m.end_col - m.start_col + 1) / 2) - 1;
            }

            cblk = new_matrix.m_matrix->m_cblktab;
            // first and last column block of the new matrix
            for (int k = 0; k < new_matrix.m_matrix->m_cblknbr; ++k, cblk++) {
                if ((cblk->m_fcolnum <= new_matrix.start_col) && (new_matrix.start_col <= cblk->m_lcolnum)) {
                    start_cblk = k;
                }
                if ((cblk->m_fcolnum <= new_matrix.end_col) && (new_matrix.end_col <= cblk->m_lcolnum)) {
                    end_cblk = k;
                }
            }
            new_matrix.start_cblk = start_cblk;
            new_matrix.end_cblk = end_cblk;

            // Calculate the average color
            new_matrix.color_avg = average(new_matrix);
            // Calculate the color error
            float color_error = error(new_matrix);
            if (color_error <= PRECISION || ((new_matrix.end_row - new_matrix.start_row) / 2) * y_coeff <= 1 || ((new_matrix.end_col - new_matrix.start_col) / 2) * x_coeff <= 1) {
                int x, y, x_end, y_end;

                // Get the start and the end of the zone to be fill in the color matrix
                x = (new_matrix.start_col - this->start_col) * x_coeff;
                x_end = (new_matrix.end_col - this->start_col) * x_coeff;

                y_end = (new_matrix.end_row - this->start_row) * y_coeff;
                y = (new_matrix.start_row - this->start_row) * y_coeff;

                // Fill the color matrix
                for (int m = x; m <= x_end; m++) {
                    for (int n = y; n <= y_end; n++) {
                        m_colors[m][n] = new_matrix.color_avg;
                    }
                }
            }
            else {
                // recursive call which means reslice the matrix again
                createTiles(new_matrix, x_coeff, y_coeff);
            }
        }
    }
}

float Quadtree::average(sliced_matrix_t &m) {
    symbol_cblk_t *cblk;
    symbol_blok_t *blok;
    int start_cblk = m.start_cblk;
    int end_cblk = m.end_cblk;
    cblk = m.m_matrix->m_cblktab + start_cblk;
    float average = 0.;
    for (int i = start_cblk; i <= end_cblk; ++i, cblk++) {

        int x = 0, x_end = 0;

        // Get first block size in col from x to x_end
        if (i == start_cblk && i != end_cblk) {
            x = m.start_col;
            x_end = cblk->m_lcolnum;
        }
        else if (i == end_cblk && i != start_cblk) {
            x = cblk->m_fcolnum;
            x_end = m.end_col;
        }
        else if (i == end_cblk && i == start_cblk) {
            x = m.start_col;
            x_end = m.end_col;
        }
        else {
            x = cblk->m_fcolnum;
            x_end = cblk->m_lcolnum;
        }

        float cblk_color = cblk->m_color;
        int fbloknum = cblk[0].m_bloknum;
        int lbloknum = cblk[1].m_bloknum;

        blok = m.m_matrix->m_bloktab + fbloknum;
        float somme = 0.;
        int y = 0, y_end = 0;
        for (int j = fbloknum; j < lbloknum; ++j, blok++) {
            if ((blok->m_lrownum < m.start_row) || (blok->m_frownum > m.end_row)) {
                continue;
            }

            // Get first block size in row from y to y_end
            if ((blok->m_lrownum >= m.start_row) && (blok->m_lrownum < m.end_row)) {
                y = m.start_row;
                y_end = blok->m_lrownum;
            }
            else if ((blok->m_frownum <= m.end_row) && (blok->m_frownum > m.start_row)) {
                y = blok->m_frownum;
                y_end = m.end_row;
            }
            else if ((blok->m_frownum < m.start_row) && (blok->m_lrownum > m.end_row)) {
                y = m.start_row;
                y_end = m.end_row;
            }
            else {
                y = blok->m_frownum;
                y_end = blok->m_lrownum;
            }

            float color = blok->m_color == -1. ? cblk_color : blok->m_color;

            somme += (y_end - y + 1) * (x_end - x + 1);
            average += (float)((y_end - y + 1) * (x_end - x + 1) * color) / ((m.end_col - m.start_col) * (m.end_row - m.start_row));
        }
        average += (float)(((x_end - x + 1) * (m.end_row - m.start_row) - somme) * 1.f) / ((m.end_col - m.start_col) * (m.end_row - m.start_row));
    }
    if (average >= 1.f) {
        return 1.f;
    }
    return average;
}

float Quadtree::error(sliced_matrix_t &m) {
    symbol_cblk_t *cblk;
    symbol_blok_t *blok;
    int start_cblk = m.start_cblk;
    int end_cblk = m.end_cblk;
    cblk = m.m_matrix->m_cblktab + start_cblk;
    float err = 0.;
    for (int i = start_cblk; i <= end_cblk; ++i, cblk++) {

        int x = 0, x_end = 0;

        // Get first block size in col from x to x_end
        if (i == start_cblk && i != end_cblk) {
            x = m.start_col;
            x_end = cblk->m_lcolnum;
        }
        else if (i == end_cblk && i != start_cblk) {
            x = cblk->m_fcolnum;
            x_end = m.end_col;
        }
        else if (i == end_cblk && i == start_cblk) {
            x = m.start_col;
            x_end = m.end_col;
        }
        else {
            x = cblk->m_fcolnum;
            x_end = cblk->m_lcolnum;
        }

        float cblk_color = cblk->m_color;
        int fbloknum = cblk[0].m_bloknum;
        int lbloknum = cblk[1].m_bloknum;

        blok = m.m_matrix->m_bloktab + fbloknum;
        float somme = 0.;
        int y = 0, y_end = 0;
        for (int j = fbloknum; j < lbloknum; ++j, blok++) {
            if ((blok->m_lrownum < m.start_row) || (blok->m_frownum > m.end_row)) {
                continue;
            }

            // Get first block size in row from y to y_end
            if ((blok->m_lrownum >= m.start_row) && (blok->m_lrownum < m.end_row)) {
                y = m.start_row;
                y_end = blok->m_lrownum;
            }
            else if ((blok->m_frownum <= m.end_row) && (blok->m_frownum > m.start_row)) {
                y = blok->m_frownum;
                y_end = m.end_row;
            }
            else if ((blok->m_frownum < m.start_row) && (blok->m_lrownum > m.end_row)) {
                y = m.start_row;
                y_end = m.end_row;
            }
            else {
                y = blok->m_frownum;
                y_end = blok->m_lrownum;
            }

            float color = blok->m_color == -1. ? cblk_color : blok->m_color;

            somme += (y_end - y + 1) * (x_end - x + 1);
            err += (float)((y_end - y + 1) * (x_end - x + 1) * (color - m.color_avg)) / (float)((m.end_col - m.start_col) * (m.end_row - m.start_row));
        }
        err += ((float)(((x_end - x + 1) * (m.end_row - m.start_row) - somme) * (1.f - m.color_avg)) / (float)((m.end_col - m.start_col) * (m.end_row - m.start_row)));
    }
    return fabs(err);
}
