//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sample/Material/std::vector<OneAdmixture>.cpp
//! @brief     Implements class OneAdmixture.
//!
//! @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 "Sample/Material/Admixtures.h"
#include "Base/Util/Assert.h"
#include "Sample/Material/RefractiveMaterial.h"
#include "Sample/Material/SLDMaterial.h"
#include "Sample/Material/Vacuum.h"
#include <stdexcept>
#include <utility>

OneAdmixture::OneAdmixture(double volume, const Material& material)
    : m_volume(volume)
    , m_material(material.clone())
{
}

OneAdmixture::OneAdmixture(const OneAdmixture& other)
    : OneAdmixture(other.volume(), *other.material())
{
}


std::unique_ptr<const Material>
MixUtil::averagedMaterial(const std::string& name, const std::vector<OneAdmixture>& admixtures,
                          double ref_wavelength)
{
    double total_volume = 0.0;
    for (const OneAdmixture& a : admixtures) {
        ASSERT(a.volume() >= 0);
        total_volume += a.volume();
    }
    ASSERT(total_volume > 0);

    double avge_r = 0.0;
    double avge_g = 0.0;
    double avge_b = 0.0;
    for (const OneAdmixture& a : admixtures) {
        const double w = a.volume() / total_volume;
        const MaterialColor c = a.material()->color();
        avge_r += w * c.r;
        avge_g += w * c.g;
        avge_b += w * c.b;
    }
    const MaterialColor avgeColor(avge_r, avge_g, avge_b);
    R3 avgeMagn;

    // Determine if we have a homogeneous or mixed set of materials
    bool hasRefr = false;
    bool hasSLD = false;
    for (const OneAdmixture& a : admixtures) {
        const Material* mat = a.material();
        if (mat->isVacuum())
            continue;
        if (mat->hasNativeRefractiveIndex() && !mat->hasNativeSLD())
            hasRefr = true;
        if (mat->hasNativeSLD() && !mat->hasNativeRefractiveIndex())
            hasSLD = true;
    }

    const bool mixedTypes = hasRefr && hasSLD;

    if (mixedTypes || hasSLD) {
        // Average in SLD space
        complex_t avgeSLD{0., 0.};
        for (const OneAdmixture& a : admixtures) {
            const Material* mat = a.material();
            double w = a.volume() / total_volume;
            avgeSLD += w * mat->sld(ref_wavelength);
            avgeMagn += w * mat->magnetization();
        }

        if (avgeSLD == 0. && avgeMagn == R3())
            return std::make_unique<Vacuum>();

        return std::make_unique<SLDMaterial>(name, avgeColor, avgeSLD.real(), avgeSLD.imag(),
                                             avgeMagn);
    }

    // All refractive-index materials (or vacuum): use original approach
    complex_t avgeData{0., 0.};
    for (const OneAdmixture& a : admixtures) {
        const Material* mat = a.material();
        double w = a.volume() / total_volume;
        avgeData += w * mat->averageableData();
        avgeMagn += w * mat->magnetization();
    }

    if (avgeData == 0. && avgeMagn == R3())
        return std::make_unique<Vacuum>();

    avgeData = std::conj(1.0 - std::sqrt(1.0 + avgeData));
    return std::make_unique<RefractiveMaterial>(name, avgeColor, avgeData.real(), avgeData.imag(),
                                                avgeMagn);
}

// Tested by Tests/Unit/Sim/Sample/MaterialTest.cpp

std::unique_ptr<const Material> MixUtil::basePlusAdmixtures(const Material& base_mat,
                                                            double base_volume,
                                                            std::vector<OneAdmixture> admixtures,
                                                            double ref_wavelength)
{
    if (!(base_volume > 0.0))
        throw std::runtime_error("MixUtil::basePlusAdmixtures: base_volume must be positive");

    double total_admixed_volume = 0.0;
    for (const OneAdmixture& admix : admixtures) {
        ASSERT(admix.volume() >= 0);
        total_admixed_volume += admix.volume();
    }
    if (total_admixed_volume > base_volume)
        throw std::runtime_error("Volume of sample components exceeds base volume");

    admixtures.emplace_back(OneAdmixture(base_volume - total_admixed_volume, base_mat));

    const std::string avge_name = base_mat.materialName() + "_avg";

    return averagedMaterial(avge_name, admixtures, ref_wavelength);
}
