/**
 * Return the reference 2theta angle and the corresponding horizon angle.
 *
 * @param spectrumInfo [in] :: a spectrum info of the input workspace.
 * @return :: the reference angle struct
 */
ReflectometrySumInQ::Angles
ReflectometrySumInQ::referenceAngles(const API::SpectrumInfo &spectrumInfo) {
  Angles a;
  const double beamCentre = getProperty(Prop::BEAM_CENTRE);
  const bool isFlat = getProperty(Prop::IS_FLAT_SAMPLE);
  if (isFlat) {
    a.horizon = centreTwoTheta(beamCentre, spectrumInfo) / 2.;
  } else {
    a.horizon = 0.;
  }
  a.referenceWSIndex = static_cast<size_t>(beamCentre);
  a.twoTheta = spectrumInfo.twoTheta(a.referenceWSIndex);
  a.delta = a.twoTheta - a.horizon;
  return a;
}
void ConvertToDiffractionMDWorkspace::convertEventList(
    int workspaceIndex, const API::SpectrumInfo &specInfo, EventList &el) {
  size_t numEvents = el.getNumberEvents();
  DataObjects::MDBoxBase<DataObjects::MDLeanEvent<3>, 3> *box = ws->getBox();

  // Get the position of the detector there.
  const auto &detectors = el.getDetectorIDs();
  if (!detectors.empty()) {
    // Check if a detector is located at this workspace index, returns
    // immediately if one is not found.
    if (!specInfo.hasDetectors(workspaceIndex)) {
      this->failedDetectorLookupCount++;
      return;
    }

    // Neutron's total travelled distance
    double distance = l1 + specInfo.l2(workspaceIndex);

    // Vector between the sample and the detector
    const V3D detPos = specInfo.position(workspaceIndex);

    // Detector direction normalized to 1
    const V3D detDir = detPos / detPos.norm();

    // The direction of momentum transfer in the inelastic convention ki-kf
    //  = input beam direction (normalized to 1) - output beam direction
    //  (normalized to 1)
    V3D Q_dir_lab_frame = beamDir - detDir;
    double qSign = -1.0;
    std::string convention =
        ConfigService::Instance().getString("Q.convention");
    if (convention == "Crystallography")
      qSign = 1.0;
    Q_dir_lab_frame *= qSign;

    // Multiply by the rotation matrix to convert to Q in the sample frame (take
    // out goniometer rotation)
    // (or to HKL, if that's what the matrix is)
    const V3D Q_dir = mat * Q_dir_lab_frame;

    // For speed we extract the components.
    coord_t Q_dir_x = coord_t(Q_dir.X());
    coord_t Q_dir_y = coord_t(Q_dir.Y());
    coord_t Q_dir_z = coord_t(Q_dir.Z());

    // For lorentz correction, calculate  sin(theta))^2
    double sin_theta_squared = 0;
    if (LorentzCorrection) {
      // Scattering angle = 2 theta = angle between neutron beam direction and
      // the detector (scattering) direction
      // The formula for Lorentz Correction is sin(theta), i.e. sin(half the
      // scattering angle)
      double theta = specInfo.twoTheta(workspaceIndex) / 2.0;
      sin_theta_squared = sin(theta);
      sin_theta_squared = sin_theta_squared * sin_theta_squared; // square it
    }

    /** Constant that you divide by tof (in usec) to get wavenumber in ang^-1 :
     * Wavenumber (in ang^-1) =  (PhysicalConstants::NeutronMass * distance) /
     * ((tof (in usec) * 1e-6) * PhysicalConstants::h_bar) * 1e-10; */
    const double wavenumber_in_angstrom_times_tof_in_microsec =
        (PhysicalConstants::NeutronMass * distance * 1e-10) /
        (1e-6 * PhysicalConstants::h_bar);

    // This little dance makes the getting vector of events more general (since
    // you can't overload by return type).
    typename std::vector<T> *events_ptr;
    getEventsFrom(el, events_ptr);
    typename std::vector<T> &events = *events_ptr;

    // Iterators to start/end
    auto it = events.begin();
    auto it_end = events.end();

    for (; it != it_end; it++) {
      // Get the wavenumber in ang^-1 using the previously calculated constant.
      coord_t wavenumber =
          coord_t(wavenumber_in_angstrom_times_tof_in_microsec / it->tof());

      // Q vector = K_final - K_initial = wavenumber * (output_direction -
      // input_direction)
      coord_t center[3] = {Q_dir_x * wavenumber, Q_dir_y * wavenumber,
                           Q_dir_z * wavenumber};

      // Check that the event is within bounds
      if (center[0] < m_extentsMin[0] || center[0] >= m_extentsMax[0])
        continue;
      if (center[1] < m_extentsMin[1] || center[1] >= m_extentsMax[1])
        continue;
      if (center[2] < m_extentsMin[2] || center[2] >= m_extentsMax[2])
        continue;

      if (LorentzCorrection) {
        // double lambda = 1.0/wavenumber;
        // (sin(theta))^2 / wavelength^4
        float correct = float(sin_theta_squared * wavenumber * wavenumber *
                              wavenumber * wavenumber);
        // Push the MDLeanEvent but correct the weight.
        box->addEvent(MDE(float(it->weight() * correct),
                          float(it->errorSquared() * correct * correct),
                          center));
      } else {
        // Push the MDLeanEvent with the same weight
        box->addEvent(
            MDE(float(it->weight()), float(it->errorSquared()), center));
      }
    }

    // Clear out the EventList to save memory
    if (ClearInputWorkspace)
      el.clear();
  }
  prog->reportIncrement(numEvents, "Adding Events");
}