#ifndef __JLANG__JPIPE__
#define __JLANG__JPIPE__

#include "JLang/JType.hh"
#include "JLang/JTypeList.hh"
#include "JLang/JNullType.hh"
#include "JLang/JValve.hh"
#include "JLang/JObjectSelector.hh"
#include "JLang/JRegulator.hh"
#include "JLang/JObjectMultiplexer.hh"
#include "JLang/JSinglePointer.hh"


/**
 * \file
 *
 * Implementation of pipe operation for object iterators.
 * \author mdejong
 */

namespace JLANG {

  /**
   * Auxiliary class for object iteration via pipe, i.e.\ operator:
   * <pre>
   *      .. | ..
   * </pre>
   *
   * A pipe consists of an object iterator, a valve, an object selector and a common regulator.\n
   * The objects are first passed through a valve which can be opened and closed.\n
   * The object selector can be used to filter specific objects/values.\n
   * Finally, the regulator can be used to control the throughput.
   *
   * This class implements the JLANG::JObjectIterator interface.
   */
  template<class T>
  class JPipe :
    public virtual JObjectIterator<T>
  {
  public:

    typedef typename JObjectIterator<T>::pointer_type  pointer_type;

    /**
     * Constructor.
     *
     * \param  input      object iterator
     * \param  valve      valve
     * \param  selector   object selector
     * \param  regulator  regulator
     */
    JPipe(JObjectIterator<T>&       input,
	  const JValve<T>&          valve,
	  const JObjectSelector<T>& selector,
	  const JRegulator&         regulator) :
      in       (input),
      valve    (valve),
      selector (selector),
      regulator(regulator)
    {}


    /**
     * Check availability of next element.
     *
     * \return            true if the iteration has more elements; else false
     */
    virtual bool hasNext() override 
    {
      if (!p.is_valid()) {
      
	if (valve.is_open()) {
	
	  while (in.hasNext()) {

	    p = in.next();
	    
	    if (selector.accept(*p)) {
	      
	      if (regulator.accept()) {
		return true;
	      }
	    }
	  }
	}

	p = NULL; // invalid pointer for next round
	
	return false;
	
      } else {

	return true;
      }
    }


    /**
     * Get next element.
     *
     * \return            pointer to element
     */
    virtual const pointer_type& next() override 
    {
      ps = p;

      p.reset();

      return ps;
    }


    /**
     * Skip items.
     *
     * \param  ns         number of items to skip
     * \return            number of items skipped
     */
    virtual skip_type skip(const skip_type ns) override 
    {
      return in.skip(ns);
    }


  protected:
    JObjectIterator<T>&        in;
    const JValve<T>&           valve;
    const JObjectSelector<T>&  selector;
    const JRegulator&          regulator;

  private:
    pointer_type ps;
    pointer_type p;
  };


  /**
   * Implementation of object iterator for multiple data types.
   *
   * This class recursively defines the JLANG::JObjectIterator interface
   * for all data types by deriving from:
   *  - JPipe<JHead_t>; and 
   *  - JPipe<JTail_t>.
   */
  template<class JHead_t, class JTail_t>
  class JPipe< JTypeList<JHead_t, JTail_t> > :
    public JPipe<JHead_t>,
    public JPipe<JTail_t>,
    public virtual JObjectIterator< JTypeList<JHead_t, JTail_t> >
  {
  public:
    typedef JTypeList<JHead_t, JTail_t>                typelist;
    
    /**
     * Constructor.
     *
     * \param  input      object iterator
     * \param  valve      valve
     * \param  selector   object selector
     * \param  regulator  regulator
     */
    JPipe(JObjectIterator<typelist>&       input,
	  const JValve<typelist>&          valve,
	  const JObjectSelector<typelist>& selector,
	  const JRegulator&                regulator) :
      JPipe<JHead_t>(input, valve, selector, regulator),
      JPipe<JTail_t>(input, valve, selector, regulator)
    {}
  };


  /**
   * Terminator class of recursive JPipe class.
   */
  template<class JHead_t>
  class JPipe< JTypeList<JHead_t, JNullType> > :
    public JPipe<JHead_t>
  {
  public:
    /**
     * Constructor.
     *
     * \param  input      object iterator
     * \param  valve      valve
     * \param  selector   object selector
     * \param  regulator  regulator
     */
    JPipe(JObjectIterator<JHead_t>&       input,
	  const JValve<JHead_t>&          valve,
	  const JObjectSelector<JHead_t>& selector,
	  const JRegulator&               regulator) :
      JPipe<JHead_t>(input, valve, selector, regulator)
    {}
  };


  /**
   * Auxiliary class for object iteration via multiple pipes, e.g.\ operator:
   * <pre>
   *      .. | .. | ..
   * </pre>
   */  
  template<class T, int N>
  class JMultiPipe :
    public JPipe<T>
  {
  public:
    /**
     * Constructor.
     *
     * \param  input      object iterator
     */
    JMultiPipe(JObjectIterator<T>& input) :
      JPipe<T>(input, JValve<T>::getDefault(), JObjectSelector<T>::getDefault(), JRegulator::getDefault())
    {}

    
    /**
     * Constructor.
     *
     * \param  input      object iterator
     * \param  valve      valve
     */
    JMultiPipe(JObjectIterator<T>& input,
	       const JValve<T>&    valve) :
      JPipe<T>(input, valve, JObjectSelector<T>::getDefault(), JRegulator::getDefault())
    {}

    
    /**
     * Constructor.
     *
     * \param  input      object iterator
     * \param  selector   object selector
     */
    JMultiPipe(JObjectIterator<T>&       input,
	       const JObjectSelector<T>& selector) :
      JPipe<T>(input, JValve<T>::getDefault(), selector, JRegulator::getDefault())
    {}

    
    /**
     * Constructor.
     *
     * \param  input      object iterator
     * \param  regulator  regulator
     */
    JMultiPipe(JObjectIterator<T>& input,
	       const JRegulator&   regulator) :
      JPipe<T>(input, JValve<T>::getDefault(), JObjectSelector<T>::getDefault(), regulator)
    {}

    
    /**
     * Constructor.
     *
     * \param  input      object iterator
     * \param  valve      valve
     * \param  selector   object selector
     * \param  regulator  regulator
     */
    JMultiPipe(JObjectIterator<T>&       input,
	       const JValve<T>&          valve,
	       const JObjectSelector<T>& selector,
	       const JRegulator&         regulator) :
      JPipe<T>(input, valve, selector, regulator)
    {}

    
    /**
     * Pipe terminator.
     * 
     * \param  left       pipe
     * \param  right      object output
     */
    friend inline void operator|(JMultiPipe& left, JObjectOutput<T>& right)
    {
      left >> right;
    }
    

    /**
     * Recursive expansion of multi-pipe.
     *
     * \param  left        multi-pipe
     * \param  right       object valve
     * \return             multi-pipe
     */
    friend inline JMultiPipe<T, N+1>& operator|(JMultiPipe& left, const JValve<T>& right)
    {
      JMultiPipe<T, N+1>::pipe.reset(new JMultiPipe<T, N+1>(left, right));
      
      return *JMultiPipe<T, N+1>::pipe;
    }


    /**
     * Recursive expansion of multi-pipe.
     *
     * \param  left        multi-pipe
     * \param  right       object selector
     * \return             multi-pipe
     */
    friend inline JMultiPipe<T, N+1>& operator|(JMultiPipe& left, const JObjectSelector<T>& right)
    {
      JMultiPipe<T, N+1>::pipe.reset(new JMultiPipe<T, N+1>(left, right));
    
      return *JMultiPipe<T, N+1>::pipe;
    }


    /**
     * Recursive expansion of multi-pipe.
     *
     * \param  left        multi-pipe
     * \param  right       regulator
     * \return             multi-pipe
     */
    friend inline JMultiPipe<T, N+1>& operator|(JMultiPipe& left, const JRegulator& right)
    {
      JMultiPipe<T, N+1>::pipe.reset(new JMultiPipe<T, N+1>(left, right));
      
      return *JMultiPipe<T, N+1>::pipe;
    }


    /**
     * Pipe operator for multiplexing.
     *
     * \param  left       object iterator
     * \param  right      data type
     * \return            object multiplexer
     */
    template<class JBase_t>
    friend inline JObjectMultiplexer<T, JBase_t>& operator|(JMultiPipe& left, const JType<JBase_t>& right)
    {
      JObjectMultiplexer<T, JBase_t>::multiplexer.reset(new JObjectMultiplexer<T, JBase_t>(left));

      return *JObjectMultiplexer<T, JBase_t>::multiplexer;
    }


    static JSinglePointer< JMultiPipe<T, N> > pipe; //!< Declaration of common pipe
  };

  /**
   * Definition of common pipe.
   */
  template<class T, int N>
  JSinglePointer< JMultiPipe<T, N> > JMultiPipe<T, N>::pipe;
}

#endif