//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org> 
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
#ifndef cwObject_H
#define cwObject_H


namespace cw
{
  
  enum
  {
   kInvalidTId = 0x00000000,
   kNullTId    = 0x00000001,
   kErrorTId   = 0x00000002,
   kCharTId    = 0x00000004,
   kInt8TId    = 0x00000008,
   kUInt8TId   = 0x00000010,
   kInt16TId   = 0x00000020,
   kUInt16TId  = 0x00000040,
   kInt32TId   = 0x00000080,
   kUInt32TId  = 0x00000100,
   kInt64TId   = 0x00000200,
   kUInt64TId  = 0x00000400,
   kFloatTId   = 0x00000800,
   kDoubleTId  = 0x00001000,
   kBoolTId    = 0x00002000,
   kStringTId  = 0x00004000,
   kCStringTId = 0x00008000,  // static string (don't delete)
   kVectTId    = 0x00010000,
   kPairTId    = 0x00020000,
   kListTId    = 0x00040000,
   kDictTId    = 0x00080000,
   kRootTId    = 0x00100000,

   kHexFl      = 0x10000000,
   kIdentFl    = 0x20000000,
   
   kOptFl      = 0x40000000,
   kReqFl      = 0x00000000,
   
  };

  typedef unsigned objTypeId_t;
  
  enum
  {
    // Value containers children are leaf-nodes: root,pair, or list are the only legal value containers.
    // Nnote that a dictionary is not a value container because it's children are pairs.
    kValueContainerFl = 0x01,
    
    // This node contains other nodes
    kContainerFl      = 0x02,  
  };
  
  enum
  {
   kRecurseFl      = 0x01,
   kOptionalFl     = 0x02
  };

  struct object_str;
  struct vect_str;

  typedef struct print_ctx_str
  {
    unsigned indent          = 0;
    bool     listOnOneLineFl = true;
  } print_ctx_t;
 
  typedef struct type_str
  {
    objTypeId_t id;
    const char* label;
    unsigned    flags;

    // Deallocate the the object body and value.
    void (*free)( struct object_str* o );

    // Deallocate the object value but not the object body
    void (*free_value)( struct object_str* o );

    rc_t (*value)( const struct object_str* o, unsigned tid, void* dst );

    // Print the object.
    void (*print)( const struct object_str* o, print_ctx_t& c );

    // Convert the object to a string and return the length of the string.
    unsigned( *to_string)( const struct object_str* o, char* buf, unsigned bufByteN );

    // Duplicate 'src'.
    struct object_str* (*duplicate)( const struct object_str* src, struct object_str* parent );
    
  } objType_t;


  struct object_str* newPairObject( const char* label, struct object_str* v, struct object_str* parent );
  struct object_str* newListObject( struct object_str* parent );

  typedef struct object_str
  {
    objType_t*         type    = nullptr;
    struct object_str* parent  = nullptr;
    struct object_str* sibling = nullptr;
    union
    {
      char             c;
      
      std::int8_t      i8;
      std::uint8_t     u8;
      std::int16_t     i16;
      std::uint16_t    u16;
      std::int32_t     i32;
      std::uint32_t    u32;
      long long        i64;
      unsigned long long u64;

      bool             b;
        
      float            f;
      double           d;
      
      char*            str;
      struct vect_str* vect;
      
      struct object_str* children; // 'children' is valid when is_container()==true
    } u;


    // Unlink this node from it's parents and siblings.
    void unlink();

    // free all resource associated with this object.
    void free();

    // Append the child node to this objects child list.
    rc_t append_child( struct object_str* child );
    
    unsigned child_count() const;

    // Value containers are parents of leaf nodes. (A dictionary is not a value container because it's children are pairs with are not leaf nodes.)
    inline bool is_value_container() const { return type != nullptr && cwIsFlag(type->flags,kValueContainerFl); }

    inline unsigned type_id() const { return type==nullptr ? (unsigned)kInvalidTId : type->id; }

    // Containers have children and use the object.u.children pointer.
    inline bool is_container() const { return type != nullptr && cwIsFlag(type->flags,kContainerFl); }
    inline bool is_pair()      const { return type != nullptr && type->id == kPairTId; }
    inline bool is_dict()      const { return type != nullptr && type->id == kDictTId; }
    inline bool is_list()      const { return type != nullptr && type->id == kListTId; }
    inline bool is_string()    const { return type != nullptr && (type->id == kStringTId || type->id == kCStringTId); }
    inline bool is_unsigned_integer() const { return type->id==kCharTId || type->id==kUInt8TId || type->id==kUInt16TId || type->id==kUInt32TId || type->id==kUInt64TId; }
    inline bool is_signed_integer()   const { return                   type->id==kInt8TId  || type->id==kInt16TId  || type->id==kInt32TId  || type->id==kInt64TId;  }
    inline bool is_floating_point()   const { return type->id==kFloatTId || type->id==kDoubleTId; }
    inline bool is_integer()          const { return is_unsigned_integer() || is_signed_integer(); }
    inline bool is_numeric()          const { return is_integer() || is_floating_point(); }
    inline bool is_type( unsigned tid ) const { return type != nullptr && type->id == tid; }

    rc_t value( void* dst, unsigned dstTypeId );
    rc_t value( char& v ) const;
    rc_t value( int8_t&  v ) const;
    rc_t value( uint8_t& v ) const;
    rc_t value( int16_t&  v ) const;
    rc_t value( uint16_t& v ) const;
    rc_t value( int32_t&  v ) const;
    rc_t value( uint32_t& v ) const;
    rc_t value( int64_t&  v ) const;
    rc_t value( uint64_t& v ) const;
    rc_t value( float&  v ) const;
    rc_t value( double& v ) const;
    rc_t value( bool& v ) const;
    rc_t value( char*& v ) const;
    rc_t value( const char*& v ) const;
    rc_t value( const struct object_str*& v) const {v=this; return kOkRC; }


    // Note that these setters will change the type of the object to match the value 'v'.
    // They do not convert 'v' to the current type of the object.
    rc_t set_value( char v );
    rc_t set_value( int8_t  v );
    rc_t set_value( uint8_t v );
    rc_t set_value( int16_t  v );
    rc_t set_value( uint16_t v );
    rc_t set_value( int32_t  v );
    rc_t set_value( uint32_t v );
    rc_t set_value( int64_t  v );
    rc_t set_value( uint64_t v );
    rc_t set_value( float  v );
    rc_t set_value( double v );
    rc_t set_value( bool v );
    rc_t set_value( char* v );
    rc_t set_value( const char* v );
    
    const char* pair_label() const;
    
    const struct object_str* pair_value() const;
    struct       object_str* pair_value();

    // Search for the pair label 'label'.
    // Return a pointer to the pair value associated with a given pair label.
    // Set flags to kRecurseFl to recurse into the object in search of the label.
    const struct object_str* find( const char* label, unsigned flags=0 ) const;
    struct       object_str* find( const char* label, unsigned flags=0 );
    
    const struct object_str* find_child( const char* label ) const { return find(label); }
    struct       object_str* find_child( const char* label )       { return find(label); }
    
    const struct object_str* child_ele( unsigned idx ) const;
    struct       object_str* child_ele( unsigned idx );

    // Set 'ele' to nullptr to return first child.  Returns nullptr when 'ele' is last child.
    const struct object_str* next_child_ele( const struct object_str* ele) const;
    struct       object_str* next_child_ele(       struct object_str* ele);

    typedef struct read_str
    {
      const char* label;
      unsigned    flags;
      const struct read_str* link;        
    } read_t;

    template< typename T >
    rc_t read( const char* label, unsigned flags, T& v ) const
    {
      const struct object_str* o;
      if((o = find(label, 0)) == nullptr )
      {
        if( cwIsNotFlag(flags, kOptFl) )
          return cwLogError(kInvalidIdRC,"The pair label '%s' could not be found.",cwStringNullGuard(label));
        
        return kEleNotFoundRC;        
      }
      else
      {
        flags = cwClrFlag(flags,kOptFl);
        if( flags &&  cwIsNotFlag(o->type->id,flags) )
          return cwLogError(kInvalidDataTypeRC,"The field '%s' data type 0x%x does not match 0x%x.",cwStringNullGuard(label),o->type->id,flags);
      }
      
      
      return o->value(v);
    }
    
    rc_t _readv(const read_t* list) const
    {
      rc_t rc = kOkRC;
      
      unsigned childN = child_count();
      
      // for each child of this dict node
      for(unsigned i=0; i<childN; ++i)
      {
        const struct object_str* child = child_ele(i);
        const char*              label = nullptr;
        const read_t*            r     = list;
          
        if( child == nullptr )
        {
          rc = cwLogError(kAssertFailRC,"A null child was encountered.");
          goto errLabel;
        }

        if( !child->is_pair() )
        {
          rc = cwLogError(kSyntaxErrorRC,"A non-pair element was encountered inside a dictionary.");
          goto errLabel;
        }

        if( (label = child->pair_label()) == nullptr )
        {
          rc = cwLogError(kInvalidStateRC,"A blank label was encountered as a dictionary label.");
          goto errLabel;
        }

        // verify that this is a known label
        // (all labels in the dictionary must be known - this prevents mispelled fields from being inadverently skipped during parsing)
        for(; r!=nullptr; r=r->link)
          if( strcmp(r->label,label) == 0 )
            break;

        if( r == nullptr )
        {
          rc = cwLogError(kSyntaxErrorRC,"The unknown field '%s' was encountered.",cwStringNullGuard(label));
          goto errLabel;
        }
        
      }

    errLabel:
      return rc;
    } 

    // readv("label0",v0,"label1",v1, ... )
    template< typename T0, typename T1, typename... ARGS >
    rc_t _readv( const read_t* list, T0 label, unsigned flags, T1& valRef, ARGS&&... args ) const
    {
      rc_t rc = read(label,flags,valRef);
      
      read_t r = { .label=label, .flags=flags, .link=list };

      // if no error occurred ....
      if( rc == kOkRC || (rc == kEleNotFoundRC && cwIsFlag(flags,kOptFl)))
        rc =  _readv(&r, std::forward<ARGS>(args)...); // ... recurse to find next label/value pair
      else
        rc = cwLogError(rc,"Object parse failed for the pair label:'%s'.",cwStringNullGuard(label));

      return rc;
    }

    
    // readv("label0",flags0,v0,"label1",flags0,v1, ... )
    // Use kReqFl/kOptFl for required/optional fields.
    // Use kListTId and kDictTId to validate the type of container fields.
    // In general it should not be necessary to validate numeric and string types because
    // they are validated by virtue of being converted to the returned value.
    // Note that readv() assumes that the list of possible fields given as input is complete
    // and any fields that it finds which are not in the list are not valid. This
    // validity check is the main difference between readv() and getv()/getv_opt().
    template< typename T0, typename T1, typename... ARGS >
    rc_t readv( T0 label, unsigned flags, T1& valRef, ARGS&&... args ) const
      { return _readv(nullptr, label,flags,valRef,args...); }


    
    

    // Set flag  'kRecurseFl' to recurse into the object in search of the value.
    // Set flag  'kOptionalFl' if the label is optional and may not exist.
    template< typename T >
      rc_t get( const char* label, T& v, unsigned flags=0  ) const
    {
      const struct object_str* o;
      if((o = find(label, flags)) == nullptr )
      {
        if( cwIsNotFlag(flags, kOptionalFl) )
          return cwLogError(kInvalidIdRC,"The pair label '%s' could not be found.",cwStringNullGuard(label));
        
        return kEleNotFoundRC;
        
      }
      return o->value(v);
    }
    
    rc_t _getv(unsigned flags) const { return kOkRC; } 

    // getv("label0",v0,"label1",v1, ... )
    template< typename T0, typename T1, typename... ARGS >
      rc_t _getv( unsigned flags, T0 label, T1& valRef, ARGS&&... args ) const
    {
      rc_t rc = get(label,valRef,flags);

      // if no error occurred ....
      if( rc == kOkRC || (rc == kEleNotFoundRC && cwIsFlag(flags,kOptionalFl)))
        rc =  _getv(flags, std::forward<ARGS>(args)...); // ... recurse to find next label/value pair
      else
        rc = cwLogError(rc,"Object parse failed for the pair label:'%s'.",cwStringNullGuard(label));

      return rc;
    }

    // getv("label0",v0,"label1",v1, ... )
    template< typename T0, typename T1, typename... ARGS >
      rc_t getv( T0 label, T1& valRef, ARGS&&... args ) const
    { return _getv(0,label,valRef,args...); }

    // getv("label0",v0,"label1",v1, ... ) where all values are optional
    template< typename T0, typename T1, typename... ARGS >
      rc_t getv_opt( T0 label, T1& valRef, ARGS&&... args ) const
    { return _getv(kOptionalFl,label,valRef,args...); }
    
    template< typename T >
      struct object_str* insert_pair( const char* label, const T& v )
    {  return newPairObject(label, v, this); }


    rc_t _putv()  { return kOkRC; } 

    // getv("label0",v0,"label1",v1, ... )
    template< typename T0, typename T1, typename... ARGS >
       rc_t _putv( T0 label, const T1& val, ARGS&&... args ) 
    {

      insert_pair(label,val);
      
      _putv(std::forward<ARGS>(args)...); // ... recurse to find next label/value pair

      return kOkRC;
    }

    
    // getv("label0",v0,"label1",v1, ... )
    template< typename T0, typename T1, typename... ARGS >
      rc_t putv( T0 label, const T1& val, ARGS&&... args )
    { return _putv(label,val,args...); }

    
    template< typename T >
    struct object_str* put_numeric_list( const char* label, const T* v, unsigned vN )
    {
      struct object_str* pair = newPairObject(label,newListObject(nullptr),this)->parent;
      struct object_str* list = pair->pair_value();
      for(unsigned i=0; i<vN; ++i)
        newObject(v[i],list);

      return pair;
    }

    

    template< typename T>
    rc_t set( const char* label, const T& value )
    {
      struct object_str* pair_value;
      if((pair_value = find_child(label)) == nullptr )
        return cwLogError(kInvalidIdRC,"Set failed the object dictionary label '%s' could not be found.",label);
      
      return pair_value->set_value( value );
    }

    
    // convert this object to a string
    unsigned to_string( char* buf, unsigned bufByteN ) const;
    char* to_string() const;

    // print this object
    void print(const print_ctx_t* c=NULL) const;

    // duplicate this object
    struct object_str* duplicate() const;

  } object_t;

  object_t* newObject( std::uint8_t  v, object_t* parent=nullptr);
  object_t* newObject( std::int8_t   v, object_t* parent=nullptr);
  object_t* newObject( std::int16_t  v, object_t* parent=nullptr);
  object_t* newObject( std::uint16_t v, object_t* parent=nullptr);
  object_t* newObject( std::int32_t  v, object_t* parent=nullptr);
  object_t* newObject( std::uint32_t v, object_t* parent=nullptr);
  object_t* newObject( std::int64_t  v, object_t* parent=nullptr);
  object_t* newObject( std::uint64_t v, object_t* parent=nullptr);
  object_t* newObject( bool          v, object_t* parent=nullptr);
  object_t* newObject( float         v, object_t* parent=nullptr);
  object_t* newObject( double        v, object_t* parent=nullptr);      
  object_t* newObject( char*         v, object_t* parent=nullptr);
  object_t* newObject( const char*   v, object_t* parent=nullptr);
  object_t* newDictObject( object_t* parent=nullptr );
  object_t* newListObject( object_t* parent=nullptr );

  // Return a pointer to the value node.
  object_t* newPairObject( const char* label, object_t*       v, object_t* parent=nullptr);
  object_t* newPairObject( const char* label, std::uint8_t    v, object_t* parent=nullptr);
  object_t* newPairObject( const char* label, std::int8_t     v, object_t* parent=nullptr);
  object_t* newPairObject( const char* label, std::int16_t    v, object_t* parent=nullptr);
  object_t* newPairObject( const char* label, std::uint16_t   v, object_t* parent=nullptr);
  object_t* newPairObject( const char* label, std::int32_t    v, object_t* parent=nullptr);
  object_t* newPairObject( const char* label, std::uint32_t   v, object_t* parent=nullptr);
  object_t* newPairObject( const char* label, std::int64_t    v, object_t* parent=nullptr);
  object_t* newPairObject( const char* label, std::uint64_t   v, object_t* parent=nullptr);
  object_t* newPairObject( const char* label, bool            v, object_t* parent=nullptr);
  object_t* newPairObject( const char* label, float           v, object_t* parent=nullptr);
  object_t* newPairObject( const char* label, double          v, object_t* parent=nullptr);      
  object_t* newPairObject( const char* label, char*           v, object_t* parent=nullptr);
  object_t* newPairObject( const char* label, const char*     v, object_t* parent=nullptr);
  
  rc_t objectFromString( const char* s, object_t*& objRef );
  rc_t objectFromFile( const char* fn, object_t*& objRef );
  void objectPrintTypes( object_t* o );

  rc_t objectToFile( const char* fn, const object_t* obj );

  rc_t object_test( const test::test_args_t& args );


}

#endif
