#pragma once
#include <xs/Scalar.h>

namespace xs {

using xs::my_perl;

struct Ref : Scalar {
    Ref (std::nullptr_t = nullptr) {}
    Ref (SV* sv, bool policy = INCREMENT) : Scalar(sv, policy) { _validate(); }

    Ref (const Ref&    oth) : Scalar(oth)            {}
    Ref (Ref&&         oth) : Scalar(std::move(oth)) {}
    Ref (const Scalar& oth) : Scalar(oth)            { _validate(); }
    Ref (Scalar&&      oth) : Scalar(std::move(oth)) { _validate(); }
    Ref (const Sv&     oth) : Ref(oth.get())         {}
    Ref (Sv&&          oth) : Scalar(std::move(oth)) { _validate(); }

    Ref (const Simple&) = delete;
    Ref (const Glob&)   = delete;
    Ref (const Array&)  = delete;
    Ref (const Hash&)   = delete;
    Ref (const Sub&)    = delete;
    Ref (const Io&)     = delete;

    static Ref create (SV* sv = nullptr, bool policy = INCREMENT) {
        SV* rv;
        if (sv) rv = (policy == INCREMENT) ? newRV(sv) : newRV_noinc(sv);
        else rv = newRV_noinc(newSV(0));
        return Ref(rv, NONE);
    }
    static Ref create (AV* sv, bool policy = INCREMENT) { return create((SV*)sv, policy); }
    static Ref create (HV* sv, bool policy = INCREMENT) { return create((SV*)sv, policy); }
    static Ref create (CV* sv, bool policy = INCREMENT) { return create((SV*)sv, policy); }
    static Ref create (GV* sv, bool policy = INCREMENT) { return create((SV*)sv, policy); }
    static Ref create (IO* sv, bool policy = INCREMENT) { return create((SV*)sv, policy); }

    static Ref create (const Sv& o) { return create(o.get()); }

    Ref& operator= (SV* val) {
        Scalar::operator=(val);
        _validate();
        return *this;
    }

    Ref& operator= (const Ref& oth) {
        Scalar::operator=(oth.sv);
        return *this;
    }

    Ref& operator= (Ref&& oth) {
        Scalar::operator=(std::move(oth));
        return *this;
    }

    Ref& operator= (const Scalar& oth) {
        Scalar::operator=(oth);
        _validate();
        return *this;
    }

    Ref& operator= (Scalar&& oth) {
        Scalar::operator=(std::move(oth));
        _validate();
        return *this;
    }

    Ref& operator= (const Sv& oth) { return operator=(oth.get()); }

    Ref& operator= (Sv&& oth) {
        Scalar::operator=(std::move(oth));
        _validate();
        return *this;
    }

    Ref& operator= (const Simple&) = delete;
    Ref& operator= (const Glob&)   = delete;
    Ref& operator= (const Array&)  = delete;
    Ref& operator= (const Hash&)   = delete;
    Ref& operator= (const Sub&)    = delete;
    Ref& operator= (const Io&)     = delete;

    void set (SV* val) { Scalar::set(val); }

    template <class T = Sv> enable_if_sv_t<T,T> value () const { return T(sv ? SvRV(sv) : nullptr); }

    void value (SV* val, bool policy = INCREMENT) {
        if (!val) val = &PL_sv_undef;
        else if (policy == INCREMENT) SvREFCNT_inc_simple_void_NN(val);
        if (sv) {
            SvREFCNT_dec_NN(SvRV(sv));
            SvRV_set(sv, val);
        }
        else sv = newRV_noinc(val);
    }
    void value (AV* val, bool policy = INCREMENT) { value((SV*)val, policy); }
    void value (HV* val, bool policy = INCREMENT) { value((SV*)val, policy); }
    void value (CV* val, bool policy = INCREMENT) { value((SV*)val, policy); }
    void value (GV* val, bool policy = INCREMENT) { value((SV*)val, policy); }
    void value (IO* val, bool policy = INCREMENT) { value((SV*)val, policy); }
    void value (const Sv& val)                    { value(val.get()); }
    void value (std::nullptr_t)                   { value((SV*)nullptr); }

private:
    inline void _validate () {
        if (!sv) return;
        if (SvROK(sv)) return;
        if (is_undef()) return reset();
        reset();
        throw std::invalid_argument("SV is not a reference");
    }
};

}
