#pragma once

#ifndef INCLUDED_FROM_KPHP_CORE
  #error "this file must be included only from runtime-core.h"
#endif

using string_size_type = uint32_t;

inline bool string_to_bool(const char *s, string_size_type size) {
  return size >= 2 || (size == 1 && s[0] != '0');
}

// tmp_string is not a real string: it can't be used in a place where string types are expected;
// it can be a result of compiler-inserted evaluation that should be then recognized by the
// runtime functions overloading
//
// in a sense, it's like a string view, but only valid for a single expression evaluation
struct tmp_string {
  const char *data = nullptr;
  string_size_type size = 0;

  tmp_string() = default;
  inline tmp_string(const char *data, string_size_type size);
  explicit inline tmp_string(const string &s);

  bool to_bool() const noexcept {
    return data && string_to_bool(data, size);
  }

  bool has_value() const noexcept { return data != nullptr; }
  bool empty() const noexcept { return size == 0; }
};

struct ArrayBucketDummyStrTag;

class string {
public:
  using size_type = string_size_type;
  static constexpr size_type npos = (size_type)-1;

private:
  char *p;

private:
  struct string_inner {
    size_type size;
    size_type capacity;
    int ref_count;

    inline bool is_shared() const;
    inline void set_length_and_sharable(size_type n);

    inline char *ref_data() const;

    inline static size_type new_capacity(size_type requested_capacity, size_type old_capacity);
    inline static string_inner *create(size_type requested_capacity, size_type old_capacity);

    inline char *reserve(size_type requested_capacity);

    inline void dispose();

    inline void destroy();

    inline size_type get_memory_usage() const;

    inline char *ref_copy();

    inline char *clone(size_type requested_cap);
  };

  inline string_inner *inner() const;

  inline bool disjunct(const char *s) const;
  inline void set_size(size_type new_size);

  inline static char *create(const char *beg, const char *end);
  // IMPORTANT: this function may return read-only strings for n == 0 and n == 1.
  // Use it unless you have to manually write something into the buffer.
  inline static char *create(size_type req, char c);
  inline static char *create(size_type req, bool b);

  friend class string_cache;

public:
  static constexpr size_type max_size() noexcept {
    return ((size_type)-1 - sizeof(string_inner) - 1) / 4;
  }

  static size_type unsafe_cast_to_size_type(int64_t size) noexcept {
    php_assert(size >= 0);
    if (unlikely(size > max_size())) {
      php_critical_error ("Trying to make too big string of size %" PRId64, size);
    }
    return static_cast<size_type>(size);
  }

  inline string();
  inline string(const string &str) noexcept;
  inline string(string &&str) noexcept;
  inline string(const char *s, size_type n);
  inline explicit string(const char *s);
  // IMPORTANT: this constructor may return read-only strings for n == 0 and n == 1.
  // Use it unless you have to manually operate with string's internal buffer.
  inline string(size_type n, char c);
  inline string(size_type n, bool b);
  inline explicit string(int64_t i);
  inline explicit string(int32_t i): string(static_cast<int64_t>(i)) {}
  inline explicit string(double f);

  ~string() noexcept;

  // Achtung! Do not use it!
  // This constructor intended ONLY for usage inside array's bucket
  // in order to being able to distinguish which key type is stored there
  inline explicit string(ArrayBucketDummyStrTag) noexcept;
  inline bool is_dummy_string() const noexcept;

  inline string &operator=(const string &str) noexcept;
  inline string &operator=(string &&str) noexcept;

  inline size_type size() const;

  inline void shrink(size_type n);

  inline size_type capacity() const;

  inline void make_not_shared();

  inline string copy_and_make_not_shared() const;

  inline void force_reserve(size_type res);
  inline string &reserve_at_least(size_type res);

  inline bool empty() const;
  inline bool starts_with(const string &other) const noexcept;
  inline bool ends_with(const string &other) const noexcept;

  inline const char &operator[](size_type pos) const;
  inline char &operator[](size_type pos);

  inline string &append(const string &str) __attribute__ ((always_inline));
  inline string &append(const string &str, size_type pos2, size_type n2) __attribute__ ((always_inline));
  inline string &append(const char *s) __attribute__ ((always_inline));
  inline string &append(const char *s, size_type n) __attribute__ ((always_inline));
  inline string &append(size_type n, char c) __attribute__ ((always_inline));

  inline string &append(bool b) __attribute__ ((always_inline));
  inline string &append(int64_t i) __attribute__ ((always_inline));
  inline string &append(int32_t v) {return append(int64_t{v});}
  inline string &append(double d) __attribute__ ((always_inline));
  inline string &append(const mixed &v) __attribute__ ((always_inline));

  inline string &append_unsafe(bool b) __attribute__((always_inline));
  inline string &append_unsafe(int64_t i) __attribute__((always_inline));
  inline string &append_unsafe(int32_t v) {return append(int64_t{v});}
  inline string &append_unsafe(double d) __attribute__((always_inline));
  inline string &append_unsafe(const string &str) __attribute__((always_inline));
  inline string &append_unsafe(tmp_string str) __attribute__((always_inline));
  inline string &append_unsafe(const char *s, size_type n) __attribute__((always_inline));
  inline string &append_unsafe(const mixed &v) __attribute__((always_inline));
  inline string &finish_append() __attribute__((always_inline));

  template<class T>
  inline string &append_unsafe(const array<T> &a) __attribute__((always_inline));

  template<class T>
  inline string &append_unsafe(const Optional<T> &v) __attribute__((always_inline));

  inline void push_back(char c);

  inline string &assign(const string &str);
  inline string &assign(const string &str, size_type pos, size_type n);
  inline string &assign(const char *s);
  inline string &assign(const char *s, size_type n);
  inline string &assign(size_type n, char c);
  inline string &assign(size_type n, bool b);//do not initialize. if b == true - just reserve

  //assign binary string_inner representation
  //can be used only on empty string to receive logically const string
  inline void assign_raw(const char *s);

  inline void swap(string &s);

  inline char *buffer();
  inline const char *c_str() const;

  inline string substr(size_type pos, size_type n) const;

  inline size_type find_first_of(const string &s, size_type pos = 0) const;
  inline size_type find(const string &s, size_type pos = 0) const;

  inline void warn_on_float_conversion() const;

  inline bool try_to_int(int64_t *val) const;
  inline bool try_to_float(double *val, bool php8_warning = true) const;
  inline bool try_to_float_as_php7(double *val) const;
  inline bool try_to_float_as_php8(double *val) const;

  inline mixed to_numeric() const;
  inline bool to_bool() const;
  inline static int64_t to_int(const char *s, size_type l);
  inline int64_t to_int(int64_t start, int64_t l) const;
  inline int64_t to_int() const;
  inline double to_float() const;
  inline const string &to_string() const;

  inline int64_t safe_to_int() const;

  inline bool is_int() const;
  inline bool is_numeric_as_php7() const;
  inline bool is_numeric_as_php8() const;
  inline bool is_numeric() const;

  inline int64_t hash() const;

  static inline int64_t compare(const string &str, const char *other, size_type other_size);
  inline int64_t compare(const string &str) const;

  inline bool isset(int64_t index) const;
  static inline int64_t get_correct_offset(size_type size, int64_t offset);
  inline int64_t get_correct_index(int64_t index) const;
  inline int64_t get_correct_offset(int64_t offset) const;
  inline int64_t get_correct_offset_clamped(int64_t offset) const;
  inline const string get_value(int64_t int_key) const;
  inline const string get_value(const string &string_key) const;
  inline const string get_value(const mixed &v) const;

  inline int64_t get_reference_counter() const;

  inline bool is_reference_counter(ExtraRefCnt ref_cnt_value) const noexcept;
  inline void set_reference_counter_to(ExtraRefCnt ref_cnt_value) noexcept;
  inline void force_destroy(ExtraRefCnt expected_ref_cnt) noexcept;

  inline size_type estimate_memory_usage() const;
  inline static size_type estimate_memory_usage(size_t len) noexcept;

  inline static constexpr size_t inner_sizeof() noexcept { return sizeof(string_inner); }
  inline static string make_const_string_on_memory(const char *str, size_type len, void *memory, size_t memory_size);

  inline void destroy() __attribute__((always_inline));
};

inline string materialize_tmp_string(tmp_string s) {
  if (!s.data) {
    return string{}; // an empty string
  }
  return string{s.data, s.size};
}

inline bool operator==(const string &lhs, const string &rhs);

#define CONST_STRING(const_str) string (const_str, sizeof (const_str) - 1)
#define STRING_EQUALS(str, const_str) (str.size() + 1 == sizeof (const_str) && !strcmp (str.c_str(), const_str))

inline bool operator!=(const string &lhs, const string &rhs);

inline bool is_ok_float(double v);

inline int64_t compare_strings_php_order(const string &lhs, const string &rhs);

inline void swap(string &lhs, string &rhs);


inline string::size_type max_string_size(bool) __attribute__((always_inline));
inline string::size_type max_string_size(int64_t) __attribute__((always_inline));
inline string::size_type max_string_size(int32_t) __attribute__((always_inline));
inline string::size_type max_string_size(double) __attribute__((always_inline));
inline string::size_type max_string_size(const string &s) __attribute__((always_inline));
inline string::size_type max_string_size(tmp_string s) __attribute__((always_inline));
inline string::size_type max_string_size(const mixed &v) __attribute__((always_inline));

template<class T>
inline string::size_type max_string_size(const array<T> &) __attribute__((always_inline));

template<class T>
inline string::size_type max_string_size(const Optional<T> &v) __attribute__((always_inline));

inline bool wrap_substr_args(string::size_type str_len, int64_t &start, int64_t &length) {
  if (length < 0 && -length > str_len) {
    return false;
  }

  if (length > str_len) {
    length = str_len;
  }

  if (start > str_len) {
    return false;
  }

  if (length < 0 && length < start - str_len) {
    return false;
  }
  start = string::get_correct_offset(str_len, start);
  if (length < 0) {
    length = (str_len - start) + length;
    if (length < 0) {
      length = 0;
    }
  }

  if (length > str_len - start) {
    length = str_len - start;
  }

  return true;
}
