/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define C_LUCY_OBJ

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"

#include "Lucy/Object/Obj.h"
#include "Lucy/Object/CharBuf.h"
#include "Lucy/Object/Err.h"
#include "Lucy/Object/VTable.h"
#include "Lucy/Util/Memory.h"

static void
S_lazy_init_host_obj(lucy_Obj *self) {
    SV *inner_obj = newSV(0);
    SvOBJECT_on(inner_obj);
#if (PERL_VERSION <= 16)
    PL_sv_objcount++;
#endif
    SvUPGRADE(inner_obj, SVt_PVMG);
    sv_setiv(inner_obj, PTR2IV(self));

    // Connect class association.
    lucy_CharBuf *class_name = Lucy_VTable_Get_Name(self->vtable);
    HV *stash = gv_stashpvn((char*)Lucy_CB_Get_Ptr8(class_name),
                            Lucy_CB_Get_Size(class_name), TRUE);
    SvSTASH_set(inner_obj, (HV*)SvREFCNT_inc(stash));

    /* Up till now we've been keeping track of the refcount in
     * self->ref.count.  We're replacing ref.count with ref.host_obj, which
     * will assume responsibility for maintaining the refcount.  ref.host_obj
     * starts off with a refcount of 1, so we need to transfer any refcounts
     * in excess of that. */
    size_t old_refcount = self->ref.count;
    self->ref.host_obj = inner_obj;
    while (old_refcount > 1) {
        SvREFCNT_inc_simple_void_NN(inner_obj);
        old_refcount--;
    }
}

uint32_t
lucy_Obj_get_refcount(lucy_Obj *self) {
    return self->ref.count < 4
           ? self->ref.count
           : SvREFCNT((SV*)self->ref.host_obj);
}

lucy_Obj*
lucy_Obj_inc_refcount(lucy_Obj *self) {
    switch (self->ref.count) {
        case 0:
            CFISH_THROW(LUCY_ERR, "Illegal refcount of 0");
            break; // useless
        case 1:
        case 2:
            self->ref.count++;
            break;
        case 3:
            S_lazy_init_host_obj(self);
            // fall through
        default:
            SvREFCNT_inc_simple_void_NN((SV*)self->ref.host_obj);
    }
    return self;
}

uint32_t
lucy_Obj_dec_refcount(lucy_Obj *self) {
    uint32_t modified_refcount = I32_MAX;
    switch (self->ref.count) {
        case 0:
            CFISH_THROW(LUCY_ERR, "Illegal refcount of 0");
            break; // useless
        case 1:
            modified_refcount = 0;
            Lucy_Obj_Destroy(self);
            break;
        case 2:
        case 3:
            modified_refcount = --self->ref.count;
            break;
        default:
            modified_refcount = SvREFCNT((SV*)self->ref.host_obj) - 1;
            // If the SV's refcount falls to 0, DESTROY will be invoked from
            // Perl-space.
            SvREFCNT_dec((SV*)self->ref.host_obj);
    }
    return modified_refcount;
}

void*
lucy_Obj_to_host(lucy_Obj *self) {
    if (self->ref.count < 4) { S_lazy_init_host_obj(self); }
    return newRV_inc((SV*)self->ref.host_obj);
}


