// -*- mode: C++ -*-
// Copyright 2017 The Procyon Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef PN_STRING_
#define PN_STRING_

#include <pn/procyon.h>
#include <string.h>
#include <pn/fwd>
#include <pn/value>
#include <string>

namespace pn {

class rune {
  public:
    using size_type = int;

    constexpr rune() : _data{0} {}
    constexpr explicit rune(uint32_t n)
            : _data{
                      // clang-format off
                      static_cast<char>(
                          (n < 0x80) ? n :
                          (n < 0x800) ? (0300 | (n >> 6)) :
                          (n < 0xd800) ? (0340 | (n >> 12)) :
                          (n < 0xe000) ? 0357 :
                          (n < 0x10000) ? (0340 | (n >> 12)) :
                          (n < 0x110000) ? (0360 | (n >> 18)) :
                          0357
                      ),
                      static_cast<char>(
                          (n < 0x80) ? 0 :
                          (n < 0x800) ? (0200 | (0077 & (n >> 0))) :
                          (n < 0xd800) ? (0200 | (0077 & (n >> 6))) :
                          (n < 0xe000) ? 0277 :
                          (n < 0x10000) ? (0200 | (0077 & (n >> 6))) :
                          (n < 0x110000) ? (0200 | (0077 & (n >> 12))) :
                          0277
                      ),
                      static_cast<char>(
                          (n < 0x800) ? 0 :
                          (n < 0xd800) ? (0200 | (0077 & (n >> 0))) :
                          (n < 0xe000) ? 0275 :
                          (n < 0x10000) ? (0200 | (0077 & (n >> 0))) :
                          (n < 0x110000) ? (0200 | (0077 & (n >> 6))) :
                          0275
                      ),
                      static_cast<char>(
                          (n < 0x10000) ? 0 :
                          (n < 0x110000) ? (0200 | (0077 & (n >> 0))) :
                          0
                      ),
                      // clang-format on
              } {}

    explicit rune(const char* data, size_type size) : rune(pn_rune(data, size, 0)) {}

    int compare(rune other) const { return memcmp(data(), other.data(), 4); }

    uint32_t              value() const { return pn_rune(data(), 4, 0); }
    constexpr const char* data() const { return _data; }
    constexpr size_type   size() const {
        return (char[]){1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 2, 2, 3, 4}[uint8_t(*data()) >> 4];
    }

    string copy() const;

    static size_type   count(string_view s);
    static string_view slice(string_view s, size_type offset);
    static string_view slice(string_view s, size_type offset, size_type size);

    int width() const { return pn_rune_width(value()); }

    bool isalnum() const { return pn_isalnum(value()); }
    bool isalpha() const { return pn_isalpha(value()); }
    bool iscntrl() const { return pn_iscntrl(value()); }
    bool isdigit() const { return pn_isdigit(value()); }
    bool islower() const { return pn_islower(value()); }
    bool isnumeric() const { return pn_isnumeric(value()); }
    bool isprint() const { return pn_isprint(value()); }
    bool ispunct() const { return pn_ispunct(value()); }
    bool isspace() const { return pn_isspace(value()); }
    bool isupper() const { return pn_isupper(value()); }
    bool istitle() const { return pn_istitle(value()); }

  private:
    char _data[4];
};

class rune_iterator {
  public:
    using iterator_category = std::bidirectional_iterator_tag;
    using size_type         = int;
    using difference_type   = int;
    using value_type        = rune;
    using pointer           = value_type*;
    using reference         = value_type&;

    rune_iterator() : _data{nullptr}, _size{0}, _offset{0} {}
    rune_iterator(const char* data, size_type size, size_type at)
            : _data{data}, _size{size}, _offset{at} {}

    rune operator*() const { return rune(&_data[_offset], _size - _offset); }

    rune_iterator& operator++() { return _offset = pn_rune_next(_data, _size, _offset), *this; }
    rune_iterator  operator++(int) {
        size_type old_offset = _offset;
        _offset              = pn_rune_next(_data, _size, _offset);
        return rune_iterator{_data, _size, old_offset};
    }
    rune_iterator& operator--() { return _offset = pn_rune_prev(_data, _size, _offset), *this; }
    rune_iterator  operator--(int) {
        size_type old_offset = _offset;
        _offset              = pn_rune_prev(_data, _size, _offset);
        return rune_iterator{_data, _size, old_offset};
    }

    bool operator==(rune_iterator other) const { return _offset == other._offset; }
    bool operator!=(rune_iterator other) const { return _offset != other._offset; }
    bool operator<(rune_iterator other) const { return _offset < other._offset; }
    bool operator<=(rune_iterator other) const { return _offset <= other._offset; }
    bool operator>(rune_iterator other) const { return _offset > other._offset; }
    bool operator>=(rune_iterator other) const { return _offset >= other._offset; }

    size_type offset() const { return _offset; }

  private:
    const char* _data;
    size_type   _size;
    size_type   _offset;
};

class reverse_rune_iterator {
  public:
    using iterator_category = std::bidirectional_iterator_tag;
    using size_type         = int;
    using difference_type   = int;
    using value_type        = rune;
    using pointer           = value_type*;
    using reference         = value_type&;

    reverse_rune_iterator() : _data{nullptr}, _size{0}, _offset{0} {}
    reverse_rune_iterator(const char* data, size_type size, size_type at)
            : _data{data}, _size{size}, _offset{at} {}

    rune operator*() const {
        size_type prev_offset = pn_rune_prev(_data, _size, _offset);
        return rune(&_data[prev_offset], _size - prev_offset);
    }

    reverse_rune_iterator& operator++() {
        return _offset = pn_rune_prev(_data, _size, _offset), *this;
    }
    reverse_rune_iterator operator++(int) {
        size_type old_offset = _offset;
        _offset              = pn_rune_prev(_data, _size, _offset);
        return reverse_rune_iterator{_data, _size, old_offset};
    }
    reverse_rune_iterator& operator--() {
        return _offset = pn_rune_next(_data, _size, _offset), *this;
    }
    reverse_rune_iterator operator--(int) {
        size_type old_offset = _offset;
        _offset              = pn_rune_next(_data, _size, _offset);
        return reverse_rune_iterator{_data, _size, old_offset};
    }

    bool operator==(reverse_rune_iterator other) const { return other._offset == _offset; }
    bool operator!=(reverse_rune_iterator other) const { return other._offset != _offset; }
    bool operator<(reverse_rune_iterator other) const { return other._offset < _offset; }
    bool operator<=(reverse_rune_iterator other) const { return other._offset <= _offset; }
    bool operator>(reverse_rune_iterator other) const { return other._offset > _offset; }
    bool operator>=(reverse_rune_iterator other) const { return other._offset >= _offset; }

    size_type offset() const { return _offset; }

  private:
    const char* _data;
    size_type   _size;
    size_type   _offset;
};

class string {
  public:
    using c_obj_type       = pn_string_t*;
    using c_obj_const_type = pn_string_t const*;
    using size_type        = int;
    using value_type       = rune;
    using reference        = value_type&;
    using const_reference  = const value_type&;
    using iterator         = rune_iterator;
    using reverse_iterator = reverse_rune_iterator;

    static constexpr size_type npos = -1;

    string() : string(nullptr, 0) {}
    string(const std::string& s) : string(s.data(), s.size()) {}
    string(const char* s) : string(s, strlen(s)) {}
    string(const char* data, size_type size);
    constexpr explicit string(c_obj_type s) noexcept : _c_obj{s} {}

    string(string&& s) noexcept : _c_obj{} { swap(s); }
    string& operator      =(string&& s) noexcept { return swap(s), *this; }
    string(const string&) = delete;
    string& operator=(const string& s) = delete;

    ~string();

    bool        empty() const { return size() == 0; }
    size_type   size() const { return (*c_obj())->count - 1; }
    char*       data() { return (*c_obj())->values; }
    const char* data() const { return (*c_obj())->values; }
    data_view   as_data() const;
    string      copy() const { return string(pn_strdup(*c_obj())); }
    int         compare(string_view other) const;

    string& operator+=(string_view s);
    void    append(const char* data, size_type size) { pn_strncat(c_obj(), data, size); }
    void    replace(size_type at, size_type size, string_view replacement);

    void clear() { (*c_obj())->count = 1, (*c_obj())->values[0] = '\0'; }

    string_view substr(size_type offset) const;
    string_view substr(size_type offset, size_type size) const;

    size_type        find(string_view needle, size_type offset = 0) const;
    size_type        rfind(string_view needle, size_type offset = 0) const;
    iterator         begin() const { return iterator{data(), size(), 0}; }
    iterator         end() const { return iterator{data(), size(), size()}; }
    reverse_iterator rbegin() const { return reverse_iterator{data(), size(), size()}; }
    reverse_iterator rend() const { return reverse_iterator{data(), size(), 0}; }

    void swap(string& other) noexcept { std::swap(*c_obj(), *other.c_obj()); }
    void swap(string&& other) noexcept { std::swap(*c_obj(), *other.c_obj()); }

    ::pn::input  input() const;
    ::pn::output output();

    const char*             c_str() const { return data(); }
    std::string             cpp_str() const { return std::string(data(), size()); }
    c_obj_type*             c_obj() { return &_c_obj; }
    c_obj_const_type const* c_obj() const { return &_c_obj; }

  private:
    c_obj_type _c_obj;
};

class string_ref {
  public:
    using c_obj_type       = pn_string_t*;
    using c_obj_const_type = pn_string_t const*;
    using size_type        = int;
    using value_type       = rune;
    using reference        = value_type&;
    using const_reference  = const value_type&;
    using iterator         = rune_iterator;
    using reverse_iterator = reverse_rune_iterator;

    static constexpr size_type npos = -1;

    explicit string_ref(c_obj_type* x) : _c_obj{x} {}
    string_ref(string& x) : _c_obj{x.c_obj()} {}

    string_ref(const string_ref&) = default;
    string_ref(string_ref&&)      = default;
    string_ref& operator=(const string_ref&) = delete;
    string_ref& operator=(string_ref&&) = delete;

    const string_ref& operator=(string x) const { return std::swap(*c_obj(), *x.c_obj()), *this; }

    bool      empty() const { return size() == 0; }
    size_type size() const { return (*c_obj())->count - 1; }
    char*     data() const { return (*c_obj())->values; }
    data_view as_data() const;
    string    copy() const { return string(pn_strdup(*c_obj())); }
    int       compare(string_view other) const;

    const string_ref& operator+=(string_view s) const;
    void append(const char* data, size_type size) const { pn_strncat(c_obj(), data, size); }
    void replace(size_type at, size_type size, string_view replacement) const;

    void clear() const { (*c_obj())->count = 1, (*c_obj())->values[0] = '\0'; }

    string_view substr(size_type offset) const;
    string_view substr(size_type offset, size_type size) const;

    size_type        find(string_view needle, size_type offset = 0) const;
    size_type        rfind(string_view needle, size_type offset = 0) const;
    iterator         begin() const { return iterator{data(), size(), 0}; }
    iterator         end() const { return iterator{data(), size(), size()}; }
    reverse_iterator rbegin() const { return reverse_iterator{data(), size(), size()}; }
    reverse_iterator rend() const { return reverse_iterator{data(), size(), 0}; }

    void swap(string& other) const noexcept { std::swap(*c_obj(), *other.c_obj()); }
    void swap(string&& other) const noexcept { std::swap(*c_obj(), *other.c_obj()); }

    ::pn::input  input() const;
    ::pn::output output() const;

    const char* c_str() const { return data(); }
    std::string cpp_str() const { return std::string(data(), size()); }
    c_obj_type* c_obj() const { return _c_obj; }

  private:
    c_obj_type* _c_obj;
};

class string_view {
  public:
    using size_type        = int;
    using value_type       = rune;
    using iterator         = rune_iterator;
    using reverse_iterator = reverse_rune_iterator;

    static constexpr size_type npos = -1;

    constexpr string_view() : string_view(nullptr, 0) {}
    string_view(const char* s) : string_view(s, s ? strlen(s) : 0) {}
    constexpr string_view(const char* data, size_type size) : _data(data), _size(size) {}
    string_view(const std::string& s) : string_view(s.data(), s.size()) {}
    string_view(const string& s) : string_view(s.data(), s.size()) {}
    string_view(string_ref s) : string_view(s.data(), s.size()) {}
    constexpr string_view(const rune& r) : string_view(r.data(), r.size()) {}

    bool        empty() const { return size() == 0; }
    size_type   size() const { return _size; }
    const char* data() const { return _data; }
    data_view   as_data() const;

    size_type        find(string_view needle, size_type offset = 0) const;
    size_type        rfind(string_view needle, size_type offset = 0) const;
    iterator         begin() const { return iterator{data(), size(), 0}; }
    iterator         end() const { return iterator{data(), size(), size()}; }
    reverse_iterator rbegin() const { return reverse_iterator{data(), size(), size()}; }
    reverse_iterator rend() const { return reverse_iterator{data(), size(), 0}; }

    string_view substr(size_type offset) const;
    string_view substr(size_type offset, size_type size) const;

    int compare(string_view other) const;

    string copy() const { return string{data(), size()}; }

    ::pn::input input() const;

    std::string cpp_str() const { return std::string(data(), size()); }

  private:
    const char* _data;
    size_type   _size;
};

bool partition(string_view* found, string_view separator, string_view* input);
bool strtoll(string_view s, int64_t* i, pn_error_code_t* error);
bool strtod(string_view s, double* f, pn_error_code_t* error);

inline string rune::copy() const { return string_view{*this}.copy(); }

inline string& string::  operator+=(string_view s) { return append(s.data(), s.size()), *this; }
inline const string_ref& string_ref::operator+=(string_view s) const {
    return append(s.data(), s.size()), *this;
}

inline string_view string::substr(size_type offset) const {
    return string_view{data() + offset, size() - offset};
}
inline string_view string_ref::substr(size_type offset) const {
    return string_view{data() + offset, size() - offset};
}
inline string_view string_view::substr(size_type offset) const {
    return string_view{data() + offset, size() - offset};
}
inline string_view string::substr(size_type offset, size_type size) const {
    return string_view{data() + offset, size};
}
inline string_view string_ref::substr(size_type offset, size_type size) const {
    return string_view{data() + offset, size};
}
inline string_view string_view::substr(size_type offset, size_type size) const {
    return string_view{data() + offset, size};
}

inline void string::replace(size_type at, size_type size, string_view replacement) {
    pn_strreplace(c_obj(), at, size, replacement.data(), replacement.size());
}
inline void string_ref::replace(size_type at, size_type size, string_view replacement) const {
    pn_strreplace(c_obj(), at, size, replacement.data(), replacement.size());
}

inline bool operator==(string_view x, string_view y) { return x.compare(y) == 0; }
inline bool operator!=(string_view x, string_view y) { return x.compare(y) != 0; }
inline bool operator<(string_view x, string_view y) { return x.compare(y) < 0; }
inline bool operator<=(string_view x, string_view y) { return x.compare(y) <= 0; }
inline bool operator>(string_view x, string_view y) { return x.compare(y) > 0; }
inline bool operator>=(string_view x, string_view y) { return x.compare(y) >= 0; }

}  // namespace pn

#endif  // PN_STRING_
