/**
 * @brief InstrumentVisitor::registerDetector
 * @param detector : IDetector being visited
 * @return Component index of this component
 */
size_t InstrumentVisitor::registerDetector(const IDetector &detector) {
  auto detectorIndex = m_detectorIdToIndexMap->at(detector.getID());

  /* Already allocated we just need to index into the inital front-detector
   * part of the collection.
   * 1. Guarantee on grouping detectors by type such that the first n
   * components
   * are detectors.
   * 2. Guarantee on ordering such that the
   * detectorIndex == componentIndex for all detectors.
   */
  // Record the ID -> component index mapping
  (*m_componentIdToIndexMap)[detector.getComponentID()] = detectorIndex;
  (*m_componentIds)[detectorIndex] = detector.getComponentID();
  m_assemblySortedDetectorIndices->push_back(detectorIndex);
  (*m_detectorPositions)[detectorIndex] = Kernel::toVector3d(detector.getPos());
  (*m_detectorRotations)[detectorIndex] =
      Kernel::toQuaterniond(detector.getRotation());
  (*m_shapes)[detectorIndex] = detector.shape();
  (*m_scaleFactors)[detectorIndex] =
      Kernel::toVector3d(detector.getScaleFactor());
  if (m_instrument->isMonitorViaIndex(detectorIndex)) {
    m_monitorIndices->push_back(detectorIndex);
  }
  (*m_names)[detectorIndex] = detector.getName();
  clearLegacyParameters(m_pmap, detector);

  /* Note that positions and rotations for detectors are currently
  NOT stored! These go into DetectorInfo at present. push_back works for other
  Component types because Detectors are always come first in the resultant
  component list
  forming a contiguous block.
  */
  markAsSourceOrSample(detector.getComponentID(),
                       detectorIndex); // TODO. Optimisation. Cannot have a
                                       // detector that is either source or
                                       // sample. So delete this.
  return detectorIndex;
}
/** Loads the instrument into a workspace.
 */
void VesuvioL1ThetaResolution::calculateDetector(
    const IDetector &detector, std::function<double()> &flatRandomVariateGen,
    std::vector<double> &l1Values, std::vector<double> &thetaValues) {
  const int numEvents = getProperty("NumEvents");
  l1Values.reserve(numEvents);
  thetaValues.reserve(numEvents);

  double sampleWidth = getProperty("SampleWidth");
  // If the sample is large fix the width to the approximate beam width
  if (sampleWidth > 4.0)
    sampleWidth = 4.0;

  // Get detector dimensions
  Geometry::IObject_const_sptr pixelShape = detector.shape();
  if (!pixelShape || !pixelShape->hasValidShape()) {
    throw std::invalid_argument("Detector pixel has no defined shape!");
  }
  Geometry::BoundingBox detBounds = pixelShape->getBoundingBox();
  V3D detBoxWidth = detBounds.width();
  const double detWidth = detBoxWidth.X() * 100;
  const double detHeight = detBoxWidth.Y() * 100;

  g_log.debug() << "detWidth=" << detWidth << "\ndetHeight=" << detHeight
                << '\n';

  // Scattering angle in rad
  const double theta = m_instWorkspace->detectorTwoTheta(detector);
  if (theta == 0.0)
    return;

  // Final flight path in cm
  const double l1av = detector.getDistance(*m_sample) * 100.0;

  const double x0 = l1av * sin(theta);
  const double y0 = l1av * cos(theta);

  // Get as many events as defined by NumEvents
  // This loop is not iteration limited but highly unlikely to ever become
  // infinate
  while (l1Values.size() < static_cast<size_t>(numEvents)) {
    const double xs = -sampleWidth / 2 + sampleWidth * flatRandomVariateGen();
    const double ys = 0.0;
    const double zs = -sampleWidth / 2 + sampleWidth * flatRandomVariateGen();
    const double rs = sqrt(pow(xs, 2) + pow(zs, 2));

    if (rs <= sampleWidth / 2) {
      const double a = -detWidth / 2 + detWidth * flatRandomVariateGen();
      const double xd = x0 - a * cos(theta);
      const double yd = y0 + a * sin(theta);
      const double zd = -detHeight / 2 + detHeight * flatRandomVariateGen();

      const double l1 =
          sqrt(pow(xd - xs, 2) + pow(yd - ys, 2) + pow(zd - zs, 2));
      double angle = acos(yd / l1);

      if (xd < 0.0)
        angle *= -1;

      // Convert angle to degrees
      angle *= 180.0 / M_PI;

      l1Values.push_back(l1);
      thetaValues.push_back(angle);
    }

    interruption_point();
  }
}