#ifndef MULTIBANDDATA_H
#define MULTIBANDDATA_H

#include <stdexcept>

#include <casacore/ms/MeasurementSets/MeasurementSet.h>

#include <casacore/tables/Tables/ArrayColumn.h>
#include <casacore/tables/Tables/ScalarColumn.h>

#include "banddata.h"

/**
 * Contains information about a set of bands. This follows the CASA Measurement
 * Set model; one MultiBandData instance can contain the band information
 * contained in the CASA Measurement Set.
 */
namespace internal {
class MultiBandData {
 public:
  using iterator = std::vector<BandData>::iterator;
  using const_iterator = std::vector<BandData>::const_iterator;

  /**
   * Construct an empty MultiBandData.
   */
  MultiBandData() {}

  /**
   * Construct a MultiBandData from the Measurement Set tables.
   * @param spwTable The spectral window table of a measurement set.
   * @param dataDescTable The data description table of a measurement set.
   */
  MultiBandData(casacore::MSSpectralWindow& spwTable,
                casacore::MSDataDescription& dataDescTable);

  /**
   * Construct a MultiBandData from another instance but only select a part of
   * each band data.
   * @param source Other instance that will be partially copied.
   * @param startChannel Start of channel range to initialize this instance
   * with.
   * @param endChannel End of channel range (exclusive) to initialize this
   * instance with.
   */
  MultiBandData(const MultiBandData& source, size_t startChannel,
                size_t endChannel);

  /**
   * Index operator to retrieve a band data given a dataDescID.
   * @param dataDescID A valid data description ID for which the band is
   * returned.
   * @returns The BandData for the requested band.
   */
  const BandData& operator[](size_t dataDescID) const {
    return _bandData[_dataDescToBand[dataDescID]];
  }

  /**
   * Retrieve the first band.
   * @returns The first band.
   */
  const BandData& FirstBand() const { return _bandData.front(); }

  /**
   * Get number of bands stored.
   * @returns Number of bands.
   */
  size_t BandCount() const { return _bandData.size(); }

  /**
   * Returns the unique number of data description IDs.
   * @returns Unique number of data desc IDs.
   */
  size_t DataDescCount() const { return _dataDescToBand.size(); }

  /**
   * Get lowest frequency.
   * @returns The channel frequency of the channel with lowest frequency.
   */
  double LowestFrequency() const {
    double freq = _bandData[0].LowestFrequency();
    for (size_t i = 0; i != _bandData.size(); ++i)
      freq = std::min(freq, _bandData[i].LowestFrequency());
    return freq;
  }

  /**
   * Get centre frequency.
   * @returns (LowestFrequency() + HighestFrequency()) * 0.5.
   */
  double CentreFrequency() const {
    return (LowestFrequency() + HighestFrequency()) * 0.5;
  }

  /**
   * Get highest frequency.
   * @returns The channel frequency of the channel with highest frequency.
   */
  double HighestFrequency() const {
    double freq = _bandData[0].HighestFrequency();
    for (size_t i = 0; i != _bandData.size(); ++i)
      freq = std::max(freq, _bandData[i].HighestFrequency());
    return freq;
  }

  /**
   * Get total bandwidth covered.
   * @returns BandEnd() - BandStart().
   */
  double Bandwidth() const { return BandEnd() - BandStart(); }

  /**
   * Get the start frequency of the lowest frequency channel.
   * @return Start of covered bandwidth.
   */
  double BandStart() const {
    double freq = std::min(_bandData[0].BandStart(), _bandData[0].BandEnd());
    for (size_t i = 0; i != _bandData.size(); ++i)
      freq = std::min(
          freq, std::min(_bandData[i].BandStart(), _bandData[i].BandEnd()));
    return freq;
  }

  /**
   * Get the end frequency of the highest frequency channel.
   * @return End of covered bandwidth.
   */
  double BandEnd() const {
    double freq = std::max(_bandData[0].BandStart(), _bandData[0].BandEnd());
    for (size_t i = 0; i != _bandData.size(); ++i)
      freq = std::max(
          freq, std::max(_bandData[i].BandStart(), _bandData[i].BandEnd()));
    return freq;
  }

  /**
   * Get the maximum number of channels in a band.
   * @returns Maximum number of channels.
   */
  size_t MaxChannels() const {
    size_t maxChannels = 0;
    for (std::vector<BandData>::const_iterator i = _bandData.begin();
         i != _bandData.end(); ++i) {
      if (i->ChannelCount() > maxChannels) maxChannels = i->ChannelCount();
    }
    return maxChannels;
  }

  /**
   * Map a dataDescId to the corresponding band index.
   * @param dataDescId A dataDescId as e.g. used in a main table.
   * @returns The band index, which is equal to the row index in the spw
   * table that describes the band in a measurement set.
   */
  size_t GetBandIndex(size_t dataDescId) const {
    return _dataDescToBand[dataDescId];
  }

  /**
   * Compose a list of dataDescIds that are used in the measurement set.
   * "Used" here means it is references from the main table.
   * @param mainTable the measurement set.
   * @returns Set of used dataDescIds.
   */
  std::set<size_t> GetUsedDataDescIds(
      casacore::MeasurementSet& mainTable) const;

  iterator begin() { return _bandData.begin(); }
  const_iterator begin() const { return _bandData.begin(); }

  iterator end() { return _bandData.end(); }
  const_iterator end() const { return _bandData.end(); }

 private:
  std::vector<size_t> _dataDescToBand;
  std::vector<BandData> _bandData;
};
}  // namespace internal
#endif
