//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Device/Data/DataUtil.cpp
//! @brief     Implements namespace DataUtil.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "Device/Data/DataUtil.h"
#include "Base/Axis/Frame.h"
#include "Base/Axis/Scale.h"
#include "Base/Math/Numeric.h"
#include "Base/Util/Assert.h"
#include "Device/Data/Datafield.h"
#include <algorithm>
#include <cmath>

double2d_t DataUtil::invertAxis(int axis, const double2d_t& original)
{
    double2d_t inverse = original;

    const size_t orig_rows = original.size();

    if (axis == 1) {
        for (size_t i = 0; i < orig_rows; ++i)
            inverse[i] = original[orig_rows - i - 1];
    } else if (axis == 0) {
        ASSERT(orig_rows > 0);
        const size_t orig_cols = original.front().size();
        for (size_t i = 0; i < orig_rows; ++i)
            for (size_t j = 0; j < orig_cols; ++j)
                inverse[i][j] = original[i][orig_cols - j - 1];
    } else
        ASSERT_NEVER;

    return inverse;
}

double2d_t DataUtil::transpose(const double2d_t& original)
{
    ASSERT(!original.empty());

    const size_t orig_rows = original.size();
    const size_t orig_cols = original.front().size();

    double2d_t transposed(orig_cols, std::vector<double>(orig_rows));

    for (size_t i = 0; i < orig_rows; ++i)
        for (size_t j = 0; j < orig_cols; ++j)
            transposed[j][i] = original[i][j];

    return transposed;
}

Datafield DataUtil::rotatedDatafield(const Datafield& data, int n)
{
    ASSERT(data.rank() == 2);
    n = (n % 4 + 4) & 3; // modulo 4, also for negative arguments
    if (n == 0)
        return data;

    const size_t N = data.size();
    const size_t nx = data.axis(0).size();
    const size_t ny = data.axis(1).size();
    std::vector<double> out(N);

    if (n == 1) {
        for (size_t iy = 0; iy < ny; ++iy)     // out-y along out-axis 0
            for (size_t ix = 0; ix < nx; ++ix) // out-x along out-axis 1
                out[(ny - 1 - iy) * nx + ix] = data[ix * ny + iy];
        return {std::vector<const Scale*>{data.axis(1).clone(), data.axis(0).clone()}, out};

    } else if (n == 2) {
        for (size_t i = 0; i < N; ++i)
            out[i] = data[N - 1 - i];
        return {std::vector<const Scale*>{data.axis(0).clone(), data.axis(1).clone()}, out};

    } else if (n == 3) {
        for (size_t iy = 0; iy < ny; ++iy)     // out-y along out-axis 0
            for (size_t ix = 0; ix < nx; ++ix) // out-x along out-axis 1
                out[iy * nx + (nx - 1 - ix)] = data[ix * ny + iy];
        return {std::vector<const Scale*>{data.axis(1).clone(), data.axis(0).clone()}, out};
    }
    ASSERT_NEVER;
}

Datafield DataUtil::relativeDifferenceField(const Datafield& dat, const Datafield& ref)
{
    ASSERT(dat.frame().hasSameSizes(ref.frame()));
    std::vector<double> out(dat.size());
    for (size_t i = 0; i < dat.size(); ++i)
        out[i] = Numeric::relativeDifference(dat[i], ref[i]);
    return {dat.frame(), out};
}
