/*
 * Copyright (c) 2017-2024 OARC, Inc.
 * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
 * All rights reserved.
 *
 * This file is part of PacketQ.
 *
 * PacketQ 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 3 of the License, or
 * (at your option) any later version.
 *
 * PacketQ 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 PacketQ.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef __packetq_variant_h
#define __packetq_variant_h

#include <cstdlib>

#include "MurmurHash3.h"
#include "refcountstring.h"

namespace packetq {

inline std::size_t hash_bytes(const char* bytes, int len)
{
    uint32_t result = 0;
    MurmurHash3_x86_32(bytes, len, 0, &result);
    return result;
}

// must be defined in this order - see the "if" statement
#define COLTYPE_MAX 4
namespace Coltype {
    enum Type {
        _bool  = 0,
        _int   = 1,
        _float = 2,
        _text  = 3,
    };
};

typedef bool     bool_column;
static const int bool_size  = sizeof(bool_column);
static const int bool_align = ((sizeof(bool_column) / sizeof(void*)) + 1) * sizeof(void*);

typedef int      int_column;
static const int int_size  = sizeof(int_column);
static const int int_align = ((sizeof(int_column) / sizeof(void*)) + 1) * sizeof(void*);

typedef double   float_column;
static const int float_size  = sizeof(float_column);
static const int float_align = ((sizeof(float_column) / sizeof(void*)) + 1) * sizeof(void*);

typedef RefCountString* text_column;
static const int        text_size  = sizeof(text_column);
static const int        text_align = ((sizeof(text_column) / sizeof(void*)) + 1) * sizeof(void*);

inline bool_column convert_column_to_bool(float_column v) { return v; }
inline bool_column convert_column_to_bool(int_column v) { return v; }
inline bool_column convert_column_to_bool(bool_column v) { return v; }
inline bool_column convert_column_to_bool(text_column v)
{
    return std::atoi(v->data);
}

inline int_column convert_column_to_int(float_column v) { return int(v); }
inline int_column convert_column_to_int(int_column v) { return v; }
inline int_column convert_column_to_int(bool_column v) { return v; }
inline int_column convert_column_to_int(text_column v)
{
    return v->data[0] != '\0';
}

inline float_column convert_column_to_float(float_column v) { return v; }
inline float_column convert_column_to_float(int_column v) { return v; }
inline float_column convert_column_to_float(bool_column v) { return v; }
inline float_column convert_column_to_float(text_column v)
{
    return std::atof(v->data);
}

inline text_column convert_column_to_text(float_column v)
{
    const int       bufsize = 50;
    RefCountString* str     = RefCountString::allocate(bufsize);
    snprintf(str->data, bufsize, "%g", v); // lgtm[cpp/badly-bounded-write]
    return str;
}
inline text_column convert_column_to_text(int_column v)
{
    const int       bufsize = (sizeof(int_column) * 8 + 1) / 3 + 1;
    RefCountString* str     = RefCountString::allocate(bufsize);
    snprintf(str->data, bufsize, "%d", v); // lgtm[cpp/badly-bounded-write]
    return str;
}
inline text_column convert_column_to_text(bool_column v)
{
    const int       bufsize = 1 + 1;
    RefCountString* str     = RefCountString::allocate(bufsize);
    if (v)
        str->data[0] = '1';
    else
        str->data[1] = '0';
    str->data[1] = '\0';
    return str;
}
inline text_column convert_column_to_text(text_column v)
{
    // to stay symmetric with above functions that allocate a new string,
    // increment reference count
    v->inc_refcount();
    return v;
}

// Variant represents a value that can be either one of the column types,
// plus a type field to figure out which kind it represents
class Variant {
public:
    Variant()
    {
        m_type      = Coltype::_int;
        m_val.m_int = 0;
    }

    Variant(bool_column val)
    {
        m_type       = Coltype::_bool;
        m_val.m_bool = val;
    }

    Variant(int_column val)
    {
        m_type      = Coltype::_int;
        m_val.m_int = val;
    }

    Variant(float_column val)
    {
        m_type        = Coltype::_float;
        m_val.m_float = val;
    }

    Variant(text_column val)
    {
        m_type       = Coltype::_text;
        m_val.m_text = val;
        m_val.m_text->inc_refcount();
    }

    Variant(const Variant& other)
    {
        m_type = other.m_type;
        m_val  = other.m_val;
        if (m_type == Coltype::_text)
            m_val.m_text->inc_refcount();
    }

    // move constructor
    Variant(Variant&& other)
    {
        // would be cleaner to use default constructor, but alas
        // constructor delegation requires GCC >= 4.7
        m_type      = Coltype::_int;
        m_val.m_int = 0;

        swap(*this, other);
    }

    ~Variant()
    {
        if (m_type == Coltype::_text)
            m_val.m_text->dec_refcount();
    }

    Variant& operator=(Variant other)
    {
        // copy and swap idiom
        swap(*this, other);
        return *this;
    }

    inline friend void swap(Variant& first, Variant& second)
    {
        using std::swap;
        swap(first.m_type, second.m_type);
        swap(first.m_val, second.m_val);
    }

    bool_column get_bool() const
    {
        switch (m_type) {
        case Coltype::_float:
            return convert_column_to_bool(m_val.m_float);
        case Coltype::_int:
            return convert_column_to_bool(m_val.m_int);
        case Coltype::_bool:
            return convert_column_to_bool(m_val.m_bool);
        case Coltype::_text:
            return convert_column_to_bool(m_val.m_text);
        }
        return false;
    }

    int_column get_int() const
    {
        switch (m_type) {
        case Coltype::_float:
            return convert_column_to_int(m_val.m_float);
        case Coltype::_int:
            return convert_column_to_int(m_val.m_int);
        case Coltype::_bool:
            return convert_column_to_int(m_val.m_bool);
        case Coltype::_text:
            return convert_column_to_int(m_val.m_text);
        }
        return 0;
    }

    float_column get_float() const
    {
        switch (m_type) {
        case Coltype::_float:
            return convert_column_to_float(m_val.m_float);
        case Coltype::_int:
            return convert_column_to_float(m_val.m_int);
        case Coltype::_bool:
            return convert_column_to_float(m_val.m_bool);
        case Coltype::_text:
            return convert_column_to_float(m_val.m_text);
        }
        return 0.0;
    }

    // this returns a RefCountString with the ref-count incremented so
    // caller is responsible for decrementing after use
    text_column get_text() const
    {
        switch (m_type) {
        case Coltype::_float:
            return convert_column_to_text(m_val.m_float);
        case Coltype::_int:
            return convert_column_to_text(m_val.m_int);
        case Coltype::_bool:
            return convert_column_to_text(m_val.m_bool);
        case Coltype::_text:
            return convert_column_to_text(m_val.m_text);
        }
        return RefCountString::construct("");
    }

    int cmp(const Variant& rhs) const
    {
        switch (m_type) {
        case (Coltype::_bool):
            return m_val.m_bool - rhs.get_bool();
        case (Coltype::_int):
            return m_val.m_int - rhs.get_int();
        case (Coltype::_float): {
            float_column r = rhs.get_float();
            if (m_val.m_float < r)
                return -1;
            if (m_val.m_float > r)
                return 1;
            return 0;
        }
        case (Coltype::_text): {
            RefCountString* s   = rhs.get_text();
            auto            res = strcmp(m_val.m_text->data, s->data);
            s->dec_refcount();
            return res;
        }
        }
        return 0;
    }

    bool operator<(const Variant& rhs) const
    {
        return cmp(rhs) < 0;
    }
    bool operator==(const Variant& rhs) const
    {
        return cmp(rhs) == 0;
    }

    std::size_t hash() const
    {
        switch (m_type) {
        case (Coltype::_bool):
            return std::hash<bool>()(m_val.m_bool);
        case (Coltype::_int):
            return std::hash<int>()(m_val.m_int);
        case (Coltype::_float):
            return std::hash<float>()(m_val.m_float);
        case (Coltype::_text):
            return hash_bytes(m_val.m_text->data, strlen(m_val.m_text->data));
        }
        return 0;
    }

    Coltype::Type m_type;

private:
    union VariantUnion {
        bool_column  m_bool;
        int_column   m_int;
        float_column m_float;
        text_column  m_text;
    };

    VariantUnion m_val;
};

} // namespace packetq

#endif // __packetq_variant_h
