// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

#include "names.h"

#include <stdlib.h>

#include "protobuf.h"

/* stringsink *****************************************************************/

typedef struct {
  char* ptr;
  size_t len, size;
} stringsink;

static size_t stringsink_string(stringsink* sink, const char* ptr, size_t len) {
  size_t new_size = sink->size;

  while (sink->len + len > new_size) {
    new_size *= 2;
  }

  if (new_size != sink->size) {
    sink->ptr = realloc(sink->ptr, new_size);
    sink->size = new_size;
  }

  memcpy(sink->ptr + sink->len, ptr, len);
  sink->len += len;

  return len;
}

static void stringsink_init(stringsink* sink) {
  sink->size = 32;
  sink->ptr = malloc(sink->size);
  PBPHP_ASSERT(sink->ptr != NULL);
  sink->len = 0;
}

static void stringsink_uninit(stringsink* sink) { free(sink->ptr); }

/* def name -> classname ******************************************************/

const char* const kReservedNames[] = {
    "abstract",   "and",      "array",        "as",
    "break",      "callable", "case",         "catch",
    "class",      "clone",    "const",        "continue",
    "declare",    "default",  "die",          "do",
    "echo",       "else",     "elseif",       "empty",
    "enddeclare", "endfor",   "endforeach",   "endif",
    "endswitch",  "endwhile", "eval",         "exit",
    "extends",    "final",    "finally",      "fn",
    "for",        "foreach",  "function",     "if",
    "implements", "include",  "include_once", "instanceof",
    "global",     "goto",     "insteadof",    "interface",
    "isset",      "list",     "match",        "namespace",
    "new",        "object",   "or",           "parent",
    "print",      "private",  "protected",    "public",
    "readonly",   "require",  "require_once", "return",
    "self",       "static",   "switch",       "throw",
    "trait",      "try",      "unset",        "use",
    "var",        "while",    "xor",          "yield",
    "int",        "float",    "bool",         "string",
    "true",       "false",    "null",         "void",
    "iterable",   "mixed",    "never",        NULL};

const char* const kPreviouslyUnreservedNames[] = {"readonly", NULL};

bool is_reserved_name(const char* name) {
  int i;
  for (i = 0; kReservedNames[i]; i++) {
    if (strcmp(kReservedNames[i], name) == 0) {
      return true;
    }
  }
  return false;
}

bool is_previously_unreserved_name(const char* name) {
  for (int i = 0; kPreviouslyUnreservedNames[i]; i++) {
    if (strcmp(kPreviouslyUnreservedNames[i], name) == 0) {
      return true;
    }
  }
  return false;
}

static char nolocale_tolower(char ch) {
  if (ch >= 'A' && ch <= 'Z') {
    return ch - ('A' - 'a');
  } else {
    return ch;
  }
}

static char nolocale_toupper(char ch) {
  if (ch >= 'a' && ch <= 'z') {
    return ch - ('a' - 'A');
  } else {
    return ch;
  }
}

static char* strdup_nolocale_lower(const char* str, int length) {
  char* lower = malloc(length + 1);
  lower[length] = '\0';
  for (int i = 0; i < length; ++i) {
    lower[i] = nolocale_tolower(str[i]);
  }
  return lower;
}

static bool is_reserved(const char* segment, int length, bool previous) {
  bool result;
  char* lower = strdup_nolocale_lower(segment, length);
  result = is_reserved_name(lower);
  if (result && previous && is_previously_unreserved_name(lower)) {
    result = false;
  }
  free(lower);
  return result;
}

static void fill_prefix(const char* segment, int length,
                        const char* prefix_given, const char* package_name,
                        stringsink* classname, bool previous) {
  if (prefix_given != NULL && strcmp(prefix_given, "") != 0) {
    stringsink_string(classname, prefix_given, strlen(prefix_given));
  } else {
    if (is_reserved(segment, length, previous)) {
      if (package_name != NULL &&
          strcmp("google.protobuf", package_name) == 0) {
        stringsink_string(classname, "GPB", 3);
      } else {
        stringsink_string(classname, "PB", 2);
      }
    }
  }
}

static void fill_segment(const char* segment, int length, stringsink* classname,
                         bool use_camel) {
  if (use_camel && (segment[0] < 'A' || segment[0] > 'Z')) {
    char first = nolocale_toupper(segment[0]);
    stringsink_string(classname, &first, 1);
    stringsink_string(classname, segment + 1, length - 1);
  } else {
    stringsink_string(classname, segment, length);
  }
}

static void fill_namespace(const char* package, const char* php_namespace,
                           stringsink* classname, bool previous) {
  if (php_namespace != NULL) {
    if (strlen(php_namespace) != 0) {
      stringsink_string(classname, php_namespace, strlen(php_namespace));
      stringsink_string(classname, "\\", 1);
    }
  } else if (package != NULL) {
    int i = 0, j = 0;
    size_t package_len = strlen(package);
    while (i < package_len) {
      j = i;
      while (j < package_len && package[j] != '.') {
        j++;
      }
      fill_prefix(package + i, j - i, "", package, classname, previous);
      fill_segment(package + i, j - i, classname, true);
      stringsink_string(classname, "\\", 1);
      i = j + 1;
    }
  }
}

static void fill_classname(const char* fullname, const char* package,
                           const char* prefix, stringsink* classname,
                           bool previous) {
  int classname_start = 0;
  if (package != NULL) {
    size_t package_len = strlen(package);
    classname_start = package_len == 0 ? 0 : package_len + 1;
  }
  size_t fullname_len = strlen(fullname);

  int i = classname_start, j;
  while (i < fullname_len) {
    j = i;
    while (j < fullname_len && fullname[j] != '.') {
      j++;
    }
    fill_prefix(fullname + i, j - i, prefix, package, classname, previous);
    fill_segment(fullname + i, j - i, classname, false);
    if (j != fullname_len) {
      stringsink_string(classname, "\\", 1);
    }
    i = j + 1;
  }
}

char* str_view_dup(upb_StringView str) {
  char* ret = malloc(str.size + 1);
  memcpy(ret, str.data, str.size);
  ret[str.size] = '\0';
  return ret;
}

char* GetPhpClassname(const upb_FileDef* file, const char* fullname,
                      bool previous) {
  // Prepend '.' to package name to make it absolute. In the 5 additional
  // bytes allocated, one for '.', one for trailing 0, and 3 for 'GPB' if
  // given message is google.protobuf.Empty.
  const google_protobuf_FileOptions* opts = upb_FileDef_Options(file);
  const char* package = upb_FileDef_Package(file);
  char* php_namespace =
      google_protobuf_FileOptions_has_php_namespace(opts)
          ? str_view_dup(google_protobuf_FileOptions_php_namespace(opts))
          : NULL;
  char* prefix =
      google_protobuf_FileOptions_has_php_class_prefix(opts)
          ? str_view_dup(google_protobuf_FileOptions_php_class_prefix(opts))
          : NULL;
  char* ret;

  stringsink namesink;
  stringsink_init(&namesink);

  fill_namespace(package, php_namespace, &namesink, previous);
  fill_classname(fullname, package, prefix, &namesink, previous);
  stringsink_string(&namesink, "\0", 1);
  ret = strdup(namesink.ptr);
  stringsink_uninit(&namesink);
  free(php_namespace);
  free(prefix);
  return ret;
}

bool IsPreviouslyUnreservedClassName(const char* fullname) {
  const char* classname = strrchr(fullname, '\\');
  if (classname) {
    classname += 1;
  } else {
    classname = fullname;
  }
  if (strncmp(classname, "PB", 2) != 0) {
    return false;
  }
  classname += 2;
  int length = strlen(classname);
  char* lower = strdup_nolocale_lower(classname, length);
  for (int j = 0; kPreviouslyUnreservedNames[j]; j++) {
    if (strcmp(kPreviouslyUnreservedNames[j], lower) == 0) {
      free(lower);
      return true;
    }
  }
  free(lower);
  return false;
}
