// 
//  JoinObject.h
//  Qdos2 - Objects for "joining"
//
//  Janet Barnes
//  Sept 2000
//

#ifndef QDOS_JOINOBJECT_H
#define QDOS_JOINOBJECT_H

#include "Exception.h"
#include "Interp.h"

namespace QDOS{

class JoinException: public Exception
{
public:
    JoinException(ExceptionMsg m=""):Exception(m<<"[JoinException]"){;}
};
class BadJoin: public JoinException
{
public:
    BadJoin(ExceptionMsg m=""):\
        JoinException(m<<"[BadJoin]"){;}
};

template<class Tx,class Ty> class JoinObject;

template<class Tx,class Ty>
class Interpolator
{
protected:
    const Interp<Tx,Ty>* _interp;
public:
    Interpolator(const Interp<Tx,Ty>* interp):_interp(interp){;}
    virtual vector<bool> interpolate(JoinObject<Tx,Ty>* join_obj) const;
};

template<class Tx,class Ty>
class GapHandler:public Interpolator<Tx,Ty>
{
protected:
    Tx _gap_criterion;
public:
    GapHandler(const Interp<Tx,Ty>* interp,const Tx& gap_criterion):\
        Interpolator<Tx,Ty>(interp),_gap_criterion(gap_criterion){;}
    virtual vector<bool> interpolate(JoinObject<Tx,Ty>* join_obj) const;
};

template<class Tx,class Ty>
class FillGaps:public GapHandler<Tx,Ty>
{
protected:
    Ty _fill_value;
public:
    FillGaps(const Interp<Tx,Ty>* interp,const Tx& gap_criterion,\
        const Ty& fill_value):GapHandler<Tx,Ty>(interp,gap_criterion),\
        _fill_value(fill_value){;}
    virtual vector<bool> interpolate(JoinObject<Tx,Ty>* join_obj) const;
};

template<class Tx,class Ty>
class GapInterp:public GapHandler<Tx,Ty>
{
protected:
    const Interp<Tx,Ty>* _gap_interp;
public:
    GapInterp(const Interp<Tx,Ty>* interp,const Tx& gap_criterion,\
        const Interp<Tx,Ty>* gap_interp):GapHandler<Tx,Ty>(interp,gap_criterion),\
        _gap_interp(gap_interp){;}
    virtual vector<bool> interpolate(JoinObject<Tx,Ty>* join_obj) const;
};

template<class Tx>
class JoinKey
{
public:
    virtual ~ JoinKey(){;}
    virtual vector<bool> join()=0;
    virtual void remove_ypoints(const vector<bool>& mask)=0;
    virtual void set_join_points(NumericSequence<Tx>* join_pts)=0;
};

template<class Tx> class MultiJoinObject;

template<class Tx,class Ty>
class JoinObject: public JoinKey<Tx>
{
public:
    const NumericSequence<Tx>* _x_in;
    const  NumericSequence<Ty>* _y_in;
    NumericSequence<Tx>* _x_out;
    NumericSequence<Ty>* _y_out;
    Interpolator<Tx,Ty>* _interp;
    
    friend class Interpolator<Tx,Ty>;
    friend class MultiJoinObject<Tx>;
   
    JoinObject(){;}
    
    JoinObject(const NumericSequence<Tx>* x_in,\
        const NumericSequence<Ty>* y_in, NumericSequence<Tx>* x_out,\
        NumericSequence<Ty>* y_out, const Interp<Tx,Ty>* interp):_x_in(x_in),\
        _y_in(y_in),_x_out(x_out),_y_out(y_out)
        { _interp=new Interpolator<Tx,Ty>(interp);}
     
    JoinObject(const NumericSequence<Tx>* x_in,\
        const NumericSequence<Ty>* y_in,  NumericSequence<Tx>* x_out,\
        NumericSequence<Ty>* y_out, const Interp<Tx,Ty>* interp,\
        const Tx& gap_criterion):_x_in(x_in),_y_in(y_in),_x_out(x_out),\
        _y_out(y_out)
        { _interp=new GapHandler<Tx,Ty>(interp,gap_criterion);}

    JoinObject(const NumericSequence<Tx>* x_in,\
        const NumericSequence<Ty>* y_in,  NumericSequence<Tx>* x_out,\
        NumericSequence<Ty>* y_out, const Interp<Tx,Ty>* interp,\
        const Tx& gap_criterion, const Ty& fill_value):_x_in(x_in),_y_in(y_in),\
        _x_out(x_out),_y_out(y_out)
        { _interp=new FillGaps<Tx,Ty>(interp,gap_criterion,fill_value);}
        
    JoinObject(const NumericSequence<Tx>* x_in,\
        const NumericSequence<Ty>* y_in,  NumericSequence<Tx>* x_out,\
        NumericSequence<Ty>* y_out, const Interp<Tx,Ty>* interp,\
        const Tx& gap_criterion, const Interp<Tx,Ty>* gap_interp):_x_in(x_in),\
        _y_in(y_in),_x_out(x_out),_y_out(y_out)
        { _interp=new GapInterp<Tx,Ty>(interp,gap_criterion,gap_interp);}
        
    virtual ~JoinObject(){if(!_interp) delete _interp;}
    
    bool is_okay() const
    {
        return ((_x_in) && (_y_in) && (_x_out) && (_y_out));
    }
    
    virtual vector<bool> join()
    { 
        if(!_interp)
            throw BadJoin("No Interpolator");
        if (!is_okay())
            throw BadJoin("Null Data Pointer(s)");
        return _interp->interpolate(this);
    }
    
    virtual void set_join_points(NumericSequence<Tx>* join_pts)
    { _x_out=join_pts;}
    
  
    virtual void remove_ypoints(const vector<bool>& mask)
    { 
        if(_y_out!=0)            
            _y_out->erase(mask);
    }
                
};

template<class Tx,class Ty>
inline vector<bool> Interpolator<Tx,Ty>::interpolate(\
    JoinObject<Tx,Ty>* xy) const
{return _interp->operator()(*xy->_x_in,*xy->_y_in,\
    *xy->_x_out,*xy->_y_out);}
    
template<class Tx,class Ty>
inline vector<bool> GapHandler<Tx,Ty>::interpolate(\
    JoinObject<Tx,Ty>* xy) const
{return this->_interp->operator()(*xy->_x_in,*xy->_y_in,\
    *xy->_x_out,*xy->_y_out,_gap_criterion);}
    
template<class Tx,class Ty>    
inline vector<bool> FillGaps<Tx,Ty>::interpolate(\
    JoinObject<Tx,Ty>* xy) const
{
   return this->_interp->operator()(*xy->_x_in,*xy->_y_in,\
    *xy->_x_out,*xy->_y_out,this->_gap_criterion,_fill_value);
}

template<class Tx,class Ty>
inline vector<bool> GapInterp<Tx,Ty>::interpolate(\
    JoinObject<Tx,Ty>* xy) const
{return this->_interp->operator()(*xy->_x_in,*xy->_y_in,\
    *xy->_x_out,*xy->_y_out,this->_gap_criterion,_gap_interp);}

template<class Tx>
class MultiJoinObject: public vector<JoinKey<Tx>*>
{
protected:
    NumericSequence<Tx>* _join_pts;
public:
    MultiJoinObject():vector<JoinKey<Tx>*>(){;}
    ~MultiJoinObject()
    { int sz=this->size(); for( int i=sz-1;i>=0;i--) delete (*this)[i];}
    
    void assign(JoinKey<Tx>* join_obj) 
    { join_obj->set_join_points(_join_pts); push_back(join_obj);}
    
    void set_join_points(NumericSequence<Tx>* join_pts)
    { 
        _join_pts=join_pts;
        int sz=this->size();
        for(int i=0;i<sz;i++)
            (*this)[i]->set_join_points(join_pts);
    }
    
    vector<bool> multijoin(int triage_cnt=-1)
    {    
       int sz=this->size();
       // set triage cnt to zero if -1 (default)   
       if (triage_cnt==-1)
           triage_cnt=0;
       // ensure traige cnt is in range 0-nseries
       triage_cnt=std::min(std::max(0,triage_cnt),sz);
       
       if(!_join_pts)
            throw BadJoin("No Join Points specified");
    
       NumericSequence<int> cnt(_join_pts->size(),0);
          
       for(int i=0;i<sz;i++)
       {
           vector<bool> vb((*this)[i]->join());
           cnt.add_assign(vb);
           // if join_pts size is different to cnt, then gaps have been erased
           if(_join_pts->size()!=cnt.size())
           {
           // so get mask of erased points
               vector<bool> emsk(!vb);
           // update cnt and all join series prior to this one
               cnt.erase(emsk);
               for(int j=0;j<i;j++)
                   (*this)[j]->remove_ypoints(emsk);
           }
       }
       // remove pts where cnt is less than triage cnt
       SequenceMask msk(cnt.get_mask(NumericSequence<int>::LT(triage_cnt)));
                
       for(int i=0;i<sz;i++)
           (*this)[i]->remove_ypoints(msk);
       
       _join_pts->erase(msk);
     
       return !msk;    
    }      
        
};

}  // end namespace QDOS
#endif
                    
