/// Calculate the distances traversed by the neutrons within the sample
/// @param detector :: The detector we are working on
/// @param L2s :: A vector of the sample-detector distance for  each segment of
/// the sample
void AbsorptionCorrection::calculateDistances(const IDetector &detector,
                                              std::vector<double> &L2s) const {
  V3D detectorPos(detector.getPos());
  if (detector.nDets() > 1) {
    // We need to make sure this is right for grouped detectors - should use
    // average theta & phi
    detectorPos.spherical(detectorPos.norm(),
                          detector.getTwoTheta(V3D(), V3D(0, 0, 1)) * 180.0 /
                              M_PI,
                          detector.getPhi() * 180.0 / M_PI);
  }

  for (size_t i = 0; i < m_numVolumeElements; ++i) {
    // Create track for distance in cylinder between scattering point and
    // detector
    V3D direction = detectorPos - m_elementPositions[i];
    direction.normalize();
    Track outgoing(m_elementPositions[i], direction);
    int temp = m_sampleObject->interceptSurface(outgoing);

    /* Most of the time, the number of hits is 1. Sometime, we have more than
     * one intersection due to
     * arithmetic imprecision. If it is the case, then selecting the first
     * intersection is valid.
     * In principle, one could check the consistency of all distances if hits is
     * larger than one by doing:
     * Mantid::Geometry::Track::LType::const_iterator it=outgoing.begin();
     * and looping until outgoing.end() checking the distances with it->Dist
     */
    // Not hitting the cylinder from inside, usually means detector is badly
    // defined,
    // i.e, position is (0,0,0).
    if (temp < 1) {
      // FOR NOW AT LEAST, JUST IGNORE THIS ERROR AND USE A ZERO PATH LENGTH,
      // WHICH I RECKON WILL MAKE A
      // NEGLIGIBLE DIFFERENCE ANYWAY (ALWAYS SEEMS TO HAPPEN WITH ELEMENT RIGHT
      // AT EDGE OF SAMPLE)
      L2s[i] = 0.0;

      // std::ostringstream message;
      // message << "Problem with detector at " << detectorPos << " ID:" <<
      // detector->getID() << '\n';
      // message << "This usually means that this detector is defined inside the
      // sample cylinder";
      // g_log.error(message.str());
      // throw std::runtime_error("Problem in
      // AbsorptionCorrection::calculateDistances");
    } else // The normal situation
    {
      L2s[i] = outgoing.cbegin()->distFromStart;
    }
  }
}
/**
 * @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();
  }
}