//# RFFlagCube.h: this defines RFFlagCube
//# Copyright (C) 2000,2001,2002
//# Associated Universities, Inc. Washington DC, USA.
//#
//# This library is free software; you can redistribute it and/or modify it
//# under the terms of the GNU Library General Public License as published by
//# the Free Software Foundation; either version 2 of the License, or (at your
//# option) any later version.
//#
//# This library is distributed in the hope that it will be useful, but WITHOUT
//# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
//# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
//# License for more details.
//#
//# You should have received a copy of the GNU Library General Public License
//# along with this library; if not, write to the Free Software Foundation,
//# Inc., 675 Massachusetts Ave, Cambridge, MA 02139, USA.
//#
//# Correspondence concerning AIPS++ should be addressed as follows:
//#        Internet email: aips2-request@nrao.edu.
//#        Postal address: AIPS++ Project Office
//#                        National Radio Astronomy Observatory
//#                        520 Edgemont Road
//#                        Charlottesville, VA 22903-2475 USA
//#
//# $Id$
#ifndef FLAGGING_RFFLAGCUBE_H
#define FLAGGING_RFFLAGCUBE_H
    
//#include <flagging/Flagging/RedFlagger.h>
#include <flagging/Flagging/RFCubeLattice.h>
#include <flagging/Flagging/RFChunkStats.h>
#include <casacore/casa/Arrays/ArrayLogical.h>
#include <casacore/casa/Arrays/LogiMatrix.h>
#include <casacore/casa/Arrays/LogiVector.h>
#include <casacore/casa/Logging/LogIO.h>
#include <stdexcept>

namespace casa { //# NAMESPACE CASA - BEGIN

typedef RFCubeLatticeIterator<RFlagWord> FlagCubeIterator;

// special row flag masks. RowFlagged for flagged rows, 
// RowAbsent for absent rows
const RFlagWord RowFlagged=1,RowAbsent=2;

// casacore::Function for working with bitmasks. Does a bitwise-AND
// on every element, returns true if !=0 or false if ==0
template<class T> casacore::Array<T> operator & ( const casacore::Array<T> &,const T &);
// returns a casacore::LogicalArray corresponding to (ARR&MASK)!=0
template<class T> casacore::LogicalArray  maskBits  ( const casacore::Array<T> &,const T &);

// <summary>
// RFFlagCube: a cube of flags
// </summary>

// <use visibility=local>

// <reviewed reviewer="" date="" tests="" demos="">
// </reviewed>

// <prerequisite>
//   <li> RFCubeLattice
// </prerequisite>
//
// <synopsis>
// RFFlagCube implements an [NCHAN,NIFR,NTIME] cube of flags, stored in
// a casacore::TempLattice that is iterated alog the TIME axis.  One static
// (i.e. global) cube is used to hold the actual flags. Individual
// instances (instantiated by flagging agents) have individual unique
// bitmasks and, possibly, individual iterators.
//
// It was/is a design mistake to use a global/static buffer to hold the
// shared flags. Instead, every agent should point to the unique dynamically
// allocated buffer. 
// </synopsis>
//
// <example>
// </example>
//
// <motivation>
// </motivation>
//
// <todo asof="2001/04/16">
//   <li> add this feature
//   <li> fix this bug
//   <li> start discussion of this possible extension
// </todo>

class RFFlagCube : public FlaggerEnums
{
public:
  // default log sink
  static casacore::LogIO default_sink;
    
  // constructor
  RFFlagCube ( RFChunkStats &ch,casacore::Bool ignore=false,casacore::Bool reset=false,casacore::LogIO &os=default_sink );
  ~RFFlagCube ();

  // returns reference to logsink
  casacore::LogIO & logSink ();

  // returns estimated size of flag cube for a given chunk.
  static casacore::uInt estimateMemoryUse ( const RFChunkStats &ch );

  // creates flag cube for current chunk. name is name of agent.
  // nAgent is total number of agents
  void init ( RFlagWord polmsk, casacore::uInt nAgent, bool is_selector, const casacore::String &name = "" );

  // cleans up at end of chunk
  void cleanup ();

  // returns summary of stats in text form
  casacore::String getSummary ();

  // prints flagging stats to stderr
  void printStats ();

  // resets at start of pass
  void reset ();

  // advances global flag iterator to time slot it (if required), sets
  // the flag cursor from the iterator (see below). If getflags is true,
  // also calls getDataFlags().
  void advance   ( casacore::uInt it,casacore::Bool getFlags=false );

  // fills global flag lattice with apriori flags from a VisBuffer (if required)
  void getMSFlags  (casacore::uInt it);

  // transfers all flags from lattice into VisBuffer
  void setMSFlags  (casacore::uInt itime);

  // creates a custom iterator
  FlagCubeIterator newCustomIter ();

  // Returns full flag matrix (i.e. cursor of global iterator)
  const FlagMatrix & flagMatrix ();
  
  // sets or clears a flag at the given flag cursor
  casacore::Bool setFlag      ( casacore::uInt ich,casacore::uInt ifr,FlagCubeIterator &iter );
  casacore::Bool clearFlag    ( casacore::uInt ich,casacore::uInt ifr,FlagCubeIterator &iter );

  // Gets full flag word at the given flag cursor.
  RFlagWord getFlag ( casacore::uInt ich,casacore::uInt ifr,FlagCubeIterator &iter );

  // Versions of above that use global flag cursor
  casacore::Bool setFlag      ( casacore::uInt ich,casacore::uInt ifr );
  casacore::Bool clearFlag    ( casacore::uInt ich,casacore::uInt ifr );
  RFlagWord getFlag ( casacore::uInt ich,casacore::uInt ifr );
  
  // the preFlagged() function uses the corr-mask to tell if any of this
  // agent's correlations are pre-flagged. Uses internal cursor.
  casacore::Bool preFlagged   ( casacore::uInt ich,casacore::uInt ifr );

  // The anyFlagged() uses the corr-flagmask to tell if any of my
  // correlations are flagged either by any agent or pre-flagged
  // Uses internal cursor.
  casacore::Bool anyFlagged   ( casacore::uInt ich,casacore::uInt ifr );
  
  // Sets or clears a row flag
  casacore::Bool setRowFlag      ( casacore::uInt ifr,casacore::uInt itime );
  casacore::Bool clearRowFlag    ( casacore::uInt ifr,casacore::uInt itime );

  // Gets full row flag word
  RFlagWord getRowFlag ( casacore::uInt ifr,casacore::uInt itime );
  
  // tells if a row is pre-flagged in the casacore::MS (or does not exist)
  casacore::Bool rowPreFlagged   ( casacore::uInt ifr,casacore::uInt itime );  

  // tells if a row is flagged by any agent
  casacore::Bool rowAgentFlagged ( casacore::uInt ifr,casacore::uInt itime );  

  // preFlagged OR agentFlagged  
  casacore::Bool rowFlagged      ( casacore::uInt ifr,casacore::uInt itime );
  
  // returns reference to internal iterator
  FlagCubeIterator &  iterator ();
  
  // returns flag mask for this agent
  RFlagWord flagMask ();      

  // returns correlations mask for this agent
  RFlagWord corrMask ();

  // returns the checked-correlations mask for this agent
  // (=0 for RESET/IGNORE policies, or =corrMask() for HONOR policy).
  RFlagWord checkCorrMask ();

  // returns mask of all correlations
  static RFlagWord fullCorrMask ();

  // returns the number of instances of the flag cube
  static casacore::Int numInstances ();

  // sets the maximum memory usage for the flag cube  
  static void setMaxMem ( casacore::Int maxmem );
  // returns the current maximum memory usage
  static int  getMaxMem ();
      
 private:
  RFChunkStats &chunk;                  // chunk

  bool kiss;  // do things simpler (faster) if there is nothing but RFAselector agents
  bool kiss_flagrow;

  static casacore::Cube<casacore::Bool> in_flags;
  static int in_flags_time;  //time stamp that in_flags has reached
  static bool in_flags_flushed; // do we need to write the flags back for this time stamp?

  // shortcut to RFChunkStats::num
  casacore::uInt num ( StatEnums which ) { return chunk.num(which); }
      
  static RFCubeLattice<RFlagWord> flag; // global flag lattice
  static FlagMatrix flagrow;             // (nIfr,nTime) matrix of row flags
  static casacore::Matrix<std::vector<bool> > flagrow_kiss;
  static casacore::Int pos_get_flag,pos_set_flag; 

  static casacore::Bool reset_preflags; // flag: RESET policy specified for at least one instance
  
  static casacore::uInt npol,nchan;
  
  // Flag mask used by this instance. Each instance has a unique 1-bit mask.
  // This is assigned automatically in the constructor, by updating the 
  // instance count and the nextmask member.
  // Note that the low N bits of a mask are assigned to pre-flags (one per
  // each correlation in the casacore::MS); so the agents start at bit N+1.
  RFlagWord flagmask,       // flagmask of this instance
    corrmask,        // corrmask of this instance (corrs used/flagged by it)
    check_corrmask,  // mask checked by preFlagged() & co. Set to 0 for
    // RESET or IGNORE policy, or to corrmask for HONOR
    check_rowmask,   // same for row flags: 0 or RowFlagged
    my_corrflagmask; // see above
  unsigned long flagmask_kiss; // represents a bitmask with only bit number <n> set where 
                          // <n> is the value of this variable
  static casacore::Int agent_count;    // # of agents instantiated
  static RFlagWord base_flagmask, // flagmask of first agent instance
    full_corrmask;          // bitmask for all correlations in casacore::MS (low N bits)

  // corr_flagmask is a mapping from corrmasks into masks of agents that flag the
  // given corrmask
  static casacore::Vector<RFlagWord> corr_flagmask;
  
  // log sink
  casacore::LogIO os;

  // pre-flag policy (can be set on a per-instance basis)
  PreFlagPolicy pfpolicy;
  
  // flagging stats for this instance
  casacore::uInt tot_fl_raised,fl_raised,fl_cleared,
    tot_row_fl_raised,row_fl_raised,row_fl_cleared;
    
  // local flag cursor used by this instance (setFlag and clearFlag). 
  // Normally, set to flag.cursor() in advance(), but can be overridden
  // by setFlagCursor();
  FlagMatrix * flag_curs;
  casacore::uInt flag_itime;
  
  // number of instances in use
  static casacore::Int num_inst;
};

inline RFlagWord RFFlagCube::flagMask ()
  { 
     if (kiss) {
       throw std::logic_error("Cannot do this in kiss mode (program bug, please report)");
     }
     return flagmask; 
  }
 
inline RFlagWord RFFlagCube::corrMask ()
   { 
     return corrmask; 
   }

inline RFlagWord RFFlagCube::checkCorrMask ()
   { return check_corrmask; }

inline RFlagWord RFFlagCube::fullCorrMask ()
   { return full_corrmask; }

inline RFlagWord RFFlagCube::getFlag ( casacore::uInt ich,casacore::uInt ifr,FlagCubeIterator &iter )
   { 
     if (kiss) {
       /* Create the bitmap (integer) from the correlation flags
          relevant for this agent */
       RFlagWord f = 0;
       casacore::uInt c = 1;

       for (casacore::uInt icorr = 0; icorr < num(CORR); icorr++, c<<=1) {
         if ((c & corrmask) && 
             in_flags(icorr, ich, ifr)) {
           f |= c;
         }
       }
       return f;
     }
     else {
       return (iter)(ich,ifr); 
     }
   }

inline casacore::Bool RFFlagCube::setFlag ( casacore::uInt ich,casacore::uInt ifr ) 
   { return setFlag(ich,ifr,flag.iterator()); } 

inline casacore::Bool RFFlagCube::clearFlag ( casacore::uInt ich,casacore::uInt ifr ) 
   { return clearFlag(ich,ifr,flag.iterator()); } 

inline RFlagWord RFFlagCube::getFlag ( casacore::uInt ich,casacore::uInt ifr ) 
   { return getFlag(ich,ifr,flag.iterator()); } 

inline FlagCubeIterator RFFlagCube::newCustomIter ()
   { return flag.newIter(); }

inline const FlagMatrix & RFFlagCube::flagMatrix ()
   { return *flag_curs; }

inline casacore::Bool RFFlagCube::preFlagged ( casacore::uInt ich,casacore::uInt ifr )
   { return (getFlag(ich,ifr)&check_corrmask) != 0; }

inline casacore::Bool RFFlagCube::anyFlagged ( casacore::uInt ich,casacore::uInt ifr )
   { 
     if (kiss) {
       throw std::logic_error("Cannot do this in kiss mode (program bug, please report)");
     }
     return (getFlag(ich,ifr)&(check_corrmask|my_corrflagmask)) != 0;
   }

// Gets full row flag word
inline RFlagWord RFFlagCube::getRowFlag ( casacore::uInt ifr,casacore::uInt itime )
  {
    if (kiss) {
      throw std::logic_error("Cannot do this in kiss mode (program bug, please report)");
    }
    return flagrow(ifr,itime); 
  }

// tells if a row is pre-flagged in the casacore::MS (or does not exist)
inline casacore::Bool RFFlagCube::rowPreFlagged   ( casacore::uInt ifr,casacore::uInt itime )
   { return getRowFlag(ifr,itime)&check_rowmask; }

// tells if a row is flagged by any agent
inline casacore::Bool RFFlagCube::rowAgentFlagged ( casacore::uInt ifr,casacore::uInt itime )
   { return getRowFlag(ifr,itime)&~(RowFlagged|RowAbsent); }

// preFlagged OR agentFlagged  
inline casacore::Bool RFFlagCube::rowFlagged      ( casacore::uInt ifr,casacore::uInt itime )
   { return getRowFlag(ifr,itime)&(check_rowmask?~0:~RowFlagged); }

inline FlagCubeIterator & RFFlagCube::iterator ()
   { return flag.iterator(); }

inline int RFFlagCube::numInstances ()
   { return num_inst; }

inline casacore::LogIO & RFFlagCube::logSink ()
   { return os; }


} //# NAMESPACE CASA - END

#ifndef AIPS_NO_TEMPLATE_SRC
#include <flagging/Flagging/RFFlagCube.tcc>
#endif //# AIPS_NO_TEMPLATE_SRC
#endif