/*
 * Copyright (c) 2002, The EROS Group, LLC and Johns Hopkins
 * University. All rights reserved.
 * 
 * This software was developed to support the EROS secure operating
 * system project (http://www.eros-os.org). The latest version of
 * the OpenCM software can be found at http://www.opencm.org.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 * 
 * 3. Neither the name of the The EROS Group, LLC nor the name of
 *    Johns Hopkins University, nor the names of its contributors
 *    may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <opencm.h>

#define L2BS 8 /* L2BS == 8 -> BLOCK_SIZE == 256 */
#define BLOCK_SIZE (1 << L2BS)

/*
   If limit=0, this will fetch the item at index
   If limit=1, this will fetch the block that holds the item at index
   So on for limit>1 [though this hasn't been tested]
*/
static const void*
block_fetch(const ObVec* vec, uint32_t index, uint32_t limit)
{
  const void* ptr = vec->elements;
  uint32_t h = vec->height;

  assert(index < vec_size(vec));
  assert(h >= limit);

  while(h > limit)
  {
    uint32_t block_index = (index >> ((h-1) * L2BS)) % BLOCK_SIZE;

    /*
      Weird special case: if h == 1 here, then what we're looking at is
      actually a member of the vector. In this case, 0 signifies a NULL element
      in the set, rather than a sub-vector we haven't looked at yet. So we don't
      want to allocate a new subvector here.
    */
    if(((void**)ptr)[block_index] == 0 && h != 1)
    {
      uint32_t j;
      ((void**)ptr)[block_index] = GC_MALLOC(sizeof(void*) * BLOCK_SIZE);
      for(j = 0; j != BLOCK_SIZE; j++)
        ((void***)ptr)[block_index][j] = 0;
    }
    ptr = ((void**)ptr)[block_index];
    h--;
  }
  return ptr;
}

static void
vec_set(ObVec* vec, uint32_t index, const void* value)
{ 
  if(vec->height == 0)
  {
    assert(index == 0);
    vec->elements = value;
  }
  else
  {
    const void** block = (const void**)block_fetch(vec, index, 1);
    block[index % BLOCK_SIZE] = value;
  }
}

static void
vec_MaybeGrow(ObVec *vec)
{
  if(vec->size == (1 << (L2BS * vec->height)))
  {
    uint32_t j;
    const void** elem = GC_MALLOC(sizeof(void*) * BLOCK_SIZE);

    for(j = 1; j != BLOCK_SIZE; j++)
      elem[j] = 0;
    elem[0] = vec->elements;
    vec->elements = elem;
    vec->height++;
    vec->maxSize = 1 << (L2BS * vec->height);
  }
}

const void* 
vvec_fetch(const ObVec* vec, uint32_t index)
{
  return block_fetch(vec, index, 0);
}

void
obvec_append(ObVec *vec, const void *vp)
{
  vec_MaybeGrow(vec);
  vec_set(vec, vec->size++, vp);

  assert(vp == vvec_fetch(vec, vec->size-1));

  SER_MODIFIED(vec);
}

void
vec_sort_using(void *vec_ptr, int (*cmp)(const void *, const void *))
{
  ObVec* vec = (ObVec*)vec_ptr;
  ObVec* new_vec = obvec_create();
  uint32_t j; 

  SER_MODIFIED(vec);

  /* Already sorted */
  if(vec->size == 0 || vec->size == 1)
    return; 

  assert(vec->height > 0);

  {
    const uint32_t full_blocks = vec_size(vec) / BLOCK_SIZE;
    const uint32_t last_used   = vec_size(vec) % BLOCK_SIZE;
    const uint32_t total_blocks = full_blocks + (last_used ? 1 : 0);

    uint32_t* leaf_index = GC_MALLOC_ATOMIC(sizeof(uint32_t) * total_blocks);

    for(j = 0; j != total_blocks; j++)
      leaf_index[j] = 0;

    /* Sort each contigious sub-block */
    for(j = 0; j != BLOCK_SIZE*full_blocks; j += BLOCK_SIZE)
    {
      const void** block = (const void**)block_fetch(vec, j, 1);
      qsort(block, BLOCK_SIZE, sizeof(void*), cmp);
    }

    /* Sort the last (non-full) block, if it's there */
    if(last_used)
    {
      const void** block = (const void**)
        block_fetch(vec, full_blocks * BLOCK_SIZE, 1);
      qsort(block, last_used, sizeof(void*), cmp);
    }

    /* Only one block? Everything has been sorted */
    if(total_blocks == 1)
      return;

    /* now merge the various vectors */
    {
      while(1)
      {
        /* First, find the leaf that has the smallest first element */
        int min_leaf = -1, found_something = 0;
        uint32_t index;
        for(j = 0; j != total_blocks; j++)
        {
          const void* elem1, *elem2;

          uint32_t used = BLOCK_SIZE;
          /* If last_used == 0, then the vector size is a multiple of BLOCK_SIZE */
          if(j == total_blocks - 1 && last_used)
            used = last_used;

          /* Have we exhausted this leaf already? If so, skip it */
          if(leaf_index[j] == used)
            continue;

          if(min_leaf == -1) { min_leaf = j; found_something = 1; }

          elem1 = vvec_fetch(vec, BLOCK_SIZE * j + leaf_index[j]);
          elem2 = vvec_fetch(vec, BLOCK_SIZE * min_leaf + leaf_index[min_leaf]);

          if(cmp(&elem1, &elem2) < 0) { min_leaf = j; found_something = 1; }
        }

        /* we're done */
        if(!found_something)
        {
          for(j = 0; j != full_blocks; j++)
            assert(leaf_index[j] == BLOCK_SIZE);
          if(last_used > 0) /* last_used == 0? no last block */
            assert(leaf_index[full_blocks] == last_used);
          break;
        }

        assert(min_leaf >= 0);
        assert(min_leaf < total_blocks);

        index = min_leaf * BLOCK_SIZE + leaf_index[min_leaf];
        obvec_append(new_vec, vvec_fetch(vec, index));
        leaf_index[min_leaf]++;
      } /* end while(1) */
    } /* end merge loop */
  }

  for(j = 0; j != vec_size(new_vec) - 1; j++)
  {
    const void* x1 = vvec_fetch(new_vec, j);
    const void* x2 = vvec_fetch(new_vec, j+1);
    int res = cmp(&x1, &x2);
    assert(res <= 0);
  }

  /* Copy the internal representation over */
  assert(vec->height == new_vec->height);
  assert(vec->maxSize == new_vec->maxSize);
  assert(vec->size == new_vec->size);

  vec->elements = new_vec->elements;
}

int
vec_bsearch(const void *vec_ptr, const void *s,
            int (*cmp)(const void *, const void *))
{
  const ObVec* vec = (const ObVec*)vec_ptr;
  uint32_t j;
  uint32_t full_blocks, last_block_used;
  int offset = 0;
  char **entry = 0;

  /* Handle some special cases first */
  if(vec->size == 0) return -1;

  if(vec->height == 0)
  {
    assert(vec->size == 1);
    if(cmp(s, &(vec->elements)) == 0) return 0;
    else                            return -1;
  }

  assert(vec->height > 0);

  full_blocks = vec_size(vec) / BLOCK_SIZE;
  last_block_used = vec_size(vec) % BLOCK_SIZE;

  /* Do a binary search on each full leaf */
  for(j = 0; j != full_blocks; j++)
  {
    const void** leaf = (const void**)block_fetch(vec, j*BLOCK_SIZE, 1);

    entry = xbsearch(s, leaf, BLOCK_SIZE, sizeof(char*), cmp);

    /*
       If it's not in this leaf, then every element of this leaf must be before
       the element we're searching for.
    */
    if(entry == 0)
      offset += BLOCK_SIZE;
    else
    {
      offset += entry - (char **)leaf;
      break; /* found it */
    }
  }

  /* Didn't find it in the first n blocks: try the very last one */
  if(entry == 0 && last_block_used)
  {
    const void** leaf = (const void**)block_fetch(vec, full_blocks*BLOCK_SIZE, 1);
    entry = xbsearch(s, leaf, last_block_used, sizeof(char*), cmp);
    if(entry)
      offset += entry - (char **)leaf;
  }

  if(entry == 0)
    offset = -1;

  return offset;
}

void
vec_remove(void *vd_vec, uint32_t ndx)
{
  uint32_t i;
  ObVec *vec = vd_vec;
  
  assert (ndx < vec_size(vec));
  
  for (i = ndx; i < (vec->size - 1); i++)
    vec_set(vec, i, vvec_fetch(vec, i+1));

  vec_set(vec, vec->size-1, 0);
  
  vec->size--;
  SER_MODIFIED(vec);
}

uint32_t 
vec_size(const void* vd_vec)
{
  const ObVec *vec = vd_vec;
  return vec->size;
}

static void 
init_vector(void *vec_ptr)
{
  ObVec* vec = vec_ptr;
  vec->size = 0;
  vec->maxSize = 1;
  vec->height = 0;
  vec->elements = 0;
}

/* Things below this point are not affected by changes to the internal
   representation */
StrVec *
strvec_create(void)
{
  StrVec *vec = (StrVec *) GC_MALLOC(sizeof(StrVec));

  ser_init(vec, &StrVec_SerType, StrVec_SerType.ver);
  SER_MODIFIED(vec);

  init_vector(vec);

  return vec;
}

TnVec *
tnvec_create(void)
{
  TnVec *vec = (TnVec *) GC_MALLOC(sizeof(TnVec));

  ser_init(vec, &TnVec_SerType, TnVec_SerType.ver);
  SER_MODIFIED(vec);

  init_vector(vec);

  return vec;
}

ObVec *
obvec_create()
{
  ObVec *vec = (ObVec *) GC_MALLOC(sizeof(ObVec));

  ser_init(vec, &ObVec_SerType, ObVec_SerType.ver);
  SER_MODIFIED(vec);

  init_vector(vec);

  return vec;
}

ObVec *
obvec_shallow_copy(ObVec *vec)
{
  int i;
  ObVec *newVec = obvec_create();
  for (i = 0; i < vec_size(vec); i++)
    obvec_append(newVec, vec_fetch(vec, i, void *));
  return newVec;
}

void
strvec_append(StrVec *vec, const char *s)
{
  obvec_append((ObVec *) vec, s);
}

void
tnvec_append(TnVec *vec, const char *s)
{
  obvec_append((ObVec *) vec, s);
}

void
obvec_serialize(SDR_stream *strm, const void *vp)
{
  uint32_t i;
  const ObVec *vec = vp;
  uint32_t size = vec_size(vec);

  sdr_w_u32("len", strm, size);

  for (i = 0; i < size; i++)
    sdr_write("elem", strm, vec_fetch(vec, i, Serializable *));
}

void *
obvec_deserialize(const DeserializeInfo *di, SDR_stream *strm)
{
  ObVec *vec = obvec_create();
  uint32_t size, i;
  
  ser_init(vec, &ObVec_SerType, di->ver);
  SER_MODIFIED(vec);

  size = sdr_r_u32("len", strm);
  
  for (i = 0; i < size; i++) {
    void *elem = sdr_read("elem", strm);
    obvec_append(vec, elem);
  }

  return vec;
}

void
obvec_mark(Repository *r, const void *container,
	   const void *ob, rbtree *memObs)
{
  /* ObVecs are serialized inline, so do not mark the truenames of
     their member elements, but need to mark the elements reachable
     from those. */
  uint32_t i;
  const ObVec *vec = ob;

  assert(container != ob);

  for (i = 0; i < vec_size(vec); i++)
    ser_mark(r, container, vec_fetch(vec, i, Serializable *), memObs);
}

void
obvec_show(const void *v)
{
  const ObVec *vec = v;

  report(0, "<obvec[%u]>\n", vec_size(vec));
}

OC_bool
obvec_check(const void *v)
{
  return TRUE;
}

void
strvec_serialize(SDR_stream *strm, const void *v)
{
  const StrVec *vec = v;
  uint32_t i;
  uint32_t size = vec_size(vec);

  sdr_w_u32("len", strm, size);

  for (i = 0; i < size; i++)
    sdr_w_string("elem", strm, vec_fetch(vec, i, const char *));
}

void *
strvec_deserialize(const DeserializeInfo *di, SDR_stream *strm)
{
  StrVec *vec = strvec_create();
  uint32_t size, i;
  
  ser_init(vec, &StrVec_SerType, di->ver);
  SER_MODIFIED(vec);

  size = sdr_r_u32("len", strm);
  
  for (i = 0; i < size; i++) {
    char *elem = sdr_r_string("elem", strm);
    strvec_append(vec, elem);
  }

  return vec;
}

void
strvec_mark(Repository *r, const void *container, 
	    const void *ob, rbtree *memObs)
{
  report(0, "strvec_mark called -- not clear what to do\n");
}

void
tnvec_mark(Repository *r, const void *container,
	   const void *ob, rbtree *memObs)
{
  const TnVec *vec = ob;
  uint32_t i;
  uint32_t size = vec_size(vec);

  assert(container == ob);

  for (i = 0; i < size; i++)
    mark_addmark(container, memObs, vec_fetch(vec, i, const char *));
}

void
tnvec_serialize(SDR_stream *strm, const void *v)
{
  const StrVec *vec = v;
  uint32_t i;
  uint32_t size = vec_size(vec);
  sdr_w_u32("len", strm, size);

  for (i = 0; i < size; i++)
    sdr_w_obname("elem", strm, vec_fetch(vec, i, const char *));
}

void *
tnvec_deserialize(const DeserializeInfo *di, SDR_stream *strm)
{
  StrVec *vec = tnvec_create();
  uint32_t size, i;
  
  ser_init(vec, &TnVec_SerType, di->ver);
  SER_MODIFIED(vec);

  size = sdr_r_u32("len", strm);
  
  for (i = 0; i < size; i++) {
    char *elem = sdr_r_obname("elem", strm);
    tnvec_append(vec, elem);
  }

  return vec;
}

OC_bool
strvec_check(const void *v)
{
  return TRUE;
}

void
strvec_show(const void *v)
{
  const StrVec *vec = v;

  report(0, "<strvec[%u]>\n", vec_size(vec));
}

OC_bool
tnvec_check(const void *v)
{
  return TRUE;
}

void
tnvec_show(const void *v)
{
  const TnVec *vec = v;

  report(0, "<tnvec[%u]>\n", vec_size(vec));
}

char *
strvec_flatten(StrVec *vec)
{
  uint32_t i;
  uint32_t cumlen = 0;
  char *s;
  uint32_t size = vec_size(vec);

  for (i = 0; i < size; i++) {
    cumlen = cumlen + strlen(vec_fetch(vec, i, const char *));
  }

  cumlen += 1;			/* for trailing null */

  s = GC_MALLOC_ATOMIC(cumlen);
  cumlen = 0;

  for (i = 0; i < size; i++) {
    uint32_t len = strlen(vec_fetch(vec, i, const char *));
    strcpy(&s[cumlen], vec_fetch(vec, i, const char *));
    cumlen += len;
  }

  return s;
}

static int
cmp_ent(const void *e1, const void *e2)
{
  const Serializable *s1 = *((const Serializable **) e1);
  const Serializable *s2 = *((const Serializable **) e2);

  assert(s1 != 0);
  assert(s2 != 0);

  return strcmp(ser_getTrueName(s1), ser_getTrueName(s2));
}

static int
cmp_name(const void *v1, const void *v2)
{
  const char *s1 = *((const char **) v1);
  const char *s2 = *((const char **) v2);

  assert(s1 != 0);
  assert(s2 != 0);
  return strcmp(s1, s2);
}

void
strvec_sort(StrVec *vec)
{
  SER_MODIFIED(vec);
  vec_sort_using(vec, cmp_name);
}

void
tnvec_sort(StrVec *vec)
{
  SER_MODIFIED(vec);
  vec_sort_using(vec, cmp_name);
}

void
obvec_sort(ObVec *vec)
{
  SER_MODIFIED(vec);
  vec_sort_using(vec, cmp_ent);
}

static int
strvec_keycmp(const void *vkey, const void *vmember)
{
  const char *key = (const char *) vkey;
  const char **member = (const char **) vmember;
  return strcmp(key, (*member));
}

void
strvec_bremove(StrVec *vec, const char *s)
{
  int entry = strvec_bsearch(vec, s);
  if(entry >= 0)
  {
    vec_remove(vec, entry);
    SER_MODIFIED(vec);
  }
}

int
strvec_bsearch(StrVec *vec, const char *s)
{
  return vec_bsearch(vec, s, strvec_keycmp);
}

void
tnvec_bremove(TnVec *vec, const char *s)
{
  strvec_bremove(vec, s);
}

int
tnvec_bsearch(StrVec *vec, const char *s)
{
  return strvec_bsearch(vec, s);
}
