# ifndef CPPAD_LOCAL_PLAY_PLAYER_HPP
# define CPPAD_LOCAL_PLAY_PLAYER_HPP
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
// SPDX-FileCopyrightText: Bradley M. Bell <bradbell@seanet.com>
// SPDX-FileContributor: 2003-22 Bradley M. Bell
// ----------------------------------------------------------------------------

# include <cppad/local/play/addr_enum.hpp>
# include <cppad/local/play/sequential_iterator.hpp>
# include <cppad/local/play/subgraph_iterator.hpp>
# include <cppad/local/play/random_setup.hpp>
# include <cppad/local/atom_state.hpp>
# include <cppad/local/is_pod.hpp>

namespace CppAD { namespace local { // BEGIN_CPPAD_LOCAL_NAMESPACE
/*!
\file player.hpp
File used to define the player class.
*/

/*!
Class used to store and play back an operation sequence recording.

\tparam Base
These were AD< Base > operations when recorded. Operations during playback
are done using the type Base .
*/

template <class Base>
class player {
   // player<Base> must be a friend of player< AD<Base> > for base2ad to work
   template <class AnotherBase> friend class player;
private:
   // ----------------------------------------------------------------------
   // information that defines the recording

   /// Number of independent dynamic parameters
   size_t num_dynamic_ind_;

   /// Number of variables in the recording.
   size_t num_var_rec_;

   /// number of vecad load opeations in the reconding
   size_t num_var_load_rec_;

   /// Number of VecAD vectors in the recording
   size_t num_var_vecad_rec_;

   /// The operators in the recording.
   pod_vector<opcode_t> op_vec_;

   /// The operation argument indices in the recording
   pod_vector<addr_t> arg_vec_;

   /// Character strings ('\\0' terminated) in the recording.
   pod_vector<char> text_vec_;

   /// The VecAD indices in the recording.
   pod_vector<addr_t> all_var_vecad_ind_;

   /// All of the parameters in the recording.
   /// Use pod_maybe because Base may not be plain old data.
   pod_vector_maybe<Base> all_par_vec_;

   /// Which elements of all_par_vec_ are dynamic parameters
   /// (size equal number of parametrers)
   pod_vector<bool> dyn_par_is_;

   /// mapping from dynamic parameter index to parameter index
   /// 1: size equal to number of dynamic parameters
   /// 2: dyn_ind2par_ind_[j] < dyn_ind2par_ind_[j+1]
   pod_vector<addr_t> dyn_ind2par_ind_;

   /// operators for just the dynamic parameters
   /// (size equal number of dynamic parameters)
   pod_vector<opcode_t> dyn_par_op_;

   /// arguments for the dynamic parameter operators
   pod_vector<addr_t> dyn_par_arg_;

   // ----------------------------------------------------------------------
   // Information needed to use member functions that begin with random_
   // and for using const_subgraph_iterator.

   /// index in arg_vec_ corresonding to the first argument for each operator
   pod_vector<unsigned char> op2arg_vec_;

   /*!
   Index of the result variable for each operator. If the operator has
   no results, this is not defined. The invalid index num_var_rec_ is used
   when NDEBUG is not defined. If the operator has more than one result, this
   is the primary result; i.e., the last result. Auxillary are only used by
   the operator and not used by other operators.
   */
   pod_vector<unsigned char> op2var_vec_;

   /// Mapping from primary variable index to corresponding operator index.
   /// This is used to traverse sub-graphs of the operation sequence.
   /// This value is valid (invalid) for primary (auxillary) variables.
   pod_vector<unsigned char> var2op_vec_;

public:
   // =================================================================
   /// default constructor
   // set all scalars to zero to avoid valgraind warning when ani assignment
   // occures before values get set.
   player(void) :
   num_dynamic_ind_(0)  ,
   num_var_rec_(0)      ,
   num_var_load_rec_(0)  ,
   num_var_vecad_rec_(0)
   { }
   // move semantics constructor
   // (none of the default constructor values matter to the destructor)
   player(player& play)
   {  swap(play);  }
   // =================================================================
   /// destructor
   ~player(void)
   { }
   // ======================================================================
   /// type used for addressing iterators for this player
   play::addr_enum address_type(void) const
   {
      // required
      size_t required = 0;
      required = std::max(required, num_var_rec_   );  // number variables
      required = std::max(required, op_vec_.size()  ); // number operators
      required = std::max(required, arg_vec_.size() ); // number arguments
      //
      // unsigned short
      if( required <= std::numeric_limits<unsigned short>::max() )
         return play::unsigned_short_enum;
      //
      // unsigned int
      if( required <= std::numeric_limits<unsigned int>::max() )
         return play::unsigned_int_enum;
      //
      // unsigned size_t
      CPPAD_ASSERT_UNKNOWN(
         required <= std::numeric_limits<size_t>::max()
      );
      return play::size_t_enum;
   }
   // ===============================================================
   /*!
   Moving an operation sequence from a recorder to this player

   \param rec
   the object that was used to record the operation sequence. After this
   operation, the state of the recording is no longer defined. For example,
   the pod_vector member variables in this have been swapped with rec.

   \param n_ind
   the number of independent variables (only used for error checking
   when NDEBUG is not defined).

   \par
   Use an assert to check that the length of the following vectors is
   less than the maximum possible value for addr_t; i.e., that an index
   in these vectors can be represented using the type addr_t:
   op_vec_, all_var_vecad_ind_, arg_vec_, test_vec_, all_par_vec_, text_vec_,
   dyn_par_arg_.
   */
   void get_recording(recorder<Base>& rec, size_t n_ind)
   {
# ifndef NDEBUG
      size_t addr_t_max = size_t( std::numeric_limits<addr_t>::max() );
# endif
      // just set size_t values
      num_dynamic_ind_    = rec.num_dynamic_ind_;
      num_var_rec_        = rec.num_var_rec_;
      num_var_load_rec_   = rec.num_var_load_rec_;

      // op_vec_
      op_vec_.swap(rec.op_vec_);
      CPPAD_ASSERT_UNKNOWN(op_vec_.size() < addr_t_max );

      // op_arg_vec_
      arg_vec_.swap(rec.arg_vec_);
      CPPAD_ASSERT_UNKNOWN(arg_vec_.size()    < addr_t_max );

      // all_par_vec_
      all_par_vec_.swap(rec.all_par_vec_);
      CPPAD_ASSERT_UNKNOWN(all_par_vec_.size() < addr_t_max );

      // dyn_par_is_, dyn_par_op_, dyn_par_arg_
      dyn_par_is_.swap( rec.dyn_par_is_ );
      dyn_par_op_.swap( rec.dyn_par_op_ );
      dyn_par_arg_.swap( rec.dyn_par_arg_ );
      CPPAD_ASSERT_UNKNOWN(dyn_par_arg_.size() < addr_t_max );

      // text_rec_
      text_vec_.swap(rec.text_vec_);
      CPPAD_ASSERT_UNKNOWN(text_vec_.size() < addr_t_max );

      // all_var_vecad_ind_
      all_var_vecad_ind_.swap(rec.all_var_vecad_ind_);
      CPPAD_ASSERT_UNKNOWN(all_var_vecad_ind_.size() < addr_t_max );

      // num_var_vecad_rec_
      num_var_vecad_rec_ = 0;
      {  // all_var_vecad_ind_ contains size of each VecAD followed by
         // the parameter indices used to inialize it.
         size_t i = 0;
         while( i < all_var_vecad_ind_.size() )
         {  num_var_vecad_rec_++;
            i += size_t( all_var_vecad_ind_[i] ) + 1;
         }
         CPPAD_ASSERT_UNKNOWN( i == all_var_vecad_ind_.size() );
      }

      // mapping from dynamic parameter index to parameter index
      dyn_ind2par_ind_.resize( dyn_par_op_.size() );
      size_t i_dyn = 0;
      for(size_t i_par = 0; i_par < all_par_vec_.size(); ++i_par)
      {  if( dyn_par_is_[i_par] )
         {  dyn_ind2par_ind_[i_dyn] = addr_t( i_par );
            ++i_dyn;
         }
      }
      CPPAD_ASSERT_UNKNOWN( i_dyn == dyn_ind2par_ind_.size() );

      // random access information
      clear_random();

      // some checks
      check_inv_op(n_ind);
      check_variable_dag();
      check_dynamic_dag();
   }
   // ----------------------------------------------------------------------
   /*!
   Check that InvOp operators start with second operator and are contiguous,
   and there are n_ind of them.
   */
# ifdef NDEBUG
   void check_inv_op(size_t n_ind) const
   {  return; }
# else
   void check_inv_op(size_t n_ind) const
   {  play::const_sequential_iterator itr = begin();
      OpCode        op;
      const addr_t* op_arg;
      size_t        var_index;
      itr.op_info(op, op_arg, var_index);
      CPPAD_ASSERT_UNKNOWN( op == BeginOp );
      size_t i_op = 0;
      while( op != EndOp )
      {  // start at second operator
         (++itr).op_info(op, op_arg, var_index);
         ++i_op;
         CPPAD_ASSERT_UNKNOWN( (op == InvOp) == (i_op <= n_ind) );
      }
      return;
   }
# endif
   // ----------------------------------------------------------------------
   /*!
   Check variable graph to make sure arguments have value less
   than or equal to the previously created variable. This is the directed
   acyclic graph condition (DAG).
   */
# ifdef NDEBUG
   void check_variable_dag(void) const
   {  return; }
# else
   void check_variable_dag(void) const
   {  play::const_sequential_iterator itr = begin();
      OpCode        op;
      const addr_t* op_arg;
      size_t        var_index;
      itr.op_info(op, op_arg, var_index);
      CPPAD_ASSERT_UNKNOWN( op == BeginOp );
      //
      addr_t arg_var_bound = 0;
      while( op != EndOp )
      {  (++itr).op_info(op, op_arg, var_index);
         switch(op)
         {
            // cases where nothing to do
            case BeginOp:
            case EndOp:
            case EqppOp:
            case InvOp:
            case LdpOp:
            case LeppOp:
            case LtppOp:
            case NeppOp:
            case ParOp:
            case AFunOp:
            case FunapOp:
            case FunrpOp:
            case FunrvOp:
            case StppOp:
            break;

            // only first argument is a variable
            case AbsOp:
            case AcosOp:
            case AcoshOp:
            case AsinOp:
            case AsinhOp:
            case AtanOp:
            case AtanhOp:
            case CosOp:
            case CoshOp:
            case DivvpOp:
            case ErfOp:
            case ErfcOp:
            case ExpOp:
            case Expm1Op:
            case LevpOp:
            case LogOp:
            case Log1pOp:
            case LtvpOp:
            case NegOp:
            case PowvpOp:
            case SignOp:
            case SinOp:
            case SinhOp:
            case SqrtOp:
            case SubvpOp:
            case TanOp:
            case TanhOp:
            case FunavOp:
            case ZmulvpOp:
            CPPAD_ASSERT_UNKNOWN(op_arg[0] <= arg_var_bound );
            break;

            // only second argument is a variable
            case AddpvOp:
            case DisOp:
            case DivpvOp:
            case EqpvOp:
            case LdvOp:
            case LepvOp:
            case LtpvOp:
            case MulpvOp:
            case NepvOp:
            case PowpvOp:
            case StvpOp:
            case SubpvOp:
            case ZmulpvOp:
            CPPAD_ASSERT_UNKNOWN(op_arg[1] <= arg_var_bound );
            break;

            // only first and second arguments are variables
            case AddvvOp:
            case DivvvOp:
            case EqvvOp:
            case LevvOp:
            case LtvvOp:
            case MulvvOp:
            case NevvOp:
            case PowvvOp:
            case SubvvOp:
            case ZmulvvOp:
            CPPAD_ASSERT_UNKNOWN(op_arg[0] <= arg_var_bound );
            CPPAD_ASSERT_UNKNOWN(op_arg[1] <= arg_var_bound );
            break;

            // StpvOp
            case StpvOp:
            CPPAD_ASSERT_UNKNOWN(op_arg[2] <= arg_var_bound );
            break;

            // StvvOp
            case StvvOp:
            CPPAD_ASSERT_UNKNOWN(op_arg[1] <= arg_var_bound );
            CPPAD_ASSERT_UNKNOWN(op_arg[2] <= arg_var_bound );
            break;

            // CSumOp
            case CSumOp:
            {  CPPAD_ASSERT_UNKNOWN( 5 < op_arg[2] );
               for(addr_t j = 5; j < op_arg[2]; j++)
                  CPPAD_ASSERT_UNKNOWN(op_arg[j] <= arg_var_bound);
            }
            itr.correct_before_increment();
            break;

            // CExpOp
            case CExpOp:
            if( op_arg[1] & 1 )
               CPPAD_ASSERT_UNKNOWN( op_arg[2] <= arg_var_bound);
            if( op_arg[1] & 2 )
               CPPAD_ASSERT_UNKNOWN( op_arg[3] <= arg_var_bound);
            if( op_arg[1] & 4 )
               CPPAD_ASSERT_UNKNOWN( op_arg[4] <= arg_var_bound);
            if( op_arg[1] & 8 )
               CPPAD_ASSERT_UNKNOWN( op_arg[5] <= arg_var_bound);
            break;

            // PriOp
            case PriOp:
            if( op_arg[0] & 1 )
               CPPAD_ASSERT_UNKNOWN( op_arg[1] <= arg_var_bound);
            if( op_arg[0] & 2 )
               CPPAD_ASSERT_UNKNOWN( op_arg[3] <= arg_var_bound);
            break;

            // CSkipOp
            case CSkipOp:
            if( op_arg[1] & 1 )
               CPPAD_ASSERT_UNKNOWN( op_arg[2] <= arg_var_bound);
            if( op_arg[1] & 2 )
               CPPAD_ASSERT_UNKNOWN( op_arg[3] <= arg_var_bound);
            itr.correct_before_increment();
            break;

            default:
            CPPAD_ASSERT_UNKNOWN(false);
            break;


         }
         if( NumRes(op) > 0 )
         {  if( var_index > 0 )
            {  CPPAD_ASSERT_UNKNOWN(size_t(arg_var_bound) < var_index);
            }
            else
            {  CPPAD_ASSERT_UNKNOWN(size_t(arg_var_bound) == var_index);
            }
            //
            arg_var_bound = addr_t(var_index);
         }
      }
      return;
   }
# endif
   // ----------------------------------------------------------------------
   /*!
   Check dynamic parameter graph to make sure arguments have value less
   than or equal to the previously created dynamic parameter.
   This is the directed acyclic graph condition (DAG).
   */
# ifdef NDEBUG
   void check_dynamic_dag(void) const
   {  return; }
# else
   void check_dynamic_dag(void) const
   {  // number of dynamic parameters
      size_t num_dyn = dyn_par_op_.size();
      //
      size_t i_arg = 0; // initialize dynamic parameter argument index
      for(size_t i_dyn = 0; i_dyn < num_dyn; ++i_dyn)
      {  // i_par is parameter index
         addr_t i_par = dyn_ind2par_ind_[i_dyn];
         CPPAD_ASSERT_UNKNOWN( dyn_par_is_[i_par] );
         //
         // operator for this dynamic parameter
         op_code_dyn op = op_code_dyn( dyn_par_op_[i_dyn] );
         //
         // number of arguments for this dynamic parameter
         size_t n_arg       = num_arg_dyn(op);
         if( op == atom_dyn )
         {  size_t n = size_t( dyn_par_arg_[i_arg + 2] );
            size_t m = size_t( dyn_par_arg_[i_arg + 3] );
            n_arg    = 6 + n + m;
            CPPAD_ASSERT_UNKNOWN(
               n_arg == size_t( dyn_par_arg_[i_arg + 5 + n + m] )
            );
            for(size_t i = 5; i < n - 1; ++i)
               CPPAD_ASSERT_UNKNOWN( dyn_par_arg_[i_arg + i] <  i_par );
# ifndef NDEBUG
            for(size_t i = 5+n; i < 5+n+m; ++i)
            {  addr_t j_par = dyn_par_arg_[i_arg + i];
               CPPAD_ASSERT_UNKNOWN( (j_par == 0) || (j_par >= i_par) );
            }
# endif
         }
         else
         {  size_t num_non_par = num_non_par_arg_dyn(op);
            for(size_t i = num_non_par; i < n_arg; ++i)
               CPPAD_ASSERT_UNKNOWN( dyn_par_arg_[i_arg + i] < i_par);
         }
         //
         // next dynamic parameter
         i_arg += n_arg;
      }
      return;
   }
# endif
   // ===============================================================
   /*!
   Copy a player<Base> to another player<Base>

   \param play
   object that contains the operatoion sequence to copy.
   */
   void operator=(const player& play)
   {
      // size_t objects
      num_dynamic_ind_    = play.num_dynamic_ind_;
      num_var_rec_        = play.num_var_rec_;
      num_var_load_rec_   = play.num_var_load_rec_;
      num_var_vecad_rec_  = play.num_var_vecad_rec_;
      //
      // pod_vectors
      op_vec_             = play.op_vec_;
      arg_vec_            = play.arg_vec_;
      text_vec_           = play.text_vec_;
      all_var_vecad_ind_  = play.all_var_vecad_ind_;
      dyn_par_is_         = play.dyn_par_is_;
      dyn_ind2par_ind_    = play.dyn_ind2par_ind_;
      dyn_par_op_         = play.dyn_par_op_;
      dyn_par_arg_        = play.dyn_par_arg_;
      op2arg_vec_         = play.op2arg_vec_;
      op2var_vec_         = play.op2var_vec_;
      var2op_vec_         = play.var2op_vec_;
      //
      // pod_maybe_vectors
      all_par_vec_        = play.all_par_vec_;
   }
   // ===============================================================
   /// Create a player< AD<Base> > from this player<Base>
   player< AD<Base> > base2ad(void) const
   {  player< AD<Base> > play;
      //
      // size_t objects
      play.num_dynamic_ind_    = num_dynamic_ind_;
      play.num_var_rec_        = num_var_rec_;
      play.num_var_load_rec_   = num_var_load_rec_;
      play.num_var_vecad_rec_  = num_var_vecad_rec_;
      //
      // pod_vectors
      play.op_vec_             = op_vec_;
      play.arg_vec_            = arg_vec_;
      play.text_vec_           = text_vec_;
      play.all_var_vecad_ind_  = all_var_vecad_ind_;
      play.dyn_par_is_         = dyn_par_is_;
      play.dyn_ind2par_ind_    = dyn_ind2par_ind_;
      play.dyn_par_op_         = dyn_par_op_;
      play.dyn_par_arg_        = dyn_par_arg_;
      play.op2arg_vec_         = op2arg_vec_;
      play.op2var_vec_         = op2var_vec_;
      play.var2op_vec_         = var2op_vec_;
      //
      // pod_maybe_vector< AD<Base> > = pod_maybe_vector<Base>
      play.all_par_vec_.resize( all_par_vec_.size() );
      for(size_t i = 0; i < all_par_vec_.size(); ++i)
         play.all_par_vec_[i] = all_par_vec_[i];
      //
      return play;
   }
   // ===============================================================
   /// swap this recording with another recording
   /// (used for move semantics version of ADFun assignment operation)
   void swap(player& other)
   {  // size_t objects
      std::swap(num_dynamic_ind_,    other.num_dynamic_ind_);
      std::swap(num_var_rec_,        other.num_var_rec_);
      std::swap(num_var_load_rec_,   other.num_var_load_rec_);
      std::swap(num_var_vecad_rec_,  other.num_var_vecad_rec_);
      //
      // pod_vectors
      op_vec_.swap(             other.op_vec_);
      arg_vec_.swap(            other.arg_vec_);
      text_vec_.swap(           other.text_vec_);
      all_var_vecad_ind_.swap(  other.all_var_vecad_ind_);
      dyn_par_is_.swap(         other.dyn_par_is_);
      dyn_ind2par_ind_.swap(    other.dyn_ind2par_ind_);
      dyn_par_op_.swap(         other.dyn_par_op_);
      dyn_par_arg_.swap(        other.dyn_par_arg_);
      op2arg_vec_.swap(         other.op2arg_vec_);
      op2var_vec_.swap(         other.op2var_vec_);
      var2op_vec_.swap(         other.var2op_vec_);
      //
      // pod_maybe_vectors
      all_par_vec_.swap(    other.all_par_vec_);
   }
   // move semantics assignment
   void operator=(player&& play)
   {  swap(play); }
   // =================================================================
   /// Enable use of const_subgraph_iterator and member functions that begin
   // with random_(no work if already setup).
   template <class Addr>
   void setup_random(void)
   {  play::random_setup(
         num_var_rec_                               ,
         op_vec_                                    ,
         arg_vec_                                   ,
         op2arg_vec_.pod_vector_ptr<Addr>()         ,
         op2var_vec_.pod_vector_ptr<Addr>()         ,
         var2op_vec_.pod_vector_ptr<Addr>()
      );
   }
   /// Free memory used for functions that begin with random_
   /// and random iterators and subgraph iterators
   void clear_random(void)
   {
      op2arg_vec_.clear();
      op2var_vec_.clear();
      var2op_vec_.clear();
      CPPAD_ASSERT_UNKNOWN( op2arg_vec_.size() == 0  );
      CPPAD_ASSERT_UNKNOWN( op2var_vec_.size() == 0  );
      CPPAD_ASSERT_UNKNOWN( var2op_vec_.size() == 0  );
   }
   /// get non-const version of all_par_vec
   pod_vector_maybe<Base>& all_par_vec(void)
   {  return all_par_vec_; }
   /// get non-const version of all_par_vec
   const pod_vector_maybe<Base>& all_par_vec(void) const
   {  return all_par_vec_; }
   // ================================================================
   // const functions that retrieve infromation from this player
   // ================================================================
   /// const version of dynamic parameter flag
   const pod_vector<bool>& dyn_par_is(void) const
   {  return dyn_par_is_; }
   /// const version of dynamic parameter index to parameter index
   const pod_vector<addr_t>& dyn_ind2par_ind(void) const
   {  return dyn_ind2par_ind_; }
   /// const version of dynamic parameter operator
   const pod_vector<opcode_t>& dyn_par_op(void) const
   {  return dyn_par_op_; }
   /// const version of dynamic parameter arguments
   const pod_vector<addr_t>& dyn_par_arg(void) const
   {  return dyn_par_arg_; }
   /*!
   \brief
   fetch an operator from the recording.

   \return
   the i-th operator in the recording.

   \param i
   the index of the operator in recording
   */
   OpCode GetOp (size_t i) const
   {  return OpCode(op_vec_[i]); }

   /*!
   \brief
   Fetch a VecAD index from the recording.

   \return
   the i-th VecAD index in the recording.

   \param i
   the index of the VecAD index in recording
   */
   size_t GetVecInd (size_t i) const
   {  return size_t( all_var_vecad_ind_[i] ); }

   /*!
   \brief
   Fetch a parameter from the recording.

   \return
   the i-th parameter in the recording.

   \param i
   the index of the parameter in recording
   */
   Base GetPar(size_t i) const
   {  return all_par_vec_[i]; }

   /*!
   \brief
   Fetch entire parameter vector from the recording.

   \return
   the entire parameter vector.

   */
   const Base* GetPar(void) const
   {  return all_par_vec_.data(); }

   /*!
   \brief
   Fetch a '\\0' terminated string from the recording.

   \return
   the beginning of the string.

   \param i
   the index where the string begins.
   */
   const char *GetTxt(size_t i) const
   {  CPPAD_ASSERT_UNKNOWN(i < text_vec_.size() );
      return text_vec_.data() + i;
   }

   /// Fetch number of independent dynamic parameters in the recording
   size_t num_dynamic_ind(void) const
   {  return num_dynamic_ind_; }

   /// Fetch number of dynamic parameters in the recording
   size_t num_dynamic_par(void) const
   {  return dyn_par_op_.size(); }

   /// Fetch number of dynamic parameters operator arguments in the recording
   size_t num_dynamic_arg(void) const
   {  return dyn_par_arg_.size(); }

   /// Fetch number of variables in the recording.
   size_t num_var_rec(void) const
   {  return num_var_rec_; }

   /// Fetch number of vecad load operations
   size_t num_var_load_rec(void) const
   {  return num_var_load_rec_; }

   /// Fetch number of operators in the recording.
   size_t num_op_rec(void) const
   {  return op_vec_.size(); }

   /// Fetch number of VecAD indices in the recording.
   size_t num_var_vecad_ind_rec(void) const
   {  return all_var_vecad_ind_.size(); }

   /// Fetch number of VecAD vectors in the recording
   size_t num_var_vecad_rec(void) const
   {  return num_var_vecad_rec_; }

   /// Fetch number of argument indices in the recording.
   size_t num_op_arg_rec(void) const
   {  return arg_vec_.size(); }

   /// Fetch number of parameters in the recording.
   size_t num_par_rec(void) const
   {  return all_par_vec_.size(); }

   /// Fetch number of characters (representing strings) in the recording.
   size_t num_text_rec(void) const
   {  return text_vec_.size(); }

   /// A measure of amount of memory used to store
   /// the operation sequence, just lengths, not capacities.
   /// In user api as f.size_op_seq(); see the file fun_property.omh.
   size_t size_op_seq(void) const
   {  // check assumptions made by ad_fun<Base>::size_op_seq()
      CPPAD_ASSERT_UNKNOWN( op_vec_.size() == num_op_rec() );
      CPPAD_ASSERT_UNKNOWN( arg_vec_.size()    == num_op_arg_rec() );
      CPPAD_ASSERT_UNKNOWN( all_par_vec_.size() == num_par_rec() );
      CPPAD_ASSERT_UNKNOWN( text_vec_.size() == num_text_rec() );
      CPPAD_ASSERT_UNKNOWN( all_var_vecad_ind_.size() == num_var_vecad_ind_rec() );
      return op_vec_.size()        * sizeof(opcode_t)
             + arg_vec_.size()       * sizeof(addr_t)
             + all_par_vec_.size()   * sizeof(Base)
             + dyn_par_is_.size()    * sizeof(bool)
             + dyn_ind2par_ind_.size() * sizeof(addr_t)
             + dyn_par_op_.size()    * sizeof(opcode_t)
             + dyn_par_arg_.size()   * sizeof(addr_t)
             + text_vec_.size()      * sizeof(char)
             + all_var_vecad_ind_.size() * sizeof(addr_t)
      ;
   }
   /// A measure of amount of memory used for random access routine
   /// In user api as f.size_random(); see the file fun_property.omh.
   size_t size_random(void) const
   {
# ifndef NDEBUG
      if( op2arg_vec_.size() == 0 )
      {  CPPAD_ASSERT_UNKNOWN( op2var_vec_.size() == 0 );
         CPPAD_ASSERT_UNKNOWN( var2op_vec_.size() == 0 );
      }
      else
      {  size_t size = 0;
         switch( address_type() )
         {  case play::unsigned_short_enum:
            size = sizeof(unsigned short);
            break;
            //
            case play::unsigned_int_enum:
            size = sizeof(unsigned int);
            break;
            //
            case play::size_t_enum:
            size = sizeof(size_t);
            break;

            default:
            CPPAD_ASSERT_UNKNOWN(false);
            break;
         }
         CPPAD_ASSERT_UNKNOWN( op2arg_vec_.size()/size  == num_op_rec() );
         CPPAD_ASSERT_UNKNOWN( op2var_vec_.size()/size  == num_op_rec() );
         CPPAD_ASSERT_UNKNOWN( var2op_vec_.size()/size  == num_var_rec() );
      }
# endif
      CPPAD_ASSERT_UNKNOWN( sizeof(unsigned char) == 1 );
      return op2arg_vec_.size()
             + op2var_vec_.size()
             + var2op_vec_.size()
      ;
   }
   // -----------------------------------------------------------------------
   /// const sequential iterator begin
   play::const_sequential_iterator begin(void) const
   {  size_t op_index = 0;
      size_t num_var  = num_var_rec_;
      return play::const_sequential_iterator(
         num_var, &op_vec_, &arg_vec_, op_index
      );
   }
   /// const sequential iterator end
   play::const_sequential_iterator end(void) const
   {  size_t op_index = op_vec_.size() - 1;
      size_t num_var  = num_var_rec_;
      return play::const_sequential_iterator(
         num_var, &op_vec_, &arg_vec_, op_index
      );
   }
   // -----------------------------------------------------------------------
   /// const subgraph iterator begin
   play::const_subgraph_iterator<addr_t>  begin_subgraph(
      const play::const_random_iterator<addr_t>& random_itr ,
      const pod_vector<addr_t>*                  subgraph   ) const
   {  size_t subgraph_index = 0;
      return play::const_subgraph_iterator<addr_t>(
         random_itr,
         subgraph,
         subgraph_index
      );
   }
   /// const subgraph iterator end
   template <class Addr>
   play::const_subgraph_iterator<Addr>  end_subgraph(
      const play::const_random_iterator<Addr>&   random_itr ,
      const pod_vector<addr_t>*                  subgraph   ) const
   {  size_t subgraph_index = subgraph->size() - 1;
      return play::const_subgraph_iterator<Addr>(
         random_itr,
         subgraph,
         subgraph_index
      );
   }
   // -----------------------------------------------------------------------
   /// const random iterator
   template <class Addr>
   play::const_random_iterator<Addr> get_random(void) const
   {  return play::const_random_iterator<Addr>(
         op_vec_,
         arg_vec_,
         op2arg_vec_.pod_vector_ptr<Addr>(),
         op2var_vec_.pod_vector_ptr<Addr>(),
         var2op_vec_.pod_vector_ptr<Addr>()
      );
   }
};

} } // END_CPPAD_lOCAL_NAMESPACE
# endif
