/*
 * PointingDirectionCache.h
 *
 *  Created on: Dec 1, 2016
 *      Author: jjacobs
 */

#ifndef MSVIS_MSVIS_POINTINGDIRECTIONCACHE_H_
#define MSVIS_MSVIS_POINTINGDIRECTIONCACHE_H_

#include <casacore/measures/Measures/MDirection.h>

#include <memory>
#include <vector>

namespace casacore {
    class MDirection;
    class MeasurementSet;
    class MSPointingColumns;
}

namespace casa {
namespace vi {

struct Pointing;
class PointingDirectionCache;
class PointingSource;

namespace pd_cache {

// The namespace pd_cache contains the internal classes to the features provided
// by PointingDirectionCache.

enum class CacheAccessStatus : int {
    Hit,            // Found requested value in cache
    MissPrior,      // Desired value before earliest time in cache
    MissInternal,   // Desired value in between cache values
    MissPost        // Desired value is after last one in cache
};

class AntennaLevelCache;
class TimeLevelCache;

bool timeMatch (double time, double rowsTime, double rowsInterval);

class TimeLevelEntry {

public:

    TimeLevelEntry () = delete;
    TimeLevelEntry (const Pointing &, const TimeLevelCache *);
    TimeLevelEntry (const TimeLevelEntry &);
    ~TimeLevelEntry ();

    TimeLevelEntry& operator= (const TimeLevelEntry & other);

    const casacore::MDirection * getDirection () const;
    double getInterval () const;
    double getTime () const;

private:

    mutable std::unique_ptr<casacore::MDirection> direction_p; // own
    int    row_p;
    double timeCenter_p;
    const TimeLevelCache * timeLevelCache_p;
    double interval_p;

    const PointingSource * getPointingSource () const;
};

bool operator== (double t, const TimeLevelEntry &);
bool operator== (const TimeLevelEntry &, double t);
bool operator< (double t, const TimeLevelEntry &);
bool operator< (const TimeLevelEntry &, double t);

class TimeLevelCache {

public:

    TimeLevelCache (int minTimes, int maxTimes, const AntennaLevelCache * );

    void addEntry (Pointing & pointing);
    void flush ();
    std::pair<CacheAccessStatus, const casacore::MDirection *> getPointingDirection (double time);
    const PointingSource * getPointingSource () const;

private:

    typedef std::vector<TimeLevelEntry> Cache;
    const AntennaLevelCache * antennaLevelCache_p;
    Cache cache_p;
    int maxTimes_p;
    int minTimes_p;

};


class AntennaLevelCache {

public:

    AntennaLevelCache (int minTimes, int maxTimes, int nAntennas, const PointingDirectionCache *);
    AntennaLevelCache (const AntennaLevelCache & other) = delete;

    AntennaLevelCache & operator= (const AntennaLevelCache & other) = delete;

    void addEntry (Pointing & pointing);
    void flushTimes ();
    std::pair<CacheAccessStatus, const casacore::MDirection *> getPointingDirection (int antenna, double time);
    const PointingSource * getPointingSource () const;

private:

    const PointingDirectionCache * pointingDirectionCache_p;
    std::vector<TimeLevelCache> timeLevelCache_p;
};

} // end namepsace pd_cache

class PointingSource;

struct Pointing {

    // The direction and interval components are only usable if
    // "valid" is set to true.

    Pointing () : antennaId (0), direction (nullptr), interval (0), row (0), source (nullptr), time (0) {}
    Pointing (const Pointing & other)
    : antennaId (other.antennaId),
      direction (std::move (other.direction)),
      interval (other.interval),
      row (other.row),
      source (other.source),
      time (other.time)
    {}


    int antennaId;
    mutable std::unique_ptr<casacore::MDirection> direction;
    double interval;
    int row;
    const PointingSource * source;
    double time;
};

class PointingSource {

    // Generic interface to allow passing different pointing information
    // sources into the PointingDirectionCache.  The primary source
    // will be a POINTING subtable; the other likely source will be a unit
    // test source.

public:

    virtual ~PointingSource () {}

    virtual Pointing getPointingRow (int row, double targetTime, bool asMeasure) const = 0;
    virtual int nRows () const = 0;

protected:

private:

};

class PointingColumns : public PointingSource {

    // Wrapper class to provide pointing subtable information into
    // the cache.

public:

    PointingColumns (const casacore::MSPointingColumns &);

    virtual Pointing getPointingRow (int row, double targetTime, bool asMeasure) const override;
    virtual int nRows () const override;

private:

    const casacore::MSPointingColumns & pointingColumns_p;
};



class PointingDirectionCache {

public:

    PointingDirectionCache (int nAntennas, const PointingSource & pointingSource);
    ~PointingDirectionCache ();

    std::pair<bool, casacore::MDirection>
    getPointingDirection (int antennaId, double time, const casacore::MDirection & phaseCenter) const;

protected:

    friend class pd_cache::AntennaLevelCache; // allow access to getPointingSource

    void fillCache (int antenna, double time, bool flushAndRewind) const;
    const PointingSource * getPointingSource () const;
    bool noDataForAntenna (int antenna, double time) const;

private:

    mutable pd_cache::AntennaLevelCache antennaLevelCache_p;
    mutable std::vector<double> antennaEarliestTime_p;
    mutable int lastRowRead_p;
    mutable int nFallbacks_p;
    mutable int nHits_p;
    mutable bool pointingEofReached_p;
    const PointingSource & pointingSource_p;

    // These constants control the cache sizing.  The max value specifies how many entries
    // can be cached.  The min entry controls how many are kept when the cache size exceeds
    // the max value.

    static const int MaxTimeEntries = 3000;
    static const int MinTimeEntries = 1000;

};



} // end namespace vi
} // end namepsace casa

#endif /* MSVIS_MSVIS_POINTINGDIRECTIONCACHE_H_ */