/* vi: set ft=c : */

#define padname_is_normal_lexical(pname)  MY_padname_is_normal_lexical(aTHX_ pname)
static bool MY_padname_is_normal_lexical(pTHX_ PADNAME *pname)
{
  /* PAD slots without names are certainly not lexicals */
  if(PadnameIsNULL(pname) || !PadnameLEN(pname))
    return FALSE;

  /* Outer lexical captures are not lexicals */
  if(PadnameOUTER(pname))
    return FALSE;

  /* state variables are not lexicals */
  if(PadnameIsSTATE(pname))
    return FALSE;

  /* Protosubs for closures are not lexicals */
  if(PadnamePV(pname)[0] == '&')
    return FALSE;

  /* anything left is a normal lexical */
  return TRUE;
}

enum {
  CV_COPY_NULL_LEXICALS = (1<<0), /* regular lexicals end up NULL */
};

#define cv_copy_flags(orig, flags)  MY_cv_copy_flags(aTHX_ orig, flags)
static CV *MY_cv_copy_flags(pTHX_ CV *orig, U32 flags)
{
  /* Parts of this code stolen from S_cv_clone() in pad.c
   */
  CV *new = MUTABLE_CV(newSV_type(SVt_PVCV));
  CvFLAGS(new) = CvFLAGS(orig) & ~CVf_CVGV_RC;

  CvFILE(new) = CvDYNFILE(orig) ? savepv(CvFILE(orig)) : CvFILE(orig);
#if HAVE_PERL_VERSION(5, 18, 0)
  if(CvNAMED(orig)) {
    /* Perl core uses CvNAME_HEK_set() here, but that involves a call to a
     * non-public function unshare_hek(). The latter is only needed in the
     * case where an old value needs to be removed, but since we've only just
     * created the CV we know it will be empty, so we can just set the field
     * directly
     */
    ((XPVCV*)MUTABLE_PTR(SvANY(new)))->xcv_gv_u.xcv_hek = share_hek_hek(CvNAME_HEK(orig));
    CvNAMED_on(new);
  }
  else
#endif
    CvGV_set(new, CvGV(orig));

  CvSTASH_set(new, CvSTASH(orig));
  {
    OP_REFCNT_LOCK;
    CvROOT(new) = OpREFCNT_inc(CvROOT(orig));
    OP_REFCNT_UNLOCK;
  }
  CvSTART(new) = CvSTART(orig);
  CvOUTSIDE(new) = MUTABLE_CV(SvREFCNT_inc(CvOUTSIDE(orig)));
  CvOUTSIDE_SEQ(new) = CvOUTSIDE_SEQ(orig);

  /* No need to bother with SvPV slot because that's the prototype, and it's
   * too late for that here
   */
  /* TODO: Consider what to do about SvPVX */

  {
    ENTER_with_name("cv_copy_flags");

    SAVESPTR(PL_compcv);
    PL_compcv = new;

    SAVESPTR(PL_comppad_name);
    PL_comppad_name = PadlistNAMES(CvPADLIST(orig));
    CvPADLIST_set(new, pad_new(padnew_CLONE|padnew_SAVE));
#if HAVE_PERL_VERSION(5, 22, 0)
    CvPADLIST(new)->xpadl_id = CvPADLIST(orig)->xpadl_id;
#endif

    PADNAMELIST *padnames = PadlistNAMES(CvPADLIST(orig));
    const PADOFFSET fnames = PadnamelistMAX(padnames);
    const PADOFFSET fpad = AvFILLp(PadlistARRAY(CvPADLIST(orig))[1]);
    int depth = CvDEPTH(orig);
    if(!depth)
      depth = 1;
    SV **origpad = AvARRAY(PadlistARRAY(CvPADLIST(orig))[depth]);

#if !HAVE_PERL_VERSION(5, 18, 0)
    /* Perls before 5.18.0 didn't copy the padnameslist
     */
    SvREFCNT_dec(PadlistNAMES(CvPADLIST(new)));
    PadlistNAMES(CvPADLIST(new)) = (PADNAMELIST *)SvREFCNT_inc(PadlistNAMES(CvPADLIST(orig)));
#endif

    av_fill(PL_comppad, fpad);
    PL_curpad = AvARRAY(PL_comppad);

    PADNAME **pnames = PadnamelistARRAY(padnames);
    PADOFFSET padix;

    /* TODO: What about padix 0? */

    for(padix = 1; padix <= fpad; padix++) {
      PADNAME *pname = (padix <= fnames) ? pnames[padix] : NULL;
      SV *newval = NULL;

      if(padname_is_normal_lexical(pname)) {
        if(flags & CV_COPY_NULL_LEXICALS)
          continue;

        switch(PadnamePV(pname)[0]) {
          case '$': newval = newSV(0); break;
          case '@': newval = MUTABLE_SV(newAV()); break;
          case '%': newval = MUTABLE_SV(newHV()); break;
          default:
            croak("ARGH unsure how to handle pname=<%s> in cv_copy_flags\n",
              PadnamePV(pname));
            break;
        }
      }
      else if(!origpad[padix])
        newval = NULL;
      else if(SvPADTMP(origpad[padix])) {
        /* We still have to copy the value, in case it is live. Also core perl
        * is known to set SvPADTMP on non-temporaries, like folded constants
        *   https://rt.cpan.org/Ticket/Display.html?id=142468
        */
        newval = newSVsv(origpad[padix]);
        SvPADTMP_on(newval);
      }
      else {
#if !HAVE_PERL_VERSION(5, 18, 0)
        /* Before perl 5.18.0, inner anon subs didn't find the right CvOUTSIDE
         * at runtime, so we'll have to patch them up here
         */
        CV *origproto;
        if(pname && PadnamePV(pname)[0] == '&' && 
           CvOUTSIDE(origproto = MUTABLE_CV(origpad[padix])) == orig) {
          /* quiet any "Variable $FOO is not available" warnings about lexicals
           * yet to be introduced
           */
          ENTER_with_name("find_cv_outside");
          SAVEINT(CvDEPTH(origproto));
          CvDEPTH(origproto) = 1;

          CV *newproto = cv_copy_flags(origproto, flags);
          CvPADLIST_set(newproto, CvPADLIST(origproto));
          CvSTART(newproto) = CvSTART(origproto);

          SvREFCNT_dec(CvOUTSIDE(newproto));
          CvOUTSIDE(newproto) = MUTABLE_CV(SvREFCNT_inc_simple_NN(new));

          LEAVE_with_name("find_cv_outside");

          newval = MUTABLE_SV(newproto);
        }
        else
#endif
        if(origpad[padix])
          newval = SvREFCNT_inc_NN(origpad[padix]);
      }

      PL_curpad[padix] = newval;
    }

    LEAVE_with_name("cv_copy_flags");
  }

  return new;
}