// ---------------------------------------------------------------------------
// - fifo.tcc                                                                -
// - afnix nuts and bolts - fifo template                                    -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - This program  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.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2023 amaury darsch                                   -
// ---------------------------------------------------------------------------

#ifndef  AFNIX_FIFO_TCC
#define  AFNIX_FIFO_TCC

namespace afnix {

  /// The fifo template is a generic resizable container that provides support
  /// for a fifo (first-in/first-out) behavior. The fifo is not self-resizable
  /// but can be resized upon request.
  /// @author amaury darsch

  template <typename T>
  class t_fifo {
  protected:
    /// the fifo size
    long d_size;
    /// the fifo length
    long d_flen;
    /// the push index
    long d_ilen;
    /// the pop index
    long d_olen;
    /// the fifo data
    T* p_data;
    
  public:
    /// create a null fifo
    t_fifo (void) {
      d_size = 0L;
      d_flen = 0L;
      d_ilen = 0L;
      d_olen = 0L;
      p_data = nullptr;
    }

    /// create a fifo by size
    /// @param size the fifo size
    t_fifo (const long size) {
      d_size = (size <= 0L) ? 0L : size;
      d_flen = 0L;
      d_ilen = 0L;
      d_olen = 0L;
      p_data = (d_size == 0L) ? nullptr : new T[d_size];
    }

    /// copy construct this fifo
    /// @param that the fifo to copy
    t_fifo (const t_fifo<T>& that) {
      d_size = that.d_size;
      d_flen = that.d_flen;
      d_ilen = 0L;
      d_olen = 0L;
      p_data = nullptr;
      if (d_size > 0L) {
	p_data = new T[d_size];
	for (long k = 0L; k < d_flen; k++) {
	  p_data[d_ilen++] = that.p_data[(that.d_olen+k)%d_size];
	}
      }
    }

    /// copy move this fifo
    /// @param that the fifo to copy
    t_fifo (t_fifo<T>&& that) noexcept {
      d_size = that.d_size; that.d_size = 0L;
      d_flen = that.d_flen; that.d_flen = 0L;
      d_ilen = that.d_ilen; that.d_ilen = 0L;
      d_olen = that.d_olen; that.d_olen = 0L;
      p_data = that.p_data; that.p_data = nullptr;
    }

    /// destroy this fifo
    ~t_fifo (void) {
      delete [] p_data;
    }

    /// assign a fifo to this one
    /// @param that the fifo to assign
    t_fifo<T>& operator = (const t_fifo<T>& that) {
      // check for self-assignation
      if (this == &that) return *this;
      // clean old fifo
      reset ();
      // assign the fifo
      d_size = that.d_size;
      d_flen = that.d_flen;
      d_ilen = 0L;
      d_olen = 0L;
      p_data = nullptr;
      if (d_size > 0L) {
	p_data = new T[d_size];
	for (long k = 0L; k < d_flen; k++) {
	  p_data[d_ilen++] = that.p_data[(that.d_olen+k)%d_size];
	}
      }
      return *this;
    }

    /// move a fifo into this one
    /// @param that the fifo to copy
    t_fifo<T>& operator = (t_fifo<T>&& that) noexcept {
      // check for self move
      if (this == &that) return *this;
      // move the fifo
      d_size = that.d_size; that.d_size = 0L;
      d_flen = that.d_flen; that.d_flen = 0L;
      d_ilen = that.d_ilen; that.d_ilen = 0L;
      d_olen = that.d_olen; that.d_olen = 0L;
      p_data = that.p_data; that.p_data = nullptr;
      return *this;
    }

    /// reset this fifo
    void reset (void) {
      d_size = 0L;
      d_flen = 0L;
      d_ilen = 0L;
      d_olen = 0L;
      delete [] p_data; p_data = nullptr;
    }

    /// @return the fifo size
    long getsize (void) const {
      return d_size;
    }
    
    /// @return the fifo length
    long length (void) const {
      return d_flen;
    }
    
    /// resize a fifo by size
    /// @param size the fifo size
    void resize (const long size) {
      // a null size resets the fifo
      if (size == 0L) {
	reset ();
	return;
      }
      // check for consistency
      if ((size <= 0L) || (size <= d_size)) return;
      // allocate a new fifo and copy
      T* data = new T[size];
      for (long k = 0; k < d_flen; k++) data[k] = p_data[(d_olen+k)%d_size];
      delete [] p_data; p_data = data; d_size = size;
      d_ilen = d_flen; d_olen = 0L;
    }

    /// @return true if the fifo is empty
    bool empty (void) const {
      return (d_flen == 0L);
    }
    
    /// @return true if the fifo is full
    bool full (void) const {
      return (d_flen == d_size);
    }
    
    /// push an element in the fifo
    /// @param val the value to push
    void push (const T& val) {
      // check for resize
      if (d_size == 0L) resize (64L);
      // push the value
      p_data[d_ilen] = val;
      d_ilen = (d_ilen + 1L) % d_size;
      // drop if full
      if (d_flen == d_size) {
	d_olen = (d_olen + 1L) % d_size;
      } else {
	d_flen++;
      }
    }

    /// pop an element from the fifo
    T pop (void) {
      // check for consistency
      if (d_flen <= 0L) throw "nil fifo in pop operation";
      // get the value
      T result = p_data[d_olen];
      // adjust indexes
      d_olen = (d_olen + 1L) % d_size;
      d_flen--;
      return result;
    }

    /// @return an array value by position
    T get (const long pos) const {
      // check for consistency
      if ((pos < 0L) || (pos >= d_flen)) throw "fifo index out of bound";
      // get at position
      return p_data[(d_olen+pos)%d_size];
    }
  };
}

#endif
