//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Detector/RectangularDetectorItem.cpp
//! @brief     Implements class RectangularDetectorItem
//!
//! @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 "GUI/Model/Detector/RectangularDetectorItem.h"
#include "GUI/Model/Detector/ResolutionFunctionItems.h"
namespace {
namespace Tag {

const QString XSize("XSize");
const QString YSize("YSize");
const QString Alignment("Alignment");
const QString Width("Width");
const QString Height("Height");
const QString u0("u0");
const QString v0("v0");
const QString Distance("Distance");
const QString NormalVector("NormalVector");
const QString DirectionVector("DirectionVector");
const QString BaseData("BaseData");
const QString ExpandXaxisGroupBox("ExpandXaxisGroupBox");
const QString ExpandYaxisGroupBox("ExpandYaxisGroupBox");

} // namespace Tag

const double default_detector_width = 20.0;
const double default_detector_height = 20.0;
const double default_detector_distance = 1000.0;

//! Holds the alignments as expected on the UI (map is sorted by keys)
const QMap<RectangularDetector::EDetectorArrangement, QString> alignment_names_map = {
    {RectangularDetector::GENERIC, "Generic"},
    {RectangularDetector::PERPENDICULAR_TO_SAMPLE, "Perpendicular to sample x-axis"},
    {RectangularDetector::PERPENDICULAR_TO_DIRECT_BEAM, "Perpendicular to direct beam"},
    {RectangularDetector::PERPENDICULAR_TO_REFLECTED_BEAM, "Perpendicular to reflected beam"}};

void initResolutionFunction(ResolutionFunctionItem* newFunc, const ResolutionFunctionItem*)
{
    newFunc->setUnit("mm");
}

} // namespace

RectangularDetectorItem::RectangularDetectorItem()
{
    m_resolutionFunction.initWithInitializer("Resolution function", "Detector resolution function",
                                             initResolutionFunction);

    m_xSize = 100;
    m_ySize = 100;
    m_width.init("Width (x-axis)", "Width of the detector", default_detector_width, "mm",
                 3 /* decimals */, RealLimits::positive(), "width");
    m_height.init("Height (y-axis)", "Height of the detector", default_detector_height, "mm",
                  3 /* decimals */, RealLimits::positive(), "height");

    m_normalVector.init(
        "Normal vector",
        "Normal of the detector plane with length equal to the sample detector distance",
        Unit::unitless, "normalVector");
    m_normalVector.setX(default_detector_distance);
    m_directionVector.init("GisasDirection vector",
                           "Detector axis direction vector w.r.t. the sample coordinate system",
                           Unit::unitless, "directionVector");
    m_directionVector.setY(-1.0);

    m_u0.init("u0", "", default_detector_width / 2., "mm", 3 /* decimals */,
              RealLimits::limitless(), "u0");
    m_v0.init("v0", "", 0.0, "mm", 3 /* decimals */, RealLimits::limitless(), "v0");
    m_distance.init("Distance", "Distance from the sample origin to the detector plane",
                    default_detector_distance, "mm", "distance");

    m_detectorAlignment = ComboProperty::fromList(
        alignment_names_map.values(),
        alignment_names_map.value(RectangularDetector::PERPENDICULAR_TO_DIRECT_BEAM));

    updateTooltips();
}

void RectangularDetectorItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeAttribute(w, XML::Attrib::version, uint(1));

    // parameters from base class
    w->writeStartElement(Tag::BaseData);
    DetectorItem::writeTo(w);
    w->writeEndElement();

    // x size
    w->writeStartElement(Tag::XSize);
    XML::writeAttribute(w, XML::Attrib::value, m_xSize);
    w->writeEndElement();

    // y size
    w->writeStartElement(Tag::YSize);
    XML::writeAttribute(w, XML::Attrib::value, m_ySize);
    w->writeEndElement();

    // width
    w->writeStartElement(Tag::Width);
    m_width.writeTo(w);
    w->writeEndElement();

    // height
    w->writeStartElement(Tag::Height);
    m_height.writeTo(w);
    w->writeEndElement();

    // alignment
    w->writeStartElement(Tag::Alignment);
    m_detectorAlignment.writeTo(w);
    w->writeEndElement();

    // normal vector
    w->writeStartElement(Tag::NormalVector);
    m_normalVector.writeTo(w);
    w->writeEndElement();

    // direction vector
    w->writeStartElement(Tag::DirectionVector);
    m_directionVector.writeTo(w);
    w->writeEndElement();

    // u0
    w->writeStartElement(Tag::u0);
    m_u0.writeTo(w);
    w->writeEndElement();

    // v0
    w->writeStartElement(Tag::v0);
    m_v0.writeTo(w);
    w->writeEndElement();

    // distance
    w->writeStartElement(Tag::Distance);
    m_distance.writeTo(w);
    w->writeEndElement();

    // x axis groupbox: is expanded?
    w->writeStartElement(Tag::ExpandXaxisGroupBox);
    XML::writeAttribute(w, XML::Attrib::value, m_expandXaxis);
    w->writeEndElement();

    // y axis groupbox: is expanded?
    w->writeStartElement(Tag::ExpandYaxisGroupBox);
    XML::writeAttribute(w, XML::Attrib::value, m_expandYaxis);
    w->writeEndElement();
}

void RectangularDetectorItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

    while (r->readNextStartElement()) {
        QString tag = r->name().toString();

        // parameters from base class
        if (tag == Tag::BaseData) {
            DetectorItem::readFrom(r);
            m_resolutionFunction->setUnit("mm");
            XML::gotoEndElementOfTag(r, tag);

            // x size
        } else if (tag == Tag::XSize) {
            XML::readAttribute(r, XML::Attrib::value, &m_xSize);
            XML::gotoEndElementOfTag(r, tag);

            // y size
        } else if (tag == Tag::YSize) {
            XML::readAttribute(r, XML::Attrib::value, &m_ySize);
            XML::gotoEndElementOfTag(r, tag);

            // width
        } else if (tag == Tag::Width) {
            m_width.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // height
        } else if (tag == Tag::Height) {
            m_height.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // alignment
        } else if (tag == Tag::Alignment) {
            m_detectorAlignment.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // normal vector
        } else if (tag == Tag::NormalVector) {
            m_normalVector.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // direction vector
        } else if (tag == Tag::DirectionVector) {
            m_directionVector.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // u0
        } else if (tag == Tag::u0) {
            m_u0.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // v0
        } else if (tag == Tag::v0) {
            m_v0.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // distance
        } else if (tag == Tag::Distance) {
            m_distance.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // x axis groupbox: is expanded?
        } else if (tag == Tag::ExpandXaxisGroupBox) {
            XML::readAttribute(r, XML::Attrib::value, &m_expandXaxis);
            XML::gotoEndElementOfTag(r, tag);

            // y axis groupbox: is expanded?
        } else if (tag == Tag::ExpandYaxisGroupBox) {
            XML::readAttribute(r, XML::Attrib::value, &m_expandYaxis);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

void RectangularDetectorItem::setDetectorAlignment(const QString& alignment)
{
    ASSERT(alignment_names_map.values().contains(alignment));
    setDetectorAlignment(alignment_names_map.key(alignment));
}

void RectangularDetectorItem::setDetectorAlignment(
    RectangularDetector::EDetectorArrangement alignment)
{
    m_detectorAlignment.setCurrentValue(alignment_names_map.value(alignment));
    updateTooltips();
}

RectangularDetector::EDetectorArrangement RectangularDetectorItem::detectorAlignment() const
{
    ASSERT(alignment_names_map.values().contains(m_detectorAlignment.currentValue()));
    return alignment_names_map.key(m_detectorAlignment.currentValue());
}

std::unique_ptr<IDetector> RectangularDetectorItem::createDomainDetector() const
{
    auto result = std::make_unique<RectangularDetector>(xSize(), width(), ySize(), height());

    // distance and alignment
    switch (detectorAlignment()) {
    case RectangularDetector::GENERIC:
        result->setDetectorPosition(m_normalVector, m_u0, m_v0, m_directionVector);
        break;
    case RectangularDetector::PERPENDICULAR_TO_SAMPLE:
        result->setPerpendicularToSampleX(m_distance, m_u0, m_v0);
        break;
    case RectangularDetector::PERPENDICULAR_TO_DIRECT_BEAM:
        result->setPerpendicularToDirectBeam(m_distance, m_u0, m_v0);
        break;
    case RectangularDetector::PERPENDICULAR_TO_REFLECTED_BEAM:
        result->setPerpendicularToReflectedBeam(m_distance, m_u0, m_v0);
        break;
    default:
        break;
    }

    return std::unique_ptr<IDetector>(result.release());
}

void RectangularDetectorItem::updateTooltips()
{
    switch (detectorAlignment()) {
    case RectangularDetector::GENERIC:
        m_u0.setTooltip(
            "u-coordinate of point of intersection of normal vector and detector plane");
        m_v0.setTooltip(
            "v-coordinate of point of intersection of normal vector and detector plane");
        break;
    case RectangularDetector::PERPENDICULAR_TO_SAMPLE:
        m_u0.setTooltip("u-coordinate of point where sample x-axis crosses the detector");
        m_v0.setTooltip("v-coordinate of point where sample x-axis crosses the detector");
        break;
    case RectangularDetector::PERPENDICULAR_TO_REFLECTED_BEAM:
        m_u0.setTooltip("u-coordinate of point where reflected beam hits the detector");
        m_v0.setTooltip("v-coordinate of point where reflected beam hits the detector");
        break;
    case RectangularDetector::PERPENDICULAR_TO_DIRECT_BEAM: // fall-through!
        m_u0.setTooltip("u-coordinate of point where direct beam hits the detector");
        m_v0.setTooltip("v-coordinate of point where direct beam hits the detector");
        break;
    default:
        break;
    }
}
