text: /* Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You 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.
   */
  
  #define C_CFISH_CHARBUF
  #define C_CFISH_STRING
  #define CFISH_USE_SHORT_NAMES
  
  #include "charmony.h"
  
  #include <string.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <ctype.h>
  
  #include "Clownfish/CharBuf.h"
  
  #include "Clownfish/Err.h"
  #include "Clownfish/String.h"
  #include "Clownfish/Util/Memory.h"
  #include "Clownfish/Util/StringHelper.h"
  #include "Clownfish/Class.h"
  
  // Helper function for throwing invalid UTF-8 error. Since THROW uses
  // a String internally, calling THROW with invalid UTF-8 would create an
  // infinite loop -- so we fwrite some of the bogus text to stderr and
  // invoke THROW with a generic message.
  #define DIE_INVALID_UTF8(text, size) \
      S_die_invalid_utf8(text, size, __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO)
  static void
  S_die_invalid_utf8(const char *text, size_t size, const char *file, int line,
                     const char *func);
  
  // Helper function for throwing invalid pattern error.
  static void
  S_die_invalid_pattern(const char *pattern);
  
  CharBuf*
  CB_new(size_t size) {
      CharBuf *self = (CharBuf*)Class_Make_Obj(CHARBUF);
      return CB_init(self, size);
  }
  
  CharBuf*
  CB_init(CharBuf *self, size_t size) {
      // Derive.
      self->ptr = (char*)MALLOCATE(size + 1);
  
      // Init.
      *self->ptr = '\0'; // Empty string.
  
      // Assign.
      self->size = 0;
      self->cap  = size + 1;
  
      return self;
  }
  
  CharBuf*
  CB_new_from_str(String *string) {
      return CB_new_from_trusted_utf8(string->ptr, string->size);
  }
  
  CharBuf*
  CB_new_from_utf8(const char *ptr, size_t size) {
      if (!StrHelp_utf8_valid(ptr, size)) {
          DIE_INVALID_UTF8(ptr, size);
      }
      return CB_new_from_trusted_utf8(ptr, size);
  }
  
  CharBuf*
  CB_new_from_trusted_utf8(const char *ptr, size_t size) {
      CharBuf *self = (CharBuf*)Class_Make_Obj(CHARBUF);
  
      // Derive.
      self->ptr = (char*)MALLOCATE(size + 1);
  
      // Copy.
      memcpy(self->ptr, ptr, size);
  
      // Assign.
      self->size      = size;
      self->cap       = size + 1;
      self->ptr[size] = '\0'; // Null terminate.
  
      return self;
  }
  
  CharBuf*
  CB_newf(const char *pattern, ...) {
      CharBuf *self = CB_new(strlen(pattern));
      va_list args;
      va_start(args, pattern);
      CB_VCatF(self, pattern, args);
      va_end(args);
      return self;
  }
  
  void
  CB_Destroy_IMP(CharBuf *self) {
      FREEMEM(self->ptr);
      SUPER_DESTROY(self, CHARBUF);
  }
  
  char*
  CB_Grow_IMP(CharBuf *self, size_t size) {
      if (size >= self->cap) {
          self->cap = size + 1;
          self->ptr = (char*)REALLOCATE(self->ptr, self->cap);
      }
      return self->ptr;
  }
  
  static void
  S_die_invalid_utf8(const char *text, size_t size, const char *file, int line,
                     const char *func) {
      fprintf(stderr, "Invalid UTF-8, aborting: '");
      fwrite(text, sizeof(char), size < 200 ? size : 200, stderr);
      if (size > 200) { fwrite("[...]", sizeof(char), 5, stderr); }
      fprintf(stderr, "' (length %lu)\n", (unsigned long)size);
      Err_throw_at(ERR, file, line, func, "Invalid UTF-8");
  }
  
  static void
  S_die_invalid_pattern(const char *pattern) {
      size_t  pattern_len = strlen(pattern);
      fprintf(stderr, "Invalid pattern, aborting: '");
      fwrite(pattern, sizeof(char), pattern_len, stderr);
      fprintf(stderr, "'\n");
      THROW(ERR, "Invalid pattern.");
  }
  
  void
  CB_catf(CharBuf *self, const char *pattern, ...) {
      va_list args;
      va_start(args, pattern);
      CB_VCatF(self, pattern, args);
      va_end(args);
  }
  
  void
  CB_VCatF_IMP(CharBuf *self, const char *pattern, va_list args) {
      size_t      pattern_len   = strlen(pattern);
      const char *pattern_start = pattern;
      const char *pattern_end   = pattern + pattern_len;
      char        buf[64];
  
      for (; pattern < pattern_end; pattern++) {
          const char *slice_end = pattern;
  
          // Consume all characters leading up to a '%'.
          while (slice_end < pattern_end && *slice_end != '%') { slice_end++; }
          if (pattern != slice_end) {
              size_t size = slice_end - pattern;
              CB_Cat_Trusted_Utf8(self, pattern, size);
              pattern = slice_end;
          }
  
          if (pattern < pattern_end) {
              pattern++; // Move past '%'.
  
              switch (*pattern) {
                  case '%': {
                          CB_Cat_Trusted_Utf8(self, "%", 1);
                      }
                      break;
                  case 'o': {
                          Obj *obj = va_arg(args, Obj*);
                          if (!obj) {
                              CB_Cat_Trusted_Utf8(self, "[NULL]", 6);
                          }
                          else if (Obj_Is_A(obj, STRING)) {
                              CB_Cat(self, (String*)obj);
                          }
                          else {
                              String *string = Obj_To_String(obj);
                              CB_Cat(self, string);
                              DECREF(string);
                          }
                      }
                      break;
                  case 'i': {
                          int64_t val = 0;
                          size_t size;
                          if (pattern[1] == '8') {
                              val = va_arg(args, int32_t);
                              pattern++;
                          }
                          else if (pattern[1] == '3' && pattern[2] == '2') {
                              val = va_arg(args, int32_t);
                              pattern += 2;
                          }
                          else if (pattern[1] == '6' && pattern[2] == '4') {
                              val = va_arg(args, int64_t);
                              pattern += 2;
                          }
                          else {
                              S_die_invalid_pattern(pattern_start);
                          }
                          size = sprintf(buf, "%" PRId64, val);
                          CB_Cat_Trusted_Utf8(self, buf, size);
                      }
                      break;
                  case 'u': {
                          uint64_t val = 0;
                          size_t size;
                          if (pattern[1] == '8') {
                              val = va_arg(args, uint32_t);
                              pattern += 1;
                          }
                          else if (pattern[1] == '3' && pattern[2] == '2') {
                              val = va_arg(args, uint32_t);
                              pattern += 2;
                          }
                          else if (pattern[1] == '6' && pattern[2] == '4') {
                              val = va_arg(args, uint64_t);
                              pattern += 2;
                          }
                          else {
                              S_die_invalid_pattern(pattern_start);
                          }
                          size = sprintf(buf, "%" PRIu64, val);
                          CB_Cat_Trusted_Utf8(self, buf, size);
                      }
                      break;
                  case 'f': {
                          if (pattern[1] == '6' && pattern[2] == '4') {
                              double num  = va_arg(args, double);
                              char bigbuf[512];
                              size_t size = sprintf(bigbuf, "%g", num);
                              CB_Cat_Trusted_Utf8(self, bigbuf, size);
                              pattern += 2;
                          }
                          else {
                              S_die_invalid_pattern(pattern_start);
                          }
                      }
                      break;
                  case 'x': {
                          if (pattern[1] == '3' && pattern[2] == '2') {
                              unsigned long val = va_arg(args, uint32_t);
                              size_t size = sprintf(buf, "%.8lx", val);
                              CB_Cat_Trusted_Utf8(self, buf, size);
                              pattern += 2;
                          }
                          else {
                              S_die_invalid_pattern(pattern_start);
                          }
                      }
                      break;
                  case 's': {
                          char *string = va_arg(args, char*);
                          if (string == NULL) {
                              CB_Cat_Trusted_Utf8(self, "[NULL]", 6);
                          }
                          else {
                              size_t size = strlen(string);
                              if (StrHelp_utf8_valid(string, size)) {
                                  CB_Cat_Trusted_Utf8(self, string, size);
                              }
                              else {
                                  CB_Cat_Trusted_Utf8(self, "[INVALID UTF8]", 14);
                              }
                          }
                      }
                      break;
                  default: {
                          // Assume NULL-terminated pattern string, which
                          // eliminates the need for bounds checking if '%' is
                          // the last visible character.
                          S_die_invalid_pattern(pattern_start);
                      }
              }
          }
      }
  }
  
  String*
  CB_To_String_IMP(CharBuf *self) {
      return Str_new_from_trusted_utf8(self->ptr, self->size);
  }
  
  String*
  CB_Yield_String_IMP(CharBuf *self) {
      String *retval
          = Str_new_steal_trusted_utf8(self->ptr, self->size);
      self->ptr  = NULL;
      self->size = 0;
      self->cap  = 0;
      return retval;
  }
  
  void
  CB_Cat_Char_IMP(CharBuf *self, int32_t code_point) {
      const size_t MAX_UTF8_BYTES = 4;
      if (self->size + MAX_UTF8_BYTES >= self->cap) {
          CB_Grow(self, Memory_oversize(self->size + MAX_UTF8_BYTES,
                                       sizeof(char)));
      }
      char *end = self->ptr + self->size;
      size_t count = StrHelp_encode_utf8_char(code_point, (uint8_t*)end);
      self->size += count;
      *(end + count) = '\0';
  }
  
  CharBuf*
  CB_Clone_IMP(CharBuf *self) {
      return CB_new_from_trusted_utf8(self->ptr, self->size);
  }
  
  static CFISH_INLINE void
  SI_mimic_utf8(CharBuf *self, const char* ptr, size_t size) {
      if (size >= self->cap) { CB_Grow(self, size); }
      memmove(self->ptr, ptr, size);
      self->size = size;
      self->ptr[size] = '\0';
  }
  
  void
  CB_Mimic_Utf8_IMP(CharBuf *self, const char* ptr, size_t size) {
      if (!StrHelp_utf8_valid(ptr, size)) {
          DIE_INVALID_UTF8(ptr, size);
      }
      SI_mimic_utf8(self, ptr, size);
  }
  
  void
  CB_Mimic_IMP(CharBuf *self, Obj *other) {
      const char *ptr;
      size_t size;
      if (Obj_Is_A(other, CHARBUF)) {
          CharBuf *twin = (CharBuf*)other;
          ptr  = twin->ptr;
          size = twin->size;
      }
      else if (Obj_Is_A(other, STRING)) {
          String *twin = (String*)other;
          ptr  = twin->ptr;
          size = twin->size;
      }
      else {
          THROW(ERR, "CharBuf can't mimic %o", Obj_Get_Class_Name(other));
      }
      SI_mimic_utf8(self, ptr, size);
  }
  
  static CFISH_INLINE void
  SI_cat_utf8(CharBuf *self, const char* ptr, size_t size) {
      const size_t new_size = self->size + size;
      if (new_size >= self->cap) {
          size_t amount = Memory_oversize(new_size, sizeof(char));
          CB_Grow(self, amount);
      }
      memcpy(self->ptr + self->size, ptr, size);
      self->size = new_size;
      self->ptr[new_size] = '\0';
  }
  
  void
  CB_Cat_Utf8_IMP(CharBuf *self, const char* ptr, size_t size) {
      if (!StrHelp_utf8_valid(ptr, size)) {
          DIE_INVALID_UTF8(ptr, size);
      }
      SI_cat_utf8(self, ptr, size);
  }
  
  void
  CB_Cat_Trusted_Utf8_IMP(CharBuf *self, const char* ptr, size_t size) {
      SI_cat_utf8(self, ptr, size);
  }
  
  void
  CB_Cat_IMP(CharBuf *self, String *string) {
      SI_cat_utf8(self, string->ptr, string->size);
  }
  
  void
  CB_Set_Size_IMP(CharBuf *self, size_t size) {
      if (size >= self->cap) {
          THROW(ERR, "Can't set size of CharBuf beyond capacity");
      }
      self->size = size;
  }
  
  size_t
  CB_Get_Size_IMP(CharBuf *self) {
      return self->size;
  }
  
  char*
  CB_Get_Ptr8_IMP(CharBuf *self) {
      return self->ptr;
  }
  
  
  