/* intern.c
 * Copyright 1984-2017 Cisco Systems, Inc.
 * 
 * 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.
 */

#include "system.h"

/* locally defined functions */
static void oblist_insert PROTO((ptr sym, iptr idx, IGEN g));
static iptr hash PROTO((const unsigned char *s, iptr n));
static iptr hash_sc PROTO((const string_char *s, iptr n));
static iptr hash_uname PROTO((const string_char *s, iptr n));
static ptr mkstring PROTO((const string_char *s, iptr n));

/* list of some primes to use for oblist sizes */
#if (ptr_bits == 32)
static iptr oblist_lengths[] = {
  1031,
  2053,
  4099,
  8209,
  16411,
  32771,
  65537,
  131101,
  262147,
  524309,
  1048583,
  2097169,
  4194319,
  8388617,
  16777259,
  33554467,
  67108879,
  134217757,
  268435459,
  536870923,
  1073741827,
  0};
#endif
#if (ptr_bits == 64)
static iptr oblist_lengths[] = {
  1031,
  2053,
  4099,
  8209,
  16411,
  32771,
  65537,
  131101,
  262147,
  524309,
  1048583,
  2097169,
  4194319,
  8388617,
  16777259,
  33554467,
  67108879,
  134217757,
  268435459,
  536870923,
  1073741827,
  2147483659,
  4294967311,
  8589934609,
  17179869209,
  34359738421,
  68719476767,
  137438953481,
  274877906951,
  549755813911,
  1099511627791,
  2199023255579,
  4398046511119,
  8796093022237,
  17592186044423,
  35184372088891,
  70368744177679,
  140737488355333,
  281474976710677,
  562949953421381,
  1125899906842679,
  2251799813685269,
  4503599627370517,
  9007199254740997,
  18014398509482143,
  36028797018963971,
  72057594037928017,
  144115188075855881,
  288230376151711813,
  576460752303423619,
  1152921504606847009,
  2305843009213693967,
  4611686018427388039,
  0};
#endif

void S_intern_init() {
    IGEN g;

    if (!S_boot_time) return;

    S_G.oblist_length_pointer = &oblist_lengths[3];
    S_G.oblist_length = *S_G.oblist_length_pointer;
    S_G.oblist_count = 0;
    S_G.oblist = S_getmem(S_G.oblist_length * sizeof(bucket *), 1);
    for (g = 0; g < static_generation; g += 1) S_G.buckets_of_generation[g] = NULL;
}

static void oblist_insert(ptr sym, iptr idx, IGEN g) {
  bucket *b, *oldb, **pb;

  find_room(g == 0 ? space_new : space_data, g, typemod, sizeof(bucket), b);
  b->sym = sym;
  if (g == 0) {
    b->next = S_G.oblist[idx];
    S_G.oblist[idx] = b;
  } else {
    for (pb = &S_G.oblist[idx]; (oldb = *pb) != NULL && SegmentGeneration(addr_get_segment(oldb)) < g; pb = &oldb->next);
    b->next = oldb;
    *pb = b;
  }

  if (g != static_generation) {
    bucket_list *bl;
    find_room(g == 0 ? space_new : space_data, g, typemod, sizeof(bucket_list), bl);
    bl->car = b;
    bl->cdr = S_G.buckets_of_generation[g];
    S_G.buckets_of_generation[g] = bl;
  }

  S_G.oblist_count += 1;
}

void S_resize_oblist(void) {
  bucket **new_oblist, *b, *oldb, **pb, *bnext;
  iptr *new_oblist_length_pointer, new_oblist_length, i, idx;
  ptr sym;
  IGEN g;

  new_oblist_length_pointer = S_G.oblist_length_pointer;

  if (S_G.oblist_count < S_G.oblist_length) {
    while (new_oblist_length_pointer != &oblist_lengths[0] && *(new_oblist_length_pointer - 1) >= S_G.oblist_count) {
      new_oblist_length_pointer -= 1;
    }
  } else if (S_G.oblist_count > S_G.oblist_length) {
    while (*(new_oblist_length_pointer + 1) != 0 && *(new_oblist_length_pointer + 1) <= S_G.oblist_count) {
      new_oblist_length_pointer += 1;
    }
  }

  if (new_oblist_length_pointer == S_G.oblist_length_pointer) return;

  new_oblist_length = *new_oblist_length_pointer;
  new_oblist = S_getmem(new_oblist_length * sizeof(bucket *), 1);

  for (i = 0; i < S_G.oblist_length; i += 1) {
    for (b = S_G.oblist[i]; b != NULL; b = bnext) {
      bnext = b->next;
      sym = b->sym;
      idx = UNFIX(SYMHASH(sym)) % new_oblist_length;
      g = GENERATION(sym);

      for (pb = &new_oblist[idx]; (oldb = *pb) != NULL && SegmentGeneration(addr_get_segment(oldb)) < g; pb = &oldb->next);
      b->next = oldb;
      *pb = b;
    }
  }

  S_freemem(S_G.oblist, S_G.oblist_length * sizeof(bucket *));
  S_G.bytesof[static_generation][countof_oblist] += (new_oblist_length - S_G.oblist_length) * sizeof(bucket *);

  S_G.oblist_length_pointer = new_oblist_length_pointer;
  S_G.oblist_length = new_oblist_length;
  S_G.oblist = new_oblist;
}

/* hash function: multiplier weights each character, h = n factors in the length */
#define multiplier 3

static iptr hash(const unsigned char *s, iptr n) {
  iptr h = n + 401887359;
  while (n--) h = h * multiplier + *s++;
  return h & most_positive_fixnum;
}

static iptr hash_sc(const string_char *s, iptr n) {
  iptr h = n + 401887359;
  while (n--) h = h * multiplier + Schar_value(*s++);
  return h & most_positive_fixnum;
}

static iptr hash_uname(const string_char *s, iptr n) {
  /* attempting to get dissimilar hash codes for gensyms created in the same session */
  iptr i = n, h = 0; iptr pos = 1; int d, c;

  while (i-- > 0) {
    if ((c = Schar_value(s[i])) == '-') {
      if (pos <= 10) break;
      return (h + 523658599) & most_positive_fixnum;
    }
    d = c - '0';
    if (d < 0 || d > 9) break;
    h += d * pos;
    pos *= 10;
  }

  return hash_sc(s, n);
}

static ptr mkstring(const string_char *s, iptr n) {
  iptr i;
  ptr str = S_string(NULL, n);
  for (i = 0; i != n; i += 1) STRIT(str, i) = s[i];
  return str;
}

/* handles single-byte characters, implicit length */
ptr S_intern(const unsigned char *s) {
  iptr n = strlen((const char *)s);
  iptr hc = hash(s, n);
  iptr idx = hc % S_G.oblist_length;
  ptr sym;
  bucket *b;

  tc_mutex_acquire()

  b = S_G.oblist[idx];
  while (b != NULL) {
    sym = b->sym;
    if (!GENSYMP(sym)) {
       ptr str = SYMNAME(sym);
       if (Sstring_length(str) == n) {
          iptr i;
          for (i = 0; ; i += 1) {
            if (i == n) {
               tc_mutex_release()
               return sym;
            }
            if (Sstring_ref(str, i) != s[i]) break;
          }
       }
    }
    b = b->next;
  }

  sym = S_symbol(S_string((const char *)s, n));
  INITSYMHASH(sym) = FIX(hc);
  oblist_insert(sym, idx, 0);

  tc_mutex_release()
  return sym;
}

/* handles string_chars, explicit length */
ptr S_intern_sc(const string_char *name, iptr n, ptr name_str) {
  iptr hc = hash_sc(name, n);
  iptr idx = hc % S_G.oblist_length;
  ptr sym;
  bucket *b;

  tc_mutex_acquire()

  b = S_G.oblist[idx];
  while (b != NULL) {
    sym = b->sym;
    if (!GENSYMP(sym)) {
       ptr str = SYMNAME(sym);
       if (Sstring_length(str) == n) {
          iptr i;
          for (i = 0; ; i += 1) {
            if (i == n) {
               tc_mutex_release()
               return sym;
            }
            if (STRIT(str, i) != name[i]) break;
          }
       }
    }
    b = b->next;
  }

  /* if (name_str == Sfalse) */ name_str = mkstring(name, n);
  sym = S_symbol(name_str);
  INITSYMHASH(sym) = FIX(hc);
  oblist_insert(sym, idx, 0);

  tc_mutex_release()
  return sym;
}

ptr S_intern3(const string_char *pname, iptr plen, const string_char *uname, iptr ulen, ptr pname_str, ptr uname_str) {
  iptr hc = hash_uname(uname, ulen);
  iptr idx = hc % S_G.oblist_length;
  ptr sym;
  bucket *b;

  tc_mutex_acquire()

  b = S_G.oblist[idx];
  while (b != NULL) {
    sym = b->sym;
    if (GENSYMP(sym)) {
       ptr str = Scar(SYMNAME(sym));
       if (Sstring_length(str) == ulen) {
          iptr i;
          for (i = 0; ; i += 1) {
            if (i == ulen) {
               tc_mutex_release()
               return sym;
            }
            if (STRIT(str, i) != uname[i]) break;
          }
       }
    }
    b = b->next;
  }

  if (pname_str == Sfalse) pname_str = mkstring(pname, plen);
  if (uname_str == Sfalse) uname_str = mkstring(uname, ulen);
  sym = S_symbol(Scons(uname_str, pname_str));
  INITSYMHASH(sym) = FIX(hc);
  oblist_insert(sym, idx, 0);

  tc_mutex_release()
  return sym;
}

void S_intern_gensym(sym) ptr sym; {
  ptr uname_str = Scar(SYMNAME(sym));
  const string_char *uname = &STRIT(uname_str, 0);
  iptr ulen = Sstring_length(uname_str);
  iptr hc = hash_uname(uname, ulen);
  iptr idx = hc % S_G.oblist_length;
  bucket *b;

  tc_mutex_acquire()

  b = S_G.oblist[idx];
  while (b != NULL) {
    ptr x = b->sym;
    if (GENSYMP(x)) {
       ptr str = Scar(SYMNAME(x));
       if (Sstring_length(str) == ulen) {
          iptr i;
          for (i = 0; ; i += 1) {
            if (i == ulen) {
               tc_mutex_release()
               S_error1("intern-gensym", "unique name ~s already interned", uname_str);
            }
            if (Sstring_ref(str, i) != uname[i]) break;
          }
       }
    }
    b = b->next;
  }

  INITSYMHASH(sym) = FIX(hc);
  oblist_insert(sym, idx, GENERATION(sym));

  tc_mutex_release()
}

/* retrofit existing symbols once nonprocedure_code is available */
void S_retrofit_nonprocedure_code() {
  ptr npc, sym, val; bucket_list *bl;

  npc = S_G.nonprocedure_code;

  /* assuming this happens early, before collector has been called, so need look only for generation 0 symbols */
  for (bl = S_G.buckets_of_generation[0]; bl != NULL; bl = bl->cdr) {
    sym = bl->car->sym;
    val = SYMVAL(sym);
    SETSYMCODE(sym, Sprocedurep(val) ? CLOSCODE(val) : npc);
  }
}
