#ifndef __OBJECT_PAD__CLASS_H__
#define __OBJECT_PAD__CLASS_H__

#include "suspended_compcv.h"

#include "linnet.h"

/* Metadata about a class or role */
#define LINNET_VAL_CLASSMETA  0x4F50434D     /* "OPCM" */
#define MUST_CLASSMETA(ptr)   LINNET_CHECK_CAST(ptr, ClassMeta *, LINNET_VAL_CLASSMETA)
struct ClassMeta {
  LINNET_FIELD
  enum MetaType type : 8;
  enum ReprType repr : 8;

  unsigned int abstract : 1;
  unsigned int begun : 1;
  unsigned int sealed : 1;
  unsigned int role_is_invokable : 1;
  unsigned int strict_params : 1;
  unsigned int has_adjust : 1; /* has at least one ADJUST(PARAMS) block */
  unsigned int composed_adjust : 1; /* all ADJUST blocks are true blocks, composed into initfields */
  unsigned int has_superclass : 1;
  unsigned int has_buildargs : 1;
  unsigned int lexical_new : 1;

  FIELDOFFSET start_fieldix; /* first field index of this partial within its instance */
  FIELDOFFSET next_fieldix;  /* 1 + final field index of this partial within its instance; includes fields in roles */

  /* In the following, "MERGED" means the item includes elements merged from a
   * superclass if present, and any applied roles
   * "direct" means only the things added directly to this exact class/role
   */

  SV *name;
  HV *stash;
  CV *constructor;     /* CV that implements &new constructor */
  AV *isa;             /* cached pointer to the @ISA array for the stash */
  AV *pending_submeta; /* NULL, or AV containing raw ClassMeta pointers to subclasses pending seal */
  AV *hooks;           /* NULL, or AV of raw pointers directly to ClassHook structs */
  AV *fields;          /* each elem is a raw pointer directly to a FieldMeta */
  AV *direct_methods;  /* each elem is a raw pointer directly to a MethodMeta */
  HV *parammap;        /* NULL, or each elem is a raw pointer directly at a ParamMeta (MERGED) */
  AV *requiremethods;  /* each elem is an SVt_PV giving a name */
  CV *initfields;      /* the INITFIELDS method body */
  AV *buildcvs;        /* the BUILD {} phaser blocks; each elem is a CV* directly (MERGED) */
  AV *adjustcvs;       /* the ADJUST {} phaser blocks; each elem is a CV* directly (MERGED) */

  AV *fieldhooks_makefield; /* NULL, or AV of struct FieldHook, all of whose ->funcs->post_makefield exist (MERGED) */
  AV *fieldhooks_construct; /* NULL, or AV of struct FieldHook, all of whose ->funcs->post_construct exist (MERGED) */

  COP *tmpcop;         /* a COP to use during generated constructor */
  CV *methodscope;     /* a temporary CV used just during compilation of a `method` */
  U32 methodscope_seq; /* PL_cop_seqmax at the time methodscope was created */

  SuspendedCompCVBuffer initfields_compcv; /* temporary PL_compcv + associated state during initfields */
  OP *initfields_lines;                    /* temporary OP_LINESEQ to contain the initfield ops */
  U32 next_field_for_initfields;           /* how many fields have we seen so far? (offset into direct_fields, !NOT! fieldix) */

  SuspendedCompCVBuffer adjust_compcv; /* temporary PL_compcv + associated state during true-block ADJUSTs */
  CV *adjust_methodscope;              /* temporary CV used during compilation of ADJUST blocks */
  AV *adjust_params;                   /* temporary AV of the params used by true-block ADJUST :params */
  OP *adjust_lines;                    /* temporary OP_LINESEQ to contain true-block ADJUSTs */
  U32 next_field_for_adjust;           /* how many fields have we seen so far? (offset into direct_fields; !NOT! fieldix) */

  union {
    /* Things that only true classes have */
    struct {
      ClassMeta *supermeta; /* superclass */
      CV *foreign_new;      /* superclass is not Object::Pad, here is the constructor */
      CV *foreign_does;     /* superclass is not Object::Pad, here is SUPER::DOES (which could be UNIVERSAL::DOES) */
      AV *direct_roles;     /* each elem is a raw pointer directly to a RoleEmbedding for roles directly applied to this class */
      AV *embedded_roles;   /* each elem is a raw pointer directly to a RoleEmbedding for all roles embedded (MERGED) */
    } cls; /* not 'class' or C++ compilers get upset */

    /* Things that only roles have */
    struct {
      AV *superroles;      /* each elem is a raw pointer directly to a ClassMeta whose type == METATYPE_ROLE */
      HV *applied_classes; /* keyed by class name each elem is a raw pointer directly to a RoleEmbedding */
      AV *applycvs;        /* the APPLY {} phaser blocks; each elem is a CV* directly */
    } role;
  };
};

/* Metadata about the embedding of a role into a class */
#define LINNET_VAL_ROLEEMBEDDING  0x4F505245  /* "OPRE" */
#define MUST_ROLEEMBEDDING(ptr)   LINNET_CHECK_CAST(ptr, RoleEmbedding *, LINNET_VAL_ROLEEMBEDDING)
typedef struct RoleEmbedding {
  LINNET_FIELD
  SV *embeddingsv;
  struct ClassMeta *rolemeta;
  struct ClassMeta *classmeta;
  PADOFFSET offset;
} RoleEmbedding;

#define LINNET_VAL_METHODMETA  0x4F504D4D  /* "OPMM" */
#define MUST_METHODMETA(ptr)   LINNET_CHECK_CAST(ptr, MethodMeta *, LINNET_VAL_METHODMETA)
struct MethodMeta {
  LINNET_FIELD
  SV *name;
  ClassMeta *class;
  ClassMeta *role;   /* set if inherited from a role */
  /* We don't store the method body CV; leave that in the class stash */
  unsigned int is_common : 1;
};

#define LINNET_VAL_PARAMMETA  0x4F50504D  /* "OPPM" */
#define MUST_PARAMMETA(ptr)   LINNET_CHECK_CAST(ptr, ParamMeta *, LINNET_VAL_PARAMMETA)
typedef struct ParamMeta {
  LINNET_FIELD
  SV *name;
  ClassMeta *class;
  enum {
    PARAM_FIELD,
    PARAM_ADJUST,
  } type;
  union {
    struct {
      FieldMeta *fieldmeta;
      FIELDOFFSET fieldix;
    } field;
    struct {
      /* TODO: store the block itself sometime?? */
      PADOFFSET padix;
      OP *defexpr;
      unsigned int def_if_undef : 1;
      unsigned int def_if_false : 1;
    } adjust;
  };
} ParamMeta;

#define MOP_CLASS_RUN_HOOKS_NOARGS(classmeta, func)                                       \
  {                                                                                       \
    U32 hooki;                                                                            \
    for(hooki = 0; classmeta->hooks && hooki < av_count(classmeta->hooks); hooki++) {     \
      struct ClassHook *h = (struct ClassHook *)AvARRAY(classmeta->hooks)[hooki];         \
      if(*h->funcs->func)                                                                 \
        (*h->funcs->func)(aTHX_ classmeta, h->attrdata, h->funcdata);                     \
    }                                                                                     \
  }

#define MOP_CLASS_RUN_HOOKS(classmeta, func, ...)                                         \
  {                                                                                       \
    U32 hooki;                                                                            \
    for(hooki = 0; classmeta->hooks && hooki < av_count(classmeta->hooks); hooki++) {     \
      struct ClassHook *h = (struct ClassHook *)AvARRAY(classmeta->hooks)[hooki];         \
      if(*h->funcs->func)                                                                 \
        (*h->funcs->func)(aTHX_ classmeta, h->attrdata, h->funcdata, __VA_ARGS__);        \
    }                                                                                     \
  }

#define mop_class_get_direct_roles(class, embeddings)  ObjectPad_mop_class_get_direct_roles(aTHX_ class, embeddings)
RoleEmbedding **ObjectPad_mop_class_get_direct_roles(pTHX_ const ClassMeta *meta, U32 *nroles);

#define mop_class_get_all_roles(class, embeddings)  ObjectPad_mop_class_get_all_roles(aTHX_ class, embeddings)
RoleEmbedding **ObjectPad_mop_class_get_all_roles(pTHX_ const ClassMeta *meta, U32 *nroles);

#define prepare_method_parse(meta)  ObjectPad__prepare_method_parse(aTHX_ meta)
void ObjectPad__prepare_method_parse(pTHX_ ClassMeta *meta);

#define add_fields_to_pad(meta, since_field)  ObjectPad__add_fields_to_pad(aTHX_ meta, since_field)
void ObjectPad__add_fields_to_pad(pTHX_ ClassMeta *meta, U32 since_field);

#define start_method_parse(meta, is_common)  ObjectPad__start_method_parse(aTHX_ meta, is_common)
void ObjectPad__start_method_parse(pTHX_ ClassMeta *meta, bool is_common);

#define finish_method_parse(meta, is_common, body)  ObjectPad__finish_method_parse(aTHX_ meta, is_common, body)
OP *ObjectPad__finish_method_parse(pTHX_ ClassMeta *meta, bool is_common, OP *body);

#define prepare_adjust_params(meta)  ObjectPad__prepare_adjust_params(aTHX_ meta)
void ObjectPad__prepare_adjust_params(pTHX_ ClassMeta *meta);

#define parse_adjust_params(meta, params)  ObjectPad__parse_adjust_params(aTHX_ meta, params)
void ObjectPad__parse_adjust_params(pTHX_ ClassMeta *meta, AV *params);

#define finish_adjust_params(meta, params, body)  ObjectPad__finish_adjust_params(aTHX_ meta, params, body)
OP *ObjectPad__finish_adjust_params(pTHX_ ClassMeta *meta, AV *params, OP *body);

#define newop_croak_from_constructor(message)  ObjectPad__newop_croak_from_constructor(aTHX_ message)
OP *ObjectPad__newop_croak_from_constructor(pTHX_ SV *message);

#define check_colliding_param(classmeta, paramname)  ObjectPad__check_colliding_param(aTHX_ classmeta, paramname)
void ObjectPad__check_colliding_param(pTHX_ ClassMeta *classmeta, SV *paramname);

#define get_embedding_from_pad()  ObjectPad__get_embedding_from_pad(aTHX)
RoleEmbedding *ObjectPad__get_embedding_from_pad(pTHX);

void ObjectPad__boot_classes(pTHX);

/* Empty role embedding that is applied to all invokable role methods */
extern struct RoleEmbedding ObjectPad__embedding_standalone;

#ifdef HAVE_UNOP_AUX
/*  this was only added in Perl 5.22.0 */
#  define METHSTART_CONTAINS_FIELD_BINDINGS

/* We'll reserve the top two bits of a UV for storing the `type` value for a
 * fieldpad operation; the remainder stores the fieldix itself */
#  define UVBITS (UVSIZE*8)
#  define FIELDIX_TYPE_SHIFT  (UVBITS-2)
#  define FIELDIX_MASK        ((1LL<<FIELDIX_TYPE_SHIFT)-1)
#endif

#if HAVE_PERL_VERSION(5, 38, 0)
#  define HAVE_SVt_PVOBJ

#  define fieldstore_fields(fs)   \
      ((SvTYPE(fs) == SVt_PVOBJ) ? ObjectFIELDS(fs) : AvARRAY(fs))
#  define fieldstore_maxfield(fs) \
      ((SvTYPE(fs) == SVt_PVOBJ) ? ObjectMAXFIELD(fs) : AvFILLp(fs))
#else
#  define fieldstore_fields(fs)   \
      AvARRAY(fs)
#  define fieldstore_maxfield(fs) \
      AvFILLp(fs)
#endif

#endif