//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Device/Detector/OffspecDetector.cpp
//! @brief     Implements class OffspecDetector.
//!
//! @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/Detector/OffspecDetector.h"
#include "Base/Axis/MakeScale.h"
#include "Base/Axis/Pixel.h"
#include "Base/Spin/SpinMatrix.h"
#include "Device/Pol/PolFilter.h"
#include <memory>

namespace {

Scale detEquiScale(size_t n_alpha, double alpha_min, double alpha_max)
{
    if (alpha_min >= alpha_max)
        throw std::runtime_error("Detector alpha: min >= max");
    return *std::unique_ptr<Scale>(EquiDivision("alpha_f (rad)", n_alpha, alpha_min, alpha_max));
}

Bin1D detBin(double phi_min, double phi_max)
{
    if (phi_min >= phi_max)
        throw std::runtime_error("Detector phi: min >= max");
    return Bin1D::FromTo(phi_min, phi_max);
}

} // namespace

OffspecDetector::OffspecDetector(size_t n_alpha, double alpha_min, double alpha_max, double phi_min,
                                 double phi_max)
    : m_alpha_axis(::detEquiScale(n_alpha, alpha_min, alpha_max))
    , m_phi_range(::detBin(phi_min, phi_max))
{
}

OffspecDetector::~OffspecDetector() = default;

void OffspecDetector::setAnalyzer(const R3& direction, double transmission_selected,
                                  double transmission_suppressed)
{
    m_pol_analyzer =
        std::make_unique<PolFilter>(direction, transmission_selected, transmission_suppressed);
}

OffspecDetector* OffspecDetector::clone() const
{
    auto* result = new OffspecDetector(m_alpha_axis.size(), m_alpha_axis.min(), m_alpha_axis.max(),
                                       m_phi_range.min(), m_phi_range.max());
    if (m_pol_analyzer)
        result->setAnalyzer(m_pol_analyzer->direction(), m_pol_analyzer->transmissionParallel(),
                            m_pol_analyzer->transmissionAntiparallel());
    return result;
}

std::unique_ptr<Pixel> OffspecDetector::createPixel(size_t alpha_index, double phi) const
{
    const Bin1D alpha_bin = m_alpha_axis.bin(alpha_index);
    const Bin1D phi_bin = Bin1D::At(phi, 0.5); // pixel width is 1, for the purpose of integration

    return std::make_unique<Pixel>(phi_bin, alpha_bin);
}

size_t OffspecDetector::aplhaIndexOfSpecular(double alpha_spec, double phi_spec) const
{
    if (phi_spec < m_phi_range.min() || m_phi_range.max() < phi_spec)
        return m_alpha_axis.size();

    if (!m_alpha_axis.rangeComprises(alpha_spec))
        return m_alpha_axis.size();

    return m_alpha_axis.closestIndex(alpha_spec);
}
SpinMatrix OffspecDetector::analyzerMatrix() const
{
    return m_pol_analyzer ? m_pol_analyzer->matrix() : SpinMatrix::One();
}
