/** @file aoflagger.h @brief Main AOFlagger header file.
 * @author André Offringa offringa@gmail.com
 * @copyright by A.R. Offringa under the GPL version 3
 */

#ifndef AOFLAGGER_INTERFACE_H
#define AOFLAGGER_INTERFACE_H

#include <cstring>
#include <string>
#include <memory>
#include <utility>
#include <vector>

/** @brief Contains all the public types used by the AOFlagger.
 *
 * See the @ref AOFlagger class description for details.
 * @author André Offringa offringa@gmail.com
 */
namespace aoflagger {

/** @brief Strategy identifier for the supported telescopes.
 *
 * If you have an optimized strategy for an unlisted telescope, please
 * contact me.
 * @sa AOFlagger::FindStrategyFile().
 * @since Version 3.0
 */
enum class TelescopeId {
  /** @brief Most generic strategy. */
  GENERIC_TELESCOPE,
  /** @brief The AARTFAAC telescope, correlating the superterp antennas of
     LOFAR. */
  AARTFAAC_TELESCOPE,
  /** @brief The WSRT telescope with the Apertif focal plane array receiver
     system. */
  APERTIF_TELESCOPE,
  /** @brief Arecibo radio telescope, the 305 m telescope in Puerto Rico. */
  ARECIBO_TELESCOPE,
  /** @brief Australian Telescope Compact Array in Australia. */
  ATCA_TELESCOPE,
  /** @brief Bighorns, instrument aimed at achieving an averaged all-sky
     measurement of the Epoch of Reionisation signal. */
  BIGHORNS_TELESCOPE,
  /** @brief JVLA, the Jansky Very Large Array in New Mexico. */
  JVLA_TELESCOPE,
  /** @brief LOFAR. the Low-Frequency Array in Europe. */
  LOFAR_TELESCOPE,
  /** @brief MWA, the Murchison Widefield Array in Western Australia. */
  MWA_TELESCOPE,
  /** @brief NenuFAR, the New Extension in Nançay upgrading LOFAR. */
  NENUFAR_TELESCOPE,
  /** @brief Parkes, the single dish telescope in New South Wales. */
  PARKES_TELESCOPE,
  /** @brief WSRT, the Westerbork Synthesis Radio Telescope in the Netherlands.
   */
  WSRT_TELESCOPE
};

/**
 * @brief Description of a single antenna.
 */
struct Antenna {
  /** @brief A unique identifying number for this antenna. */
  size_t id;
  /** @brief ITRF position of the antenna. May be zero if unknown. */
  double x, y, z;
  /** @brief Name of the antenna, e.g. "RT5". May be empty if unknown. */
  std::string name;
  /** @brief Diameter of the antenna in meters. May be zero if unknown. */
  double diameter;
  /** @brief Type of mount, e.g. "FIXED". May be empty if unknown. */
  std::string mount;
  /** @brief Station to which this antenna belongs. May be empty if unknown. */
  std::string station;
};

/**
 * @brief A single channel.
 */
struct Channel {
  /** @brief Central frequency of this channel. */
  double frequency;
  /** @brief Width of this channel. */
  double width;
};

/**
 * @brief Description of a single band.
 */
struct Band {
  /** @brief A unique identifying number for this band. */
  size_t id;
  /** @brief List of channels in this band. */
  std::vector<Channel> channels;
};

/** @brief A list of time value for a consecutive interval. */
struct Interval {
  size_t id;
  /** @brief Time values (MJD). */
  std::vector<double> times;
};

/** @brief A set of time-frequency 'images' which together contain data for one
 * correlated baseline or dish.
 *
 * The class either holds 1, 2, 4 or 8 images. These images have time on the
 * x-axis (most rapidly changing index) and frequency on the y-axis. The
 * cells specify flux levels, which do not need to have been calibrated.
 *
 * If the set contains only one image, it specifies amplitudes of a single
 * polarization. If it contains two images, it specifies the real and imaginary
 * parts of a single polarization. With four images, it contains the real
 * and imaginary values of two polarizations (ordered real pol A, imag pol A,
 * real pol B, imag pol B). With eight images, it contains complex values for
 * four correlated polarizations (ordered real pol A, imag pol A, real pol B,
 * ... etc).
 *
 * @note When accesses the image data, note that there might be more items on
 * one row than the width of the image. The rows are padded to align them e.g.
 * for SSE instructions. Use @ref HorizontalStride() to get the actual number of
 * floats per row.
 */
class ImageSet {
 public:
  friend class AOFlagger;
  friend class QualityStatistics;
  friend class Strategy;

  /** @brief Construct an empty ImageSet.
   *
   * The only operations allowed on an empty ImageSet are to assign to it. Use
   * AOFlagger::MakeImageSet() to construct a non-empty ImageSet.
   */
  ImageSet();

  /** @brief Copy the image set. Only references to images are copied. */
  ImageSet(const ImageSet& sourceImageSet);

  /** @brief Move from the image set.
   * @since Version 2.10
   */
  ImageSet(ImageSet&& sourceImageSet);

  /** @brief Destruct image set. Destroys its images if no longer referenced. */
  ~ImageSet();

  /** @brief Assign to this image set. Only references to images are copied. */
  ImageSet& operator=(const ImageSet& sourceImageSet);

  /** @brief Move assign to this image set.
   * @since Version 2.10
   */
  ImageSet& operator=(ImageSet&& sourceImageSet);

  /** @brief Get access to the data buffer of an image.
   * @param imageIndex Index of image. See class description for ordering.
   * \note Rows are padded, see @ref HorizontalStride().
   */
  float* ImageBuffer(size_t imageIndex);

  /** @brief Get constant access to the data buffer of an image.
   * @param imageIndex Index of image. See class description for ordering.
   * \note Rows are padded, see @ref HorizontalStride().
   */
  const float* ImageBuffer(size_t imageIndex) const;

  /** @brief Get width (number of time steps) of images. */
  size_t Width() const;

  /** @brief Get height (number of frequency channels) of images. */
  size_t Height() const;

  /** @brief Get number of images, see class description for details. */
  size_t ImageCount() const;

  /** @brief Get total number of floats in one row.
   *
   * Row might have been padded to allow for
   * SSE instructions and other optimizations. Therefore, one should
   * add the horizontal stride to a data pointer to get the float in the next
   * row (channel).
   *
   * Example:
   * @code{.cpp}
   *(ImageSet::ImageBuffer(imageIndex) + x + y * ImageSet::HorizontalStride())
   * @endcode
   * will return the value at position x,y.
   */
  size_t HorizontalStride() const;

  /** @brief Set all samples to the specified value.
   * @param newValue The new value for all values of all images in the set.
   * @since 2.5
   */
  void Set(float newValue);

  /** @brief Resize the image without reallocating new memory.
   *
   * This function allows to quickly change the dimension of the images in the
   * imageset. The new width has to fit in the image capacity as specified
   * during creation. When flagging many images of "almost" the same size, using
   * this method to change the size of images is drastically faster compared
   * to freeing and then allocating new images. It was added after rather
   * severe memory fragmentation problems in the Cotter MWA pipeline.
   * @param newWidth The new width of the images. Should satisfy newWidth <=
   * HorizontalStride().
   * @since 2.5
   */
  void ResizeWithoutReallocation(size_t newWidth) const;

  void SetAntennas(size_t antenna1Index, size_t antenna2Index);
  bool HasAntennas() const;
  size_t Antenna1() const;
  size_t Antenna2() const;

  void SetInterval(size_t index);
  bool HasInterval() const;
  size_t Interval() const;

  void SetBand(size_t index);
  bool HasBand() const;
  size_t Band() const;

 private:
  ImageSet(size_t width, size_t height, size_t count);

  ImageSet(size_t width, size_t height, size_t count, float initialValue);

  ImageSet(size_t width, size_t height, size_t count, size_t widthCapacity);

  ImageSet(size_t width, size_t height, size_t count, float initialValue,
           size_t widthCapacity);

  static void assertValidCount(size_t count);

  std::unique_ptr<class ImageSetData> _data;
};

/** @brief A two-dimensional flag mask.
 *
 * The flag mask specifies which values in an @ref ImageSet are flagged.
 * A value @c true means a value is flagged, i.e., contains RFI and should
 * not be used in further data processing (calibration, imaging, etc.).
 * A flag denotes that the value at that time-frequency position should
 * be ignored for all polarizations. This normally makes sense, because if one
 * polarization is contaminated by RFI, all polarizations are probably
 * affected. Also, solving for Stokes matrices during calibration might
 * not work well when the polarizations are not flagged equally.
 *
 * If polarization-specific flags are needed, one could run the flagger on
 * each polarization individually. However, note that some algorithms, like
 * the morphological scale-invariant rank operator (SIR operator), work best
 * when seeing the flags from all polarizations.
 *
 * @note When accesses the flag data, note that there might be more items on one
 * row than the width of the mask. The rows are padded to align them e.g. for
 * SSE instructions. Use @ref HorizontalStride() to get the actual number of
 * bools per row.
 */
class FlagMask {
 public:
  friend class AOFlagger;
  friend class QualityStatistics;
  friend class Strategy;

  /** @brief Construct an empty FlagMask.
   * The properties of an empty FlagMask can not be accessed.
   */
  FlagMask();

  /** @brief Copy a flag mask. Only copies a reference, not the data. */
  FlagMask(const FlagMask& sourceMask);

  /** @brief Move construct a flag mask.
   * @since Version 2.10
   */
  FlagMask(FlagMask&& sourceMask);

  /** @brief Copy assignment.
   * @since Version 2.10
   */
  FlagMask& operator=(const FlagMask& source);

  /** @brief Move assignment.
   * @since Version 2.10
   */
  FlagMask& operator=(FlagMask&& source);

  /** @brief Destroy a flag mask. Destroys mask data if no longer references. */
  ~FlagMask();

  /** @brief Get the width of the mask. */
  size_t Width() const;

  /** @brief Get the height of the mask. */
  size_t Height() const;

  /** @brief Get total number of bools in one row.
   *
   * Row might have been padded to allow for
   * SSE instructions and other optimizations. Therefore, one should
   * add the horizontal stride to a data pointer to get the flags in
   * the next row (channel).
   *
   * Example:
   * @code{.cpp}
   *(FlagMask::Buffer() + x + y * Buffer::HorizontalStride())
   * @endcode
   * will return the flag value at position x,y.
   */
  size_t HorizontalStride() const;

  /** @brief Get access to the data buffer.
   * @note The buffer is padded, see @ref HorizontalStride(). */
  bool* Buffer();

  /** @brief Get constant access to the data buffer.
   * @note The buffer is padded, see @ref HorizontalStride(). */
  const bool* Buffer() const;

 private:
  FlagMask(size_t width, size_t height);
  FlagMask(size_t width, size_t height, bool initialValue);

  std::unique_ptr<class FlagMaskData> _data;
};

/** @brief Holds a flagging strategy.
 *
 * Default "stock" strategies can be found with
 * @ref AOFlagger::FindStrategyFile(), and these or custom Lua files can
 * be loaded from disc with @ref AOFlagger::LoadStrategyFile().
 * A user can create strategies with the @c rfigui tool that is part
 * of the aoflagger package.
 *
 * When flagging a large number of baselines it is recommended to use multiple
 * threads. This class is itself not thread save, but it is safe to use
 * different Strategy objects from different thread contexts.
 */
class Strategy {
 public:
  friend class AOFlagger;

  /** @brief Construct an empty strategy.
   *
   * The only operations allowed on an empty Strategy are to assign to it. Use
   * e.g. AOFlagger::LoadStrategyFile() to construct a non-empty Strategy.
   * @since Version 3
   */
  Strategy();

  /** @brief Move construct a strategy.
   * @since Version 2.10
   */
  Strategy(Strategy&& sourceStrategy);

  /** @brief Destruct strategy. */
  ~Strategy();

  /** @brief Move assign to strategy.
   * @since Version 2.10
   */
  Strategy& operator=(Strategy&& sourceStrategy);

  /** @brief Run the flagging strategy on the given data.
   *
   * The Lua strategy is executed single-threaded. This function is not
   * thread safe: To flag multiple imagesets simultaneously from different
   * threads, it is necessary to create a Strategy object for each thread.
   *
   * @param input The data to run the flagger on.
   * @return The flags identifying bad (RFI contaminated) data.
   * @since 3.0
   */
  FlagMask Run(const ImageSet& input);

  /** @brief Run the flagging strategy on the given data with existing flags.
   *
   * This method is similar to @ref Run(const ImageSet&), except
   * that it will pass existing flags (e.g. as set by the correlator)
   * to the flagging strategy, which in the case of bad data can do a better
   * job of finding RFI in the good data.
   * @p input parameter. The @p strategy parameter can be the
   * same for different threads.
   * @param input The data to run the flagger on.
   * @param existingFlags Flags that indicate what data are bad.
   * @return A flag mask that identifies bad (RFI contaminated) data.
   * @since 3.0
   */
  FlagMask Run(const ImageSet& input, const FlagMask& existingFlags);

 private:
  Strategy(const std::string& filename, class AOFlagger* aoflagger);
  Strategy(const Strategy& sourceStrategy) = delete;
  Strategy& operator=(const Strategy& sourceStrategy) = delete;
  static Strategy makeFromString(const std::string& script,
                                 class AOFlagger* aoflagger);

  FlagMask run(const ImageSet& input, const FlagMask* existingFlags);

  std::unique_ptr<class StrategyData> _data;
  class AOFlagger* _aoflagger;
};

/** @brief Statistics that can be collected online and saved to a measurement
 * set.
 *
 * It is useful to collect some statistics during flagging, because all data
 * goes through memory at highest resolution. This class contains the collected
 * statistics and some meta data required for collecting. It can be created with
 * @ref AOFlagger::MakeQualityStatistics(). Statistics can be added to it with
 * @ref CollectStatistics(), and saved to disk with
 * @ref WriteStatistics().
 *
 * This class does not allow viewing or modifying statistics, it only contains
 * the most basic form to collect statistics during flagging and writing them in
 * the (well-defined) quality statistic tables format. These statistics can be
 * viewed interactively with the @c aoqplot tool.
 *
 * Collecting statistics is not as expensive as flagging, but still takes some
 * time, so it is recommended to use multiple threads for collecting as well.
 * This class is however not thread save, but it is okay to use different
 * QualityStatistics objects from different thread contexts. During
 * finalization, the different objects can be combined with the operator+=()
 * method, and then in full written to the measurement set.
 */
class QualityStatistics {
 public:
  friend class AOFlagger;

  /** Construct a QualityStatistics with null state.
   *
   * An object created by this constructor can only be assigned to.
   * @since Version 2.13
   */
  QualityStatistics();

  /** @brief Copy the object. This is fast; only references are copied. */
  QualityStatistics(const QualityStatistics& sourceQS);

  /** @brief Move construct the object.
   * @since Version 2.10
   */
  QualityStatistics(QualityStatistics&& sourceQS);

  /** @brief Destruct the object. Data is destroyed if no more references exist.
   */
  ~QualityStatistics();

  /** @brief Assign to this object. This is fast; only references are copied. */
  QualityStatistics& operator=(const QualityStatistics& sourceQS);

  /** @brief Move-assign this object. This is fast; only references are moved.
   * @since Version 2.10
   */
  QualityStatistics& operator=(QualityStatistics&& sourceQS);

  /** @brief Combine the statistics from the given object with the statistics in
   * this object.
   *
   * This is a relative expensive operation, so should only be used scarsely. It
   * can be used to combine the results of different threads, as explained in
   * the class description.
   *
   * It is safe to combine quality statistics with different meta data (scan
   * time count, channel count, etc.). When using this object again during
   * collecting (see @ref CollectStatistics()), after combining it with another
   * object, it will still use the meta data it was initialized with.
   */
  QualityStatistics& operator+=(const QualityStatistics& rhs);

  /** @brief Collect statistics from time-frequency images and masks.
   *
   * This will update the statistics in this object so that it
   * represents the combination of previous collected data and the newly
   * given data.
   *
   * This function can be called from different thread context, as long as each
   * thread uses its own QualityStatistics object.
   * See the @ref QualityStatistics class documentation for further
   * multithreading info.
   * @param imageSet Data to collect statistics from
   * @param rfiFlags Flags set by the automatic RFI detector
   * @param correlatorFlags Flags that were set prior to RFI detector, e.g.
   * because of a broken antenna or correlator hickup.
   * @param antenna1 Index of the first antenna involved in this baseline.
   * @param antenna2 Index of the second antenna involved in this baseline.
   * @since 3.0
   */
  void CollectStatistics(const ImageSet& imageSet, const FlagMask& rfiFlags,
                         const FlagMask& correlatorFlags, size_t antenna1,
                         size_t antenna2);

  /** @brief Write collected statistics in standard tables to a measurement set.
   * @param measurementSetPath Path to measurement set to which the statistics
   * will be written.
   * @since 3.0
   */
  void WriteStatistics(const std::string& measurementSetPath) const;

 private:
  QualityStatistics(const double* scanTimes, size_t nScans,
                    const double* channelFrequencies, size_t nChannels,
                    size_t nPolarizations, bool computeHistograms);

  std::unique_ptr<class QualityStatisticsData> _data;
};

/**
 * @brief A base class which callers can inherit from to be able to receive
 * progress updates and error messages.
 *
 * A status listener should be thread safe when the Run() method is called in
 * parallel with the same StatusListener object.
 */
class StatusListener {
 public:
  /**
   * @brief Virtual destructor.
   */
  virtual ~StatusListener() {}
  /**
   * @brief This virtual method is called when a new task is started.
   *
   * Typically, a client could display a message saying that the given task
   * 'description' is started.
   * @param description Description of the task, e.g. "SumThreshold".
   * @since 3.0
   */
  virtual void OnStartTask(const std::string& description) {}
  /**
   * @brief Called to update current progress.
   *
   * This can be used to display a progress bar if the strategy would take a lot
   * of time.
   * @param progress Current progress
   * @param maxProgress Progress that is required to finish the current task.
   */
  virtual void OnProgress(size_t progress, size_t maxProgress) {}
  /**
   * @brief Called when detection has completely finished.
   *
   * This function will always be called exactly once on success. It is not
   * called when an exception occurred. @ref OnException() is called in that
   * case.
   */
  virtual void OnFinish() {}
  /**
   * @brief Called when an exception occurs during execution of the strategy.
   *
   * This can occur when the Lua script throws an error.
   * @param thrownException The exception that was thrown.
   */
  virtual void OnException(std::exception& thrownException) = 0;
};

/** @brief Main class for access to the flagger functionality.
 *
 * Software using the flagger should first create an instance of the @ref
 * AOFlagger class, from which other actions can be initiated.
 *
 * ### Overview
 *
 * To flag a data set:
 * - Create the AOFlagger instance
 * - To use a stock strategy, call FindStrategyFile()
 * - Load and parse the strategy with LoadStrategyFile(), once for each thread
 * that will run.
 * - Create data buffers with MakeImageSet()
 * - For each correlated baseline or dish:
 * - - Fill the images with data from this correlated baseline or dish
 * - - Call Strategy::Run() with the created Strategy and ImageSet
 * - - Process the data that was returned in the FlagMask.
 *
 * Optionally, it is possible to assemble quality statistics that can be written
 * to the measurement set in the standard format that e.g. the @c aoqplot tool
 * can read. To do this:
 * - Create (once) a quality statistics object with MakeQualityStatistics().
 * - After flagging a baseline, add it to the statistics object with
 *   QualityStatistics::CollectStatistics().
 * A "correlator mask" can be specified that describes which flags are not due
 * to RFI but caused by different things.
 * - When a full set is processed, store the statistics with WriteStatistics().
 *
 * To flag multiple baselines, the Strategy and/or ImageSet objects can be
 * reused.
 *
 * ### Thread safety
 *
 * Each Strategy object runs in its own context, and will not perform any
 * unsynchronised writes to global variables. It is therefore safe to call
 * Strategy::Run() from different threads, as long as each thread uses its own
 * Strategy and ImageSet instances. QualityStatistics::CollectStatistics() is
 * also thread safe, as long as different QualityStatistics instances are
 * passed. For multi-threading, each thread should collect into its own
 * QualityStatistics object. When finished, these can be combined with
 * QualityStatistics::operator+=().
 *
 * It is safe to create multiple AOFlagger instances, but not recommended.
 *
 * ### Data order
 *
 * A common problem for integrating the flagger, is that data are stored in a
 * different order: the time dimension
 * is often the direction with the slowest increasing indices. Because the
 * flagger needs one baseline at a time, this requires reordering the data. As
 * long as the data fits in memory, this reordering is quite straightforward.
 * When this is not the case, the data could be split into sub-bands and/or time
 * windows. Next, these parts can be passed to the flagger and recombined later
 * (if desired).
 *
 * To decide how to split, keep in mind that the flagger
 * works best when both a lot of channels and a lot of
 * timesteps are available. As an example: LOFAR splits into subbands of ~64
 * channels, and the default processing with NDPPP loads as many timesteps as
 * possible in memory for flagging. Typically, this means at least a few hundred
 * of timesteps are processed at a time (with 1-3s per timestep), and this seems
 * to work well.
 *
 * The 'aoflagger' executable flags by default on the full measurement set.
 * For sets that are larger than memory, a mode is used in
 * which the data is reordered to disk before the actual flagging starts. It
 * turns out that this is much faster than reading each baseline directly from
 * the set, and it simultaneously produces the best flagging accuracy, so if
 * enough processing power is available to do so, this is another approach.
 */
class AOFlagger {
 public:
  /** @brief Create and initialize the flagger main class. */
  AOFlagger() : _statusListener(nullptr) {}

  /** @brief Destructor. */
  ~AOFlagger() {}

  /** @brief Create a new uninitialized @ref ImageSet with specified specs.
   *
   * The float values will not be initialized.
   * @param width Number of time steps in images
   * @param height Number of frequency channels in images
   * @param count Number of images in set (see class description
   * of @ref ImageSet for image order).
   * @return A new ImageSet.
   */
  ImageSet MakeImageSet(size_t width, size_t height, size_t count) {
    return ImageSet(width, height, count);
  }

  /** @brief Create a new uninitialized @ref ImageSet with specified specs.
   *
   * The float values will not be initialized.
   * @param width Number of time steps in images
   * @param height Number of frequency channels in images
   * @param count Number of images in set (see class description
   * of @ref ImageSet for image order).
   * @param widthCapacity Allow for enlarging image to this size, @sa
   * ImageSet::ResizeWithoutReallocation()
   * @return A new ImageSet.
   * @since 2.6
   */
  ImageSet MakeImageSet(size_t width, size_t height, size_t count,
                        size_t widthCapacity) {
    return ImageSet(width, height, count, widthCapacity);
  }

  /** @brief Create a new initialized @ref ImageSet with specified specs.
   * @param width Number of time steps in images
   * @param height Number of frequency channels in images
   * @param count Number of images in set (see class description
   * of @ref ImageSet for image order).
   * @param initialValue Initialize all pixels with this value.
   * @return A new ImageSet.
   */
  ImageSet MakeImageSet(size_t width, size_t height, size_t count,
                        float initialValue) {
    return ImageSet(width, height, count, initialValue);
  }

  /** @brief Create a new initialized @ref ImageSet with specified specs.
   * @param width Number of time steps in images
   * @param height Number of frequency channels in images
   * @param count Number of images in set (see class description
   * of @ref ImageSet for image order).
   * @param initialValue Initialize all pixels with this value.
   * @param widthCapacity Allow for enlarging image to this size, @sa
   * ImageSet::ResizeWithoutReallocation()
   * @return A new ImageSet.
   * @since 2.6
   */
  ImageSet MakeImageSet(size_t width, size_t height, size_t count,
                        float initialValue, size_t widthCapacity) {
    return ImageSet(width, height, count, initialValue, widthCapacity);
  }

  /** @brief Create a new uninitialized @ref FlagMask with specified dimensions.
   * @param width Width of mask (number of timesteps)
   * @param height Height of mask (number of frequency channels)
   * @return A new FlagMask.
   */
  FlagMask MakeFlagMask(size_t width, size_t height) {
    return FlagMask(width, height);
  }

  /** @brief Create a new initialized @ref FlagMask with specified dimensions.
   * @param width Width of mask (number of timesteps)
   * @param height Height of mask (number of frequency channels)
   * @param initialValue Value to initialize the mask to.
   * @return A new FlagMask.
   */
  FlagMask MakeFlagMask(size_t width, size_t height, bool initialValue) {
    return FlagMask(width, height, initialValue);
  }

  /** @brief Find a Lua strategy for a specific telescope.
   *
   * The scenario name can be used to
   * distinguish different strategies for the same telescope, for example
   * to distinguish between different bands, different processing stages (e.g.
   * before or after averaging), different antennas (LOFAR HBA vs LBA), etc.
   *
   * This will search the data directory of the installation path of
   * aoflagger for a file named &lt;Telescope_Name&gt;-&lt;scenario&gt;.lua, or
   * &lt;Telescope_Name&gt;-default.lua in case scenario is empty.
   *
   * @param telescopeId Identifies the telescope to optimize the strategy for.
   * @param scenario A scenario name that should be searched for.
   * @returns Filename of strategy, or empty string if none found.
   * @since Version 3.0
   */
  std::string FindStrategyFile(
      enum TelescopeId telescopeId = TelescopeId::GENERIC_TELESCOPE,
      const std::string& scenario = "");

  /** @brief Load a strategy from disk.
   *
   * The best way to create strategies is to use the @c rfigui tool. In case you
   * have optimized strategies for an unlisted telescope or for new scenarios,
   * please consider providing the file so I can add them to the repository.
   *
   * @param filename Full pathname to .lua strategy file.
   * @return The new @ref Strategy.
   * @since Version 3.0
   */
  Strategy LoadStrategyFile(const std::string& filename) {
    return Strategy(filename, this);
  }

  /** @brief Load a strategy from a string containing a Lua script.
   *
   * Similar to @ref LoadStrategyFile().
   *
   * @param script String containing full Lua script.
   * @return The new @ref Strategy.
   * @since Version 3.1
   */
  Strategy LoadStrategyString(const std::string& script) {
    return Strategy::makeFromString(script, this);
  }

  /**
   * @brief Create a new object for collecting statistics.
   * @param scanTimes Array with times. The number of elements should match the
   * dimension of the time axis in calls to
   * QualityStatistics::CollectStatistics(). Each time is a MJD time in seconds.
   * @param nScans Number of elements in the @c scanTimes array.
   * @param channelFrequencies Frequency in Hz of each channel. The number of
   * elements should match the frequency axis in calls to
   * QualityStatistics::CollectStatistics().
   * @param nChannels Number of elements in the @c channelFrequencies array.
   * @param nPolarizations Number of polarizations in the set (1, 2 or 4).
   *
   * See the QualityStatistics class description for info on multithreading
   * and/or combining statistics with different meta data. The meta data that is
   * passed to this method will be used for all calls to
   * QualityStatistics::CollectStatistics(). No histograms will be computed.
   */
  QualityStatistics MakeQualityStatistics(const double* scanTimes,
                                          size_t nScans,
                                          const double* channelFrequencies,
                                          size_t nChannels,
                                          size_t nPolarizations);

  /** @brief Create a new object for collecting statistics, possibly with
   * histograms.
   *
   * See other overload of MakeQualityStatistics() for info.
   * @since Version 2.6
   */
  QualityStatistics MakeQualityStatistics(
      const double* scanTimes, size_t nScans, const double* channelFrequencies,
      size_t nChannels, size_t nPolarizations, bool computeHistograms);

  /** @brief Get the AOFlagger version number as a string.
   * @returns The version number, formatted like '1.2.3-subtitle', e.g.
   * '3.0-alpha'.
   * @since Version 2.6
   */
  static std::string GetVersionString();

  /** @brief Get the AOFlagger version number separated in major, minor and
   * subminor fields.
   * @param major Most significant number of the version, e.g. '1' for version
   * '1.2.3'. This number is only incremented in major changes of the flagger.
   * @param minor Minor number of the version, e.g. '2' for version '1.2.3'.
   * This number is incremented for every public release.
   * @param subMinor Subminor number of the version, e.g. '3' for version
   * '1.2.3', or zero if the current version has no subminor number. This number
   * is incremented for internal releases or small bug fixes.
   * @since Version 2.6
   */
  static void GetVersion(short& major, short& minor, short& subMinor);

  /** @brief Get the date this version was released as a string.
   * @returns The version date formatted like "1982-05-08".
   * @since Version 2.6
   */
  static std::string GetVersionDate();

  /**
   * @brief Set a handler for progress updates and exceptions.
   *
   * By default, exceptions will be reported to stderr and progress updates
   * will be ignored. If an application needs to handle either of these
   * themselves, they can override a @ref StatusListener that handles these
   * events and call this method to enable receiving the events.
   * This method is not thread safe.
   * @param statusListener The handler that will receive the status updates.
   * @since Version 2.7
   */
  void SetStatusListener(StatusListener* statusListener) {
    _statusListener = statusListener;
  }

  /**
   * Provide information about the antennas used in the data set.
   * This information is propagated to the Lua environment,
   * such that scripts can make choices based on the antenna.
   * It is not required to set this before flagging.
   */
  void SetAntennaList(std::vector<Antenna>&& antennas) {
    _antennas = std::move(antennas);
  }
  const std::vector<Antenna>& AntennaList() const { return _antennas; }

  /**
   * Provide frequency information for the data set.
   * This information is propagated to the Lua environment,
   * such that scripts can make choices based on the antenna.
   * It is not required to set this before flagging.
   */
  void SetBandList(std::vector<Band>&& bands) { _bands = std::move(bands); }
  const std::vector<Band>& BandList() const { return _bands; }

  void SetIntervalList(std::vector<Interval>&& intervals) {
    _intervals = std::move(intervals);
  }
  const std::vector<Interval>& IntervalList() const { return _intervals; }

 private:
  friend class Strategy;

  /** @brief It is not allowed to copy this class
   */
  AOFlagger(const AOFlagger&) = delete;

  /** @brief It is not allowed to assign to this class
   */
  void operator=(const AOFlagger&) = delete;

  StatusListener* _statusListener;
  std::vector<Antenna> _antennas;
  std::vector<Band> _bands;
  std::vector<Interval> _intervals;
};

}  // namespace aoflagger

#endif
