/*
    SuperCollider real time audio synthesis system
    Copyright (c) 2002 James McCartney. All rights reserved.
    http://www.audiosynth.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*/
/*

PyrSlot is a value holder for SC variables.
A PyrSlot is an 8-byte value which is either a double precision float or a
32-bit tag plus a 32-bit value.

*/

#pragma once

#include "SC_Endian.h"
#include "SC_Types.h"
#include "PyrErrors.h"

#include <cassert>
#include <cstddef>
#include <cstdint>

struct PyrSymbol;

/*
    Pyrite slots are the size of an 8 byte double. If the upper bits
    indicate that the double is a 'Not-A-Number' then the upper 32
    bits are used as a tag to indicate one of a number of other types
    whose data is in the lower 32 bits.
*/

/* some DSPs like the TIC32 do not support 8 byte doubles */
/* on such CPUs, set DOUBLESLOTS to zero */

#define DOUBLESLOTS 1

/* use the high order bits of an IEEE double NaN as a tag */
enum {
    tagObj = 0x7FF90001,
    tagInt = 0x7FF90002,
    tagSym = 0x7FF90003,
    tagChar = 0x7FF90004,
    tagNil = 0x7FF90005, // nil, false, and true are indicated by the tag alone.
    tagFalse = 0x7FF90006, // the lower 32 bits are zero.
    tagTrue = 0x7FF90007,
    tagPtr = 0x7FF90008,
    /* anything else is a double */
    tagUnused = 0x7FF9000D


#if !DOUBLESLOTS
    ,
    tagFloat = 0x7FF9000F /* used only to initialized 4 byte float tags, never compared with */
#endif
};

typedef union pyrslot {
    double f;
    struct {
#if BYTE_ORDER == BIG_ENDIAN
        int tag;
#endif // BIG_ENDIAN
        union {
            int c; /* char */
            int i;
            float f;
            void* ptr;
            struct PyrObject* o;
            PyrSymbol* s;
            struct PyrMethod* om;
            struct PyrBlock* oblk;
            struct PyrClass* oc;
            struct PyrFrame* of;
            struct PyrList* ol;
            struct PyrString* os;
            struct PyrInt8Array* ob;
            struct PyrDoubleArray* od;
            struct PyrSymbolArray* osym;
            struct PyrProcess* op;
            struct PyrThread* ot;
            struct PyrInterpreter* oi;
        } u;
#if BYTE_ORDER == LITTLE_ENDIAN
        // need to swap on intel <sk>
        int tag;
#endif // LITTLE_ENDIAN
    } s;
} PyrSlot;

/*
    these are some defines to make accessing the structure less verbose.
    obviously it polutes the namespace of identifiers beginning with 'u'.
*/
#define utag s.tag
// int
#define ui s.u.i
// PyrObject
#define uo s.u.o
// PyrSymbol
#define us s.u.s
#define uc s.u.c
#define uoc s.u.oc
#define uof s.u.of
#define uol s.u.ol
#define uod s.u.od
#define uob s.u.ob
#define uop s.u.op
#define uoi s.u.oi
#define uod s.u.od
// string
#define uos s.u.os
#define uot s.u.ot
// method
#define uom s.u.om
// symbol array
#define uosym s.u.osym
#define uoblk s.u.oblk
#define uptr s.u.ptr

#if DOUBLESLOTS
#    define uf f
#else
#    define uf s.u.f
#endif

/*
    Note that on the PowerPC, the fastest way to copy a slot is to
    copy the double field, not the struct.
*/

inline int GetTag(const PyrSlot* slot) { return slot->utag; }

/* some macros for setting values of slots */
inline void SetInt(PyrSlot* slot, int val) {
    (slot)->utag = tagInt;
    (slot)->ui = (val);
}
inline void SetObject(PyrSlot* slot, struct PyrObjectHdr* val) {
    (slot)->utag = tagObj;
    (slot)->uo = (PyrObject*)(val);
}
inline void SetSymbol(PyrSlot* slot, PyrSymbol* val) {
    (slot)->utag = tagSym;
    (slot)->us = (val);
}
inline void SetChar(PyrSlot* slot, char val) {
    (slot)->utag = tagChar;
    (slot)->uc = (val);
}
inline void SetPtr(PyrSlot* slot, void* val) {
    (slot)->utag = tagPtr;
    (slot)->uptr = (void*)(val);
}
inline void SetObjectOrNil(PyrSlot* slot, PyrObject* val) {
    if (val) {
        (slot)->utag = tagObj;
        (slot)->uo = (val);
    } else {
        (slot)->utag = tagNil;
        (slot)->ui = 0;
    }
}

inline void SetTrue(PyrSlot* slot) {
    (slot)->utag = tagTrue;
    (slot)->ui = 0;
}
inline void SetFalse(PyrSlot* slot) {
    (slot)->utag = tagFalse;
    (slot)->ui = 0;
}
inline void SetBool(PyrSlot* slot, bool test) {
    (slot)->utag = ((test) ? tagTrue : tagFalse);
    (slot)->ui = 0;
}
inline void SetNil(PyrSlot* slot) {
    (slot)->utag = tagNil;
    (slot)->ui = 0;
}

#if DOUBLESLOTS
inline void SetFloat(PyrSlot* slot, double val) { (slot)->uf = (val); }
#else
inline void SetFloat(PyrSlot* slot, double val) {
    (slot)->utag = s_float;
    (slot)->uf = (val);
}
#endif

inline bool IsObj(const PyrSlot* slot) { return ((slot)->utag == tagObj); }
inline bool NotObj(const PyrSlot* slot) { return ((slot)->utag != tagObj); }

inline bool IsNil(const PyrSlot* slot) { return ((slot)->utag == tagNil); }
inline bool NotNil(const PyrSlot* slot) { return ((slot)->utag != tagNil); }

inline bool IsFalse(const PyrSlot* slot) { return ((slot)->utag == tagFalse); }
inline bool IsTrue(const PyrSlot* slot) { return ((slot)->utag == tagTrue); }

inline bool SlotEq(const PyrSlot* a, const PyrSlot* b) { return ((a)->ui == (b)->ui && (a)->utag == (b)->utag); }

inline bool IsSym(const PyrSlot* slot) { return ((slot)->utag == tagSym); }
inline bool NotSym(const PyrSlot* slot) { return ((slot)->utag != tagSym); }

inline bool IsChar(const PyrSlot* slot) { return ((slot)->utag == tagChar); }
inline bool NotChar(const PyrSlot* slot) { return ((slot)->utag != tagChar); }

inline bool IsInt(const PyrSlot* slot) { return ((slot)->utag == tagInt); }
inline bool NotInt(const PyrSlot* slot) { return ((slot)->utag != tagInt); }

inline bool IsFloatTag(int tag) { return ((tag & 0xFFFFFFF0) != 0x7FF90000); }
inline bool IsFloat(const PyrSlot* slot) { return (((slot)->utag & 0xFFFFFFF0) != 0x7FF90000); }
inline bool NotFloat(const PyrSlot* slot) { return (((slot)->utag & 0xFFFFFFF0) == 0x7FF90000); }

inline bool IsPtr(const PyrSlot* slot) { return ((slot)->utag == tagPtr); }
inline bool NotPtr(const PyrSlot* slot) { return ((slot)->utag != tagPtr); }

inline void SetRawChar(PyrSlot* slot, int val) {
    assert(IsChar(slot));
    slot->uc = val;
}
inline void SetRaw(PyrSlot* slot, int val) {
    assert(IsInt(slot));
    slot->ui = val;
}
inline void SetRaw(PyrSlot* slot, std::int64_t val) {
    assert(IsInt(slot));
    slot->ui = val;
}
inline void SetRaw(PyrSlot* slot, PyrObject* val) {
    assert(IsObj(slot));
    slot->uo = val;
}
inline void SetRaw(PyrSlot* slot, PyrSymbol* val) {
    assert(IsSym(slot));
    slot->us = val;
}
inline void SetRaw(PyrSlot* slot, void* val) {
    assert(IsPtr(slot));
    slot->uptr = val;
}
inline void SetRaw(PyrSlot* slot, double val) {
    assert(IsFloat(slot));
    SetFloat(slot, val);
}

inline void SetTagRaw(PyrSlot* slot, int tag) { slot->utag = tag; }

template <typename numeric_type> inline int slotVal(PyrSlot* slot, numeric_type* value) {
    if (IsFloat(slot)) {
        *value = static_cast<numeric_type>(slot->uf);
        return errNone;
    } else if (IsInt(slot)) {
        *value = static_cast<numeric_type>(slot->ui);
        return errNone;
    }
    return errWrongType;
}


inline int slotFloatVal(PyrSlot* slot, float* value) { return slotVal<float>(slot, value); }

inline int slotIntVal(PyrSlot* slot, int* value) { return slotVal<int>(slot, value); }

inline int slotDoubleVal(PyrSlot* slot, double* value) { return slotVal<double>(slot, value); }

inline int slotSymbolVal(PyrSlot* slot, PyrSymbol** symbol) {
    if (!IsSym(slot))
        return errWrongType;
    *symbol = slot->us;
    return errNone;
}

inline void* slotRawPtr(PyrSlot* slot) {
    assert(IsPtr(slot) || (slot->s.u.ptr == NULL && IsNil(slot)));
    return slot->s.u.ptr;
}

inline PyrBlock* slotRawBlock(PyrSlot* slot) { return slot->s.u.oblk; }

inline PyrSymbolArray* slotRawSymbolArray(PyrSlot* slot) { return slot->s.u.osym; }

inline PyrDoubleArray* slotRawDoubleArray(PyrSlot* slot) { return slot->s.u.od; }

inline PyrInt8Array* slotRawInt8Array(PyrSlot* slot) { return slot->s.u.ob; }

inline PyrMethod* slotRawMethod(PyrSlot* slot) { return slot->s.u.om; }

inline const PyrMethod* slotRawMethod(const PyrSlot* slot) { return slot->s.u.om; }

inline PyrThread* slotRawThread(PyrSlot* slot) { return slot->s.u.ot; }

inline PyrString* slotRawString(PyrSlot* slot) { return slot->s.u.os; }

inline PyrList* slotRawList(PyrSlot* slot) { return slot->s.u.ol; }

inline PyrFrame* slotRawFrame(PyrSlot* slot) { return slot->s.u.of; }

inline PyrClass* slotRawClass(PyrSlot* slot) { return slot->s.u.oc; }

inline const PyrClass* slotRawClass(const PyrSlot* slot) { return slot->s.u.oc; }

inline PyrInterpreter* slotRawInterpreter(PyrSlot* slot) { return slot->s.u.oi; }

inline PyrSymbol* slotRawSymbol(PyrSlot* slot) {
    assert(IsSym(slot));
    return slot->s.u.s;
}

inline const PyrSymbol* slotRawSymbol(const PyrSlot* slot) { return slot->s.u.s; }

inline int slotRawChar(const PyrSlot* slot) {
    assert(IsChar(slot));
    return slot->s.u.c;
}

inline int slotRawInt(const PyrSlot* slot) {
    assert(IsInt(slot));
    return slot->s.u.i;
}

inline double slotRawFloat(const PyrSlot* slot) {
    assert(IsFloat(slot));
    return slot->uf;
}

inline PyrObject* slotRawObject(PyrSlot* slot) {
    assert(IsObj(slot));
    return slot->s.u.o;
}

inline const PyrObject* slotRawObject(const PyrSlot* slot) {
    assert(IsObj(slot));
    return slot->s.u.o;
}


inline void slotCopy(PyrSlot* dst, const PyrSlot* src) {
    double* dstp = (double*)dst;
    double* srcp = (double*)src;
    *dstp = *srcp;
}

inline void slotCopy(PyrSlot* dst, const PyrSlot* src, int num) {
    double* dstp = (double*)dst - 1;
    double* srcp = (double*)src - 1;
    for (int i = 0; i < num; ++i) {
        *++dstp = *++srcp;
    }
}

#undef uptr
#undef uoblk
#undef uosym
#undef uom
#undef uot
#undef uos
#undef uod
#undef uob
#undef uol
#undef uof
#undef uoc
#undef utag
#undef us
#undef uc
#undef uo
#undef uoi
#undef ui
#undef uf

#undef uop
