/*
===========================================================================
Copyright (C) 2015 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA source code 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.

OpenMoHAA source code 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 OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

// str.cpp: Simple, DLL portable string class
//

#pragma once

#include <cassert>
#include <cstring>
#include <cstdio>
#include <cstdint>
#include <cinttypes>

#ifdef _WIN32
#    pragma warning(disable : 4710) // function 'blah' not inlined
#endif

void TestStringClass();

class strdata
{
public:
    strdata()
        : len(0)
        , refcount(0)
        , data(NULL)
        , alloced(0)
    {}

    ~strdata()
    {
        if (data) {
            delete[] data;
        }
    }

    void AddRef() { refcount++; }

    bool DelRef() // True if killed
    {
        refcount--;
        if (refcount < 0) {
            delete this;
            return true;
        }

        return false;
    }

    char  *data;
    int    refcount;
    size_t alloced;
    size_t len;
};

class str
{
protected:
    friend class Archiver;
    strdata *m_data;
    void     EnsureAlloced(size_t, bool keepold = true);
    void     EnsureDataWritable();

public:
    ~str();
    str();
    str(const char *text);
    str(const str& string);
    str(const str& string, size_t start, size_t end);
    str(const char ch);
    str(const int num);
    str(const float num);
    str(const unsigned int num);
    str(const long num);
    str(const unsigned long num);
    str(const long long num);
    str(const unsigned long long num);

    size_t      length(void) const;
    const char *c_str(void) const;

    void append(const char *text);
    void append(const str& text);

    char  operator[](intptr_t index) const;
    char& operator[](intptr_t index);

    void operator=(const str& text);
    void operator=(const char *text);

    friend str operator+(const str& a, const str& b);
    friend str operator+(const str& a, const char *b);
    friend str operator+(const char *a, const str& b);

    friend str operator+(const str& a, const float b);
    friend str operator+(const str& a, const int b);
    friend str operator+(const str& a, const unsigned b);
    friend str operator+(const str& a, const bool b);
    friend str operator+(const str& a, const char b);

    str& operator+=(const str& a);
    str& operator+=(const char *a);
    str& operator+=(const float a);
    str& operator+=(const char a);
    str& operator+=(const int a);
    str& operator+=(const unsigned a);
    str& operator+=(const bool a);

    str& operator-=(int c);
    str& operator--(int);

    friend bool operator==(const str& a, const str& b);
    friend bool operator==(const str& a, const char *b);
    friend bool operator==(const char *a, const str& b);

    friend bool operator!=(const str& a, const str& b);
    friend bool operator!=(const str& a, const char *b);
    friend bool operator!=(const char *a, const str& b);

    operator const char *() const;

    int icmpn(const char *text, size_t n) const;
    int icmpn(const str& text, size_t n) const;
    int icmp(const char *text) const;
    int icmp(const str& text) const;
    int cmpn(const char *text, size_t n) const;
    int cmpn(const str& text, size_t n) const;

    void tolower(void);
    void toupper(void);

    static char *tolower(char *s1);
    static char *toupper(char *s1);

    static int icmpn(const char *s1, const char *s2, size_t n);
    static int icmp(const char *s1, const char *s2);
    static int cmpn(const char *s1, const char *s2, size_t n);
    static int cmp(const char *s1, const char *s2);

    static void snprintf(char *dst, int size, const char *fmt, ...);
    void        strip(void);

    static bool isNumeric(const char *str);
    bool        isNumeric(void) const;

    void CapLength(size_t newlen);

    void        BackSlashesToSlashes();
    void        SlashesToBackSlashes();
    void        DefaultExtension(const char *extension);
    const char *GetExtension() const;
    void        StripExtension();
    void        SkipFile();
    void        SkipPath();
};

char *strstrip(char *string);
char *strlwc(char *string);

inline char str::operator[](intptr_t index) const
{
    assert(m_data);

    if (!m_data) {
        return 0;
    }

    // don't include the '/0' in the test, because technically, it's out of bounds
    assert((index >= 0) && (index < (int)m_data->len));

    // In release mode, give them a null character
    // don't include the '/0' in the test, because technically, it's out of bounds
    if ((index < 0) || (index >= (int)m_data->len)) {
        return 0;
    }

    return m_data->data[index];
}

inline size_t str::length(void) const
{
    return (m_data != NULL) ? m_data->len : 0;
}

inline str::~str()
{
    if (m_data) {
        m_data->DelRef();
        m_data = NULL;
    }
}

inline str::str()
    : m_data(NULL)
{}

inline str::str(const char *text)
    : m_data(NULL)
{
    size_t len;

    assert(text);
    if (*text) {
        len = strlen(text);

        if (len) {
            EnsureAlloced(len + 1);
            strcpy(m_data->data, text);
            m_data->len = len;
        }
    }
}

inline str::str(const str& text)
    : m_data(NULL)
{
    if (text.m_data) {
        text.m_data->AddRef();
    }

    if (m_data) {
        m_data->DelRef();
    }

    m_data = text.m_data;
}

inline str::str(const str& text, size_t start, size_t end)
    : m_data(NULL)
{
    size_t i;
    size_t len;

    if (end > text.length()) {
        end = text.length();
    }

    if (start > text.length()) {
        start = text.length();
    }

    if (end >= start) {
        len = end - start;
    } else {
        len = 0;
    }

    if (len > 0) {
        EnsureAlloced(len + 1);

        for (i = 0; i < len; i++) {
            m_data->data[i] = text[start + i];
        }

        m_data->data[len] = 0;
        m_data->len       = len;
    }
}

inline str::str(const char ch)
    : m_data(NULL)
{
    EnsureAlloced(2);

    m_data->data[0] = ch;
    m_data->data[1] = 0;
    m_data->len     = 1;
}

inline str::str(const float num)
    : m_data(NULL)
{
    char   text[32];
    size_t len;

    snprintf(text, sizeof(text), "%.3f", num);
    len = strlen(text);
    EnsureAlloced(len + 1);
    strcpy(m_data->data, text);
    m_data->len = len;
}

inline str::str(const int num)
    : m_data(NULL)
{
    char   text[32];
    size_t len;

    snprintf(text, sizeof(text), "%d", num);
    len = strlen(text);
    EnsureAlloced(len + 1);
    strcpy(m_data->data, text);
    m_data->len = len;
}

inline str::str(const unsigned int num)
    : m_data(NULL)
{
    char   text[32];
    size_t len;

    snprintf(text, sizeof(text), "%u", num);
    len = strlen(text);
    EnsureAlloced(len + 1);
    strcpy(m_data->data, text);
    m_data->len = len;
}

inline str::str(const long num)
    : m_data(NULL)
{
    char   text[64];
    size_t len;

    snprintf(text, sizeof(text), "%ld", num);
    len = strlen(text);
    EnsureAlloced(len + 1);
    strcpy(m_data->data, text);
    m_data->len = len;
}

inline str::str(const unsigned long num)
    : m_data(NULL)
{
    char   text[64];
    size_t len;

    snprintf(text, sizeof(text), "%lu", num);
    len = strlen(text);
    EnsureAlloced(len + 1);
    strcpy(m_data->data, text);
    m_data->len = len;
}

inline str::str(const long long num)
    : m_data(NULL)
{
    char   text[64];
    size_t len;

    snprintf(text, sizeof(text), "%lld", num);
    len = strlen(text);
    EnsureAlloced(len + 1);
    strcpy(m_data->data, text);
    m_data->len = len;
}

inline str::str(const unsigned long long num)
    : m_data(NULL)
{
    char   text[64];
    size_t len;

    snprintf(text, sizeof(text), "%llu", num);
    len = strlen(text);
    EnsureAlloced(len + 1);
    strcpy(m_data->data, text);
    m_data->len = len;
}

inline const char *str::c_str(void) const
{
    if (m_data) {
        return m_data->data;
    } else {
        return "";
    }
}

inline void str::append(const char *text)
{
    size_t len;

    assert(text);

    if (*text) {
        len = length();
        len += strlen(text);
        EnsureAlloced(len + 1);

        strcat(m_data->data, text);
        m_data->len = len;
    }
}

inline void str::append(const str& text)
{
    append(text.c_str());
}

inline char& str::operator[](intptr_t index)
{
    // Used for result for invalid indices
    static char dummy = 0;
    assert(m_data);

    // We don't know if they'll write to it or not
    // if it's not a const object
    EnsureDataWritable();

    if (!m_data) {
        return dummy;
    }

    // don't include the '/0' in the test, because technically, it's out of bounds
    assert((index >= 0) && (index < (int)m_data->len));

    // In release mode, let them change a safe variable
    // don't include the '/0' in the test, because technically, it's out of bounds
    if ((index < 0) || (index >= (int)m_data->len)) {
        return dummy;
    }

    return m_data->data[index];
}

inline void str::operator=(const str& text)
{
    // adding the reference before deleting our current reference prevents
    // us from deleting our string if we are copying from ourself
    if (text.m_data) {
        text.m_data->AddRef();
    }

    if (m_data) {
        m_data->DelRef();
    }

    m_data = text.m_data;
}

inline void str::operator=(const char *text)
{
    size_t len;

    assert(text);

    if (m_data) {
        if (text == m_data->data) {
            return; // Copying same thing.  Punt.
        }

        m_data->DelRef();
        m_data = NULL;
    }

    if (*text) {
        len = strlen(text);

        m_data          = new strdata;
        m_data->len     = len;
        m_data->alloced = len + 1;
        m_data->data    = new char[len + 1];
        strcpy(m_data->data, text);
    }
}

inline str operator+(const str& a, const str& b)
{
    str result(a);

    result.append(b);

    return result;
}

inline str operator+(const str& a, const char *b)
{
    str result(a);

    result.append(b);

    return result;
}

inline str operator+(const char *a, const str& b)
{
    str result(a);

    result.append(b);

    return result;
}

inline str operator+(const str& a, const bool b)
{
    str result(a);

    result.append(b ? "true" : "false");

    return result;
}

inline str operator+(const str& a, const char b)
{
    char text[2];

    text[0] = b;
    text[1] = 0;

    return a + text;
}

inline str& str::operator+=(const str& a)
{
    append(a);
    return *this;
}

inline str& str::operator+=(const char *a)
{
    append(a);
    return *this;
}

inline str& str::operator+=(const char a)
{
    char text[2];

    text[0] = a;
    text[1] = 0;
    append(text);

    return *this;
}

inline str& str::operator+=(const bool a)
{
    append(a ? "true" : "false");
    return *this;
}

inline bool operator==(const str& a, const str& b)
{
    return (!strcmp(a.c_str(), b.c_str()));
}

inline bool operator==(const str& a, const char *b)
{
    assert(b);
    if (!b) {
        return false;
    }
    return (!strcmp(a.c_str(), b));
}

inline bool operator==(const char *a, const str& b)
{
    assert(a);
    if (!a) {
        return false;
    }
    return (!strcmp(a, b.c_str()));
}

inline bool operator!=(const str& a, const str& b)
{
    return !(a == b);
}

inline bool operator!=(const str& a, const char *b)
{
    return !(a == b);
}

inline bool operator!=(const char *a, const str& b)
{
    return !(a == b);
}

inline int str::icmpn(const char *text, size_t n) const
{
    assert(text);

    return str::icmpn(c_str(), text, n);
}

inline int str::icmpn(const str& text, size_t n) const
{
    return str::icmpn(c_str(), text.c_str(), n);
}

inline int str::icmp(const char *text) const
{
    assert(text);

    return str::icmp(c_str(), text);
}

inline int str::icmp(const str& text) const
{
    return str::icmp(c_str(), text.c_str());
}

inline int str::cmpn(const char *text, size_t n) const
{
    assert(text);

    return str::cmpn(c_str(), text, n);
}

inline int str::cmpn(const str& text, size_t n) const
{
    return str::cmpn(c_str(), text.c_str(), n);
}

inline void str::tolower(void)
{
    if (m_data)
    {
        EnsureDataWritable();

        str::tolower(m_data->data);
    }
}

inline void str::toupper(void)
{
    if (m_data)
    {
        EnsureDataWritable();

        str::toupper(m_data->data);
    }
}

inline bool str::isNumeric(void) const
{
    assert(m_data);
    return str::isNumeric(m_data->data);
}

inline str::operator const char *(void) const
{
    return c_str();
}
