//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Base/Axis/Frame.cpp
//! @brief     Implements class Frame.
//!
//! @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 "Base/Axis/Frame.h"
#include "Base/Axis/Scale.h"
#include "Base/Util/Assert.h"
#include "Base/Util/StringUtil.h"

namespace {

size_t product_size(const std::vector<const Scale*>& axes)
{
    size_t result = 1;
    for (const Scale* ax : axes)
        result *= ax->size();
    return result;
}

} // namespace


Frame::Frame(const std::vector<const Scale*>& axes)
    : m_axes(axes)
    , m_size(::product_size(axes))
{
}

Frame::Frame(const Scale* ax0)
    : Frame(std::vector<const Scale*>{ax0})
{
}

Frame::Frame(const Scale* ax0, const Scale* ax1)
    : Frame(std::vector<const Scale*>{ax0, ax1})
{
}

Frame::Frame(const Frame&) = default;

Frame::~Frame() = default;

Frame* Frame::clone() const
{
    return new Frame(*this);
}

size_t Frame::rank() const
{
    return m_axes.size();
}
const Scale& Frame::axis(size_t k_axis) const
{
    ASSERT(k_axis < rank());
    return *m_axes.at(k_axis);
}
const Scale& Frame::xAxis() const
{
    return *m_axes.at(0);
}
const Scale& Frame::yAxis() const
{
    ASSERT(1 < rank());
    return *m_axes.at(1);
}

size_t Frame::projectedSize(size_t k_axis) const
{
    ASSERT(k_axis < rank());
    return m_axes[k_axis]->size();
}

double Frame::projectedCoord(size_t i_flat, size_t k_axis) const
{
    return projectedBin(i_flat, k_axis).center();
}

const Bin1D& Frame::projectedBin(size_t i_flat, size_t k_axis) const
{
    auto axis_index = projectedIndex(i_flat, k_axis);
    return m_axes[k_axis]->bin(axis_index);
}

std::vector<int> Frame::allIndices(size_t i_flat) const
{
    std::vector<int> result(rank());
    for (size_t k = 0; k < rank(); ++k)
        result[k] = projectedIndex(i_flat, k);
    return result;
}

size_t Frame::projectedIndex(size_t i, size_t k_axis) const
{
    ASSERT(k_axis < rank());
    if (rank() == 1)
        return i;
    if (rank() == 2) {
        if (k_axis == 0)
            return i % m_axes[0]->size();
        if (k_axis == 1)
            return (i / m_axes[0]->size()) % m_axes[1]->size();
    }
    ASSERT_NEVER;
}

bool Frame::operator==(const Frame& o) const
{
    if (rank() != o.rank())
        return false;
    for (size_t k = 0; k < rank(); ++k)
        if (axis(k) != o.axis(k))
            return false;
    return true;
}

bool Frame::hasSameSizes(const Frame& o) const
{
    if (rank() != o.rank())
        return false;
    for (size_t k = 0; k < rank(); ++k)
        if (axis(k).size() != o.axis(k).size())
            return false;
    return true;
}

Frame* Frame::plottableFrame() const
{
    std::vector<const Scale*> outaxes;
    for (size_t k = 0; k < rank(); ++k) {
        auto* s = new Scale(axis(k).plottableScale());
        outaxes.emplace_back(s);
    }
    return new Frame(std::move(outaxes));
}

Frame* Frame::angularFrame(double lambda, double alpha_i) const
{
    ASSERT(rank() == 2);

    auto* phi_f_scale = new Scale(xAxis().phi_f_Scale(lambda));
    auto* alpha_f_scale = new Scale(yAxis().alpha_f_Scale(lambda, alpha_i));

    return new Frame(phi_f_scale, alpha_f_scale);
}

Frame* Frame::qSpaceFrame(double lambda, double alpha_i) const
{
    ASSERT(rank() == 2);

    auto* qy_scale = new Scale(xAxis().qy_Scale(lambda));
    auto* qz_scale = new Scale(yAxis().qz_Scale(lambda, alpha_i));

    return new Frame(qy_scale, qz_scale);
}

Frame* Frame::flat() const
{
    std::vector<const Scale*> outaxes;
    for (const Scale* s : m_axes)
        if (s->size() > 1)
            outaxes.emplace_back(s->clone());
    return new Frame(std::move(outaxes));
}
