#include <luna/Alloc.h>
#include <luna/CString.h>
#include <luna/Format.h>
#include <luna/String.h>
#include <luna/StringBuilder.h>
#include <luna/Vector.h>

String::String()
{
    empty();
}

void String::empty()
{
    m_inline = true;
    m_length = 0;
    memset(m_inline_storage, 0, sizeof(m_inline_storage));
}

String::String(String&& other)
{
    m_inline = other.m_inline;

    m_string = other.m_string;
    m_length = other.m_length;

    if (m_inline) memcpy(m_inline_storage, other.m_inline_storage, sizeof(m_inline_storage));

    other.empty();
}

String& String::operator=(String&& other)
{
    if (&other == this) return *this;

    if (!m_inline) free_impl(m_string);

    m_inline = other.m_inline;

    m_string = other.m_string;
    m_length = other.m_length;

    if (m_inline) memcpy(m_inline_storage, other.m_inline_storage, sizeof(m_inline_storage));

    other.empty();

    return *this;
}

String::String(char* c_str)
{
    check(c_str);
    m_string = c_str;
    m_inline = false;
    m_length = strlen(m_string);
}

String::String(char* c_str, usize length)
{
    check(c_str);
    m_string = c_str;
    m_inline = false;
    m_length = length;
}

String::~String()
{
    if (!m_inline) free_impl(m_string);
}

Result<String> String::clone() const
{
    return from_cstring(chars());
}

Result<String> String::substring(usize begin, usize size) const
{
    if (begin + size >= size) return err(EINVAL);
    char* const dup = strndup(chars() + begin, size);
    if (!dup) return err(ENOMEM);
    return String { dup, size };
}

void String::trim(StringView delim)
{
    isize i = (isize)m_length;

    while (i--)
    {
        char c = chars()[i];
        if (!strchr(delim.chars(), c)) break;
    }

    i++;

    if (m_inline) m_inline_storage[i] = '\0';
    else
        m_string[i] = '\0';

    m_length = (usize)i;
}

const char& String::operator[](usize index) const
{
    expect(index < m_length, "index out of range");
    return chars()[index];
}

Result<String> String::format(const String& fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);

    auto rc = vformat(fmt.view(), ap);

    va_end(ap);

    return rc;
}

Result<String> String::format(StringView fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);

    auto rc = vformat(fmt, ap);

    va_end(ap);

    return rc;
}

Result<String> String::vformat(StringView fmt, va_list ap)
{
    Vector<char> buf;

    TRY(cstyle_format(
        fmt.chars(), [](char c, void* data) -> Result<void> { return ((Vector<char>*)data)->try_append(c); }, &buf,
        ap));

    TRY(buf.try_append(0));

    return String { buf.release_data() };
}

Result<String> String::from_cstring(const char* str)
{
    return from_string_view(StringView { str });
}

Result<String> String::from_string_view(StringView str)
{
    if (str.length() < sizeof(m_inline_storage))
    {
        String result;
        result.m_inline = true;
        result.m_length = str.length();
        memset(result.m_inline_storage, 0, sizeof(result.m_inline_storage));
        memcpy(result.m_inline_storage, str.chars(), str.length());
        return result;
    }

    char* const dup = strndup(str.chars(), str.length());
    if (!dup) return err(ENOMEM);
    return String { dup, str.length() };
}

Result<String> String::join(const Vector<String>& vec, StringView delim)
{
    if (vec.size() == 0) return String {};
    if (vec.size() == 1) return vec[0].clone();

    StringBuilder sb;
    TRY(sb.add(vec[0].view()));

    for (usize i = 1; i < vec.size(); i++)
    {
        TRY(sb.add(delim));
        TRY(sb.add(vec[i].view()));
    }

    return sb.string();
}

Result<String> String::join(const Vector<StringView>& vec, StringView delim)
{
    if (vec.size() == 0) return String {};
    if (vec.size() == 1) return from_string_view(vec[0]);

    StringBuilder sb;
    TRY(sb.add(vec[0]));

    for (usize i = 1; i < vec.size(); i++)
    {
        TRY(sb.add(delim));
        TRY(sb.add(vec[i]));
    }

    return sb.string();
}

int String::compare(const String* a, const String* b)
{
    return strcmp(a->chars(), b->chars());
}

template <> u64 hash(const String& value, u64 salt)
{
    return hash_memory(value.chars(), value.length(), salt);
}