#include "SymbolMatrix.hpp"

#include "../Helper.hpp"

#include <cstdlib>
#include <cstring>
#include <cmath>

static inline int32_t imin(int32_t a, int32_t b)
{
    return (a < b) ? a : b;
}
static inline int32_t imax(int32_t a, int32_t b)
{
    return (a > b) ? a : b;
}

/* Macros */
#define memFree(ptr) free((void*)(ptr))
#define memFree_null(ptr) do			\
    {                                           \
        memFree( ptr );                         \
        (ptr) = nullptr;                        \
    } while(0)

void symbol_matrix_init(symbol_matrix_t* matrix)
{
    memset(matrix, 0, sizeof(symbol_matrix_t));
    return;
}

void symbol_matrix_deinit(symbol_matrix_t* matrix)
{
    if (matrix->m_sndetab != nullptr)
    {
        memFree_null(matrix->m_sndetab);
    }
    if (matrix->m_cblktab != nullptr)
    {
        memFree_null(matrix->m_cblktab);
    }
    if (matrix->m_bloktab != nullptr)
    {
        memFree_null(matrix->m_bloktab);
    }
    if (matrix->m_browtab != nullptr)
    {
        memFree_null(matrix->m_browtab);
    }

    memset(matrix, 0, sizeof(symbol_matrix_t));

    return;
}

#include <iostream>

void symbol_matrix_print_stats(symbol_matrix_t* matrix)
{
    symbol_cblk_t* cblk;
    symbol_blok_t* blok;
    int32_t itercblk, dof;
    int32_t sndenbr, cblknbr, bloknbr;
    int32_t cblkmin, cblkmax;
    int32_t blokmin, blokmax;
    double cblkavg1, blokavg1;
    double cblkavg2, blokavg2;
    size_t mem = 0;

    sndenbr     = matrix->m_sndenbr;
    cblknbr     = matrix->m_cblknbr;
    bloknbr     = matrix->m_bloknbr;
    cblkmin     = INT32_MAX;
    cblkmax     = 0;
    cblkavg1    = 0.0;
    cblkavg2    = 0.0;
    blokmin     = INT32_MAX;
    blokmax     = 0;
    blokavg1    = 0.0;
    blokavg2    = 0.0;

    cblk = matrix->m_cblktab;
    blok = matrix->m_bloktab;

    for (itercblk = 0; itercblk < cblknbr; ++itercblk, ++cblk)
    {
        int32_t iterblok = cblk[0].m_bloknum + 1;
        int32_t lbloknum = cblk[1].m_bloknum;

        int32_t colnbr = cblk->m_lcolnum - cblk->m_fcolnum + 1;

        cblkmin = imin(cblkmin, colnbr);
        cblkmax = imax(cblkmax, colnbr);
        cblkavg1 += colnbr;
        cblkavg2 += colnbr * colnbr;
        blok++;

        /* Only extra diagonal */
        for (; iterblok < lbloknum; ++iterblok, ++blok)
        {
            int32_t rownbr = blok->m_lrownum - blok->m_frownum + 1;

            blokmin = imin(blokmin, rownbr);
            blokmax = imax(blokmax, rownbr);
            blokavg1 += rownbr;
            blokavg2 += rownbr * rownbr;
        }
    }

    dof = matrix->m_dof;

    if (dof < 1)
    {
        Helper::log(LogStatus::WARNING, "DOF lower than 1 so statistics might be wrong !");
        dof = 1;
    }

    blokmin *= dof;
    blokmax *= dof;
    cblkmin *= dof;
    cblkmax *= dof;
    cblkavg1 = (cblkavg1 * (double)dof) / (double)cblknbr;
    blokavg1 = (blokavg1 * (double)dof) / (double)bloknbr;
    cblkavg2 = sqrt( ((cblkavg2 * (double)dof * (double)dof) / (double)cblknbr) - cblkavg1 * cblkavg1);
    blokavg2 = sqrt( ((blokavg2 * (double)dof * (double)dof) / (double)bloknbr) - blokavg1 * blokavg1);

    /* Compute symbol matrix space */
    mem  = sizeof(symbol_matrix_t);
    mem += sizeof(symbol_cblk_t) * (cblknbr + 1);
    mem += sizeof(symbol_blok_t) * matrix->m_bloknbr;
    mem += sizeof(int32_t) * bloknbr;

    Helper::set_infos(
        "Symbol Matrix statistics:\n"
        "      Number of superndoes              %10ld\n"
        "      Number of cblk                    %10ld\n"
        "      Number of blok                    %10ld\n"
        "      Cblk width min                    %10ld\n"
        "      Cblk width max                    %10ld\n"
        "      Cblk width avg                 %11.2lf\n"
        "      Cblk width stdev               %11.2lf\n"
        "      Blok height min                   %10ld\n"
        "      Blok height max                   %10ld\n"
        "      Blok height avg                %11.2lf\n"
        "      Blok height stdev              %11.2lf\n"
        "      Matrix structure space         %11.2lf %cB\n",
        (long)sndenbr, (long)cblknbr, (long)bloknbr,
        (long)cblkmin, (long)cblkmax, cblkavg1, cblkavg2,
        (long)blokmin, (long)blokmax, blokavg1, blokavg2,
        print_get_value( mem ), print_get_units( mem ) );
}
