
#include "OVOneToOne.h"
#include "OVHeapArray.h"
#include "ov_utility.h"

#define HASH(value,mask) (((value^(value>>24))^((value>>8)^(value>>16)))&mask)


/* FYI: "up" stands for UniquePair -- a precursor to OneToOne */

typedef struct {
  int active;
  ov_word forward_value, reverse_value;
  ov_size forward_next, reverse_next;
} up_element;

struct _OVOneToOne {
  OVHeap *heap;
  ov_uword mask;
  ov_size size, n_inactive;
  ov_word next_inactive;
  up_element *elem;
  ov_word *forward;
  ov_word *reverse;
};

OVstatus OVOneToOne_Init(OVOneToOne * up, OVHeap * heap)
{
  ov_utility_zero_range(up, up + 1);
  up->heap = heap;
  return_OVstatus_SUCCESS;
}

OVOneToOne *OVOneToOne_New(OVHeap * heap)
{
  OVOneToOne *up;
  up = OVHeap_ALLOC(heap, OVOneToOne);
  up->heap = heap;
  return up;
}

void OVOneToOne_Purge(OVOneToOne * up)
{
  if(up) {
    OVHeapArray_FREE_AUTO_NULL(up->elem);
    OVHeap_FREE_AUTO_NULL(up->heap, up->forward);
    OVHeap_FREE_AUTO_NULL(up->heap, up->reverse);
  }
}

void OVOneToOne_Del(OVOneToOne * up)
{
  if(up) {
    OVOneToOne_Purge(up);
    OVHeap_FREE_AUTO_NULL(up->heap, up);
  }
}

void OVOneToOne_Reset(OVOneToOne * up)
{
  OVOneToOne_Purge(up);
  OVOneToOne_Init(up, up->heap);
}

void OVOneToOne_Dump(OVOneToOne * up)
{
  ov_uword a;
  ov_boolean empty = OV_TRUE;
  if(up && up->mask) {
    for(a = 0; a <= up->mask; a++) {
      if(up->forward[a] || up->reverse[a]) {
        fprintf(stderr,
                " OVOneToOne_Dump: Hashes forward[0x%02x]->%d    reverse[0x%02x]->%d\n",
                (unsigned int) a, (int) up->forward[a],
                (unsigned int) a, (int) up->reverse[a]);
        empty = OV_FALSE;
      }
    }

    for(a = 0; a < up->size; a++)
      if(up->elem[a].active) {
        fprintf(stderr,
                " OVOneToOne_Dump: Elements %d:    %d (->%d)    %d (->%d)\n",
                (int) a + 1,
                (int) up->elem[a].forward_value,
                (int) up->elem[a].forward_next,
                (int) up->elem[a].reverse_value, (int) up->elem[a].reverse_next);
        empty = OV_FALSE;
      }
  }
  if(empty) {
    fprintf(stderr, " OVOneToOne_Dump: Empty. \n");
  }
}

static void Reload(OVOneToOne * up)
{                               /* assumes hash tables are clean and initialized to zero */
#ifdef DEBUG_UP
  fprintf(stderr, "Reload-Debug: entered\n");
#endif
  ov_uword mask = up->mask;

  if(up->elem && mask) {
    {
      up_element *elem = up->elem;
      ov_uword a;
      for(a = 0; a < up->size; a++) {
        if(elem->active) {
          elem->forward_next = 0;       /* 0 is the sentinel for end of list */
          elem->reverse_next = 0;
        }
        elem++;
      }
    }

    {
      ov_uword a;
      ov_word *forward = up->forward;
      ov_word *reverse = up->reverse;
      up_element *elem = up->elem;
      {
        ov_word fwd, rev;
        ov_word fwd_val;
        ov_word rev_val;
        for(a = 0; a < up->size; a++) {
          if(elem->active) {
            fwd_val = elem->forward_value;
            rev_val = elem->reverse_value;
            fwd = HASH(fwd_val, mask);
            rev = HASH(rev_val, mask);
            elem->forward_next = forward[fwd];
            forward[fwd] = a + 1;       /* NOTE: 1 based indices */
            elem->reverse_next = reverse[rev];
            reverse[rev] = a + 1;
          }
          elem++;
        }
      }
    }
  }
#ifdef DEBUG_UP
  {
    ov_uword a;
    for(a = 0; a <= up->mask; a++) {
      fprintf(stderr, "Reload-Debug: forward[%d]=%d, reverse[%d]=%d\n",
              a, up->forward[a], a, up->reverse[a]);
    }
  }
#endif

}

OVreturn_word OVOneToOne_GetReverse(OVOneToOne * up, ov_word reverse_value)
{
  if(!up) {
    OVreturn_word result = { OVstatus_NULL_PTR };
    return result;
  } else {
    if(up->mask) {
      ov_word hash = HASH(reverse_value, up->mask);
      up_element *elem = up->elem;
      ov_word index = up->reverse[hash];
      up_element *cur_elem = elem + (index - 1);

      while(index) {
        if(cur_elem->reverse_value == reverse_value) {
          OVreturn_word result = { OVstatus_SUCCESS };
          result.word = cur_elem->forward_value;
          return result;
        }
        index = cur_elem->reverse_next;
        cur_elem = elem + (index - 1);
      }
    }
    {
      OVreturn_word result = { OVstatus_NOT_FOUND };
      return result;
    }
  }
}

OVreturn_word OVOneToOne_IterateForward(OVOneToOne * up, ov_word * hidden)
{
  if(!up) {
    OVreturn_word result = { OVstatus_NULL_PTR };
    return result;
  } else {
    OVreturn_word result = { OVstatus_YES };
    unsigned int a;
    up_element *cur_elem = up->elem + (*hidden);
    for(a = *hidden; a < up->size; a++) {
      if(cur_elem->active) {
        result.word = cur_elem->forward_value;
        *hidden = a + 1;
        return result;
      }
      cur_elem++;
    }
    *hidden = 0;
    result.status = OVstatus_NO;
    return result;
  }
}

OVreturn_word OVOneToOne_GetForward(OVOneToOne * up, ov_word forward_value)
{
  if(!up) {
    OVreturn_word result = { OVstatus_NULL_PTR };
    return result;
  } else {
    ov_uword mask = up->mask;
    if(mask) {
      ov_word hash = HASH(forward_value, mask);
      up_element *elem = up->elem;
      ov_word index = up->forward[hash];
      up_element *cur_elem = elem + (index - 1);
      while(index) {
        if(cur_elem->forward_value == forward_value) {
          OVreturn_word result = { OVstatus_SUCCESS };
          result.word = cur_elem->reverse_value;
          return result;
        }
        index = cur_elem->forward_next;
        cur_elem = elem + (index - 1);
      }
    }
    {
      OVreturn_word result = { OVstatus_NOT_FOUND };
      return result;
    }
  }
}

static OVstatus Recondition(OVOneToOne * up, ov_uword size, int force)
{
  if(!up) {
    return_OVstatus_NULL_PTR;
  } else {
    ov_uword mask = up->mask;
#ifdef DEBUG_UP
    fprintf(stderr, "Recondition-Debug: entered for size %d.\n", size);
#endif
    if((size > mask) || ((size << 2) < mask) || force) {

      while((size << 2) < mask) {
        mask = mask >> 1;
        if(mask < 2)
          break;
      }

      while(size > mask) {
        mask = (mask << 1) + 1;
      }

#ifdef DEBUG_UP
      fprintf(stderr, "Recondition-Debug: mask %d\n", mask);
#endif
      {
        if(!up->elem) {
          up->elem = OVHeapArray_CALLOC(up->heap, up_element, size);
          if(!up->elem) {
            return_OVstatus_OUT_OF_MEMORY;
          }
        }
        if(mask != up->mask) {
          ov_word *tmp_forward = OVHeap_CALLOC(up->heap, ov_word, mask + 1);
          ov_word *tmp_reverse = OVHeap_CALLOC(up->heap, ov_word, mask + 1);
          if(!(tmp_forward && tmp_reverse)) {   /* validate */
            OVHeap_FREE_AUTO_NULL(up->heap, tmp_forward);
            OVHeap_FREE_AUTO_NULL(up->heap, tmp_reverse);
            /* being unable to condition is not an error */
          } else {
            /* impossible to fail after here... */
            OVHeap_FREE_AUTO_NULL(up->heap, up->forward);
            OVHeap_FREE_AUTO_NULL(up->heap, up->reverse);
            up->forward = tmp_forward;
            up->reverse = tmp_reverse;
            up->mask = mask;
          }
        } else {
          ov_utility_zero_range(up->forward, up->forward + (up->mask + 1));
          ov_utility_zero_range(up->reverse, up->reverse + (up->mask + 1));
        }
        Reload(up);
      }
    }
  }
  return_OVstatus_SUCCESS;
}

OVstatus OVOneToOne_Pack(OVOneToOne * up)
{
  if(!up) {
    return_OVstatus_NULL_PTR;
  } else {
    if(up->n_inactive && up->elem) {
      ov_uword new_size = 0;
      up_element *src = up->elem, *dst = up->elem;
      ov_uword a;

      for(a = 0; a < up->size; a++) {
        if(src->active) {
          if(src > dst) {
            *dst = *src;
          }
          dst++;
          new_size++;
        }
        src++;
      }
      up->n_inactive = 0;
      up->next_inactive = 0;
      if(new_size > 0 && new_size < up->size) {
        if(!OVHeapArray_SET_SIZE(up->elem, up_element, new_size))
          ov_utility_zero_range(up->elem + new_size, up->elem + up->size);
      }
      up->size = new_size;
      return Recondition(up, new_size, OV_TRUE);
    }
    return_OVstatus_SUCCESS;
  }
}

OVreturn_size OVOneToOne_GetSize(OVOneToOne * up)
{
  if(!up) {
    OVreturn_size result = { OVstatus_NULL_PTR };
    return result;
  } else {
    OVreturn_size result = { OVstatus_SUCCESS };
    result.size = up->size - up->n_inactive;
    return result;
  }
}

OVstatus OVOneToOne_DelReverse(OVOneToOne * up, ov_word reverse_value)
{
  if(!up) {
    return_OVstatus_NULL_PTR;
  } else {
    ov_word mask = up->mask;
    if(mask) {
      ov_word rev_hash = HASH(reverse_value, mask);
      ov_word rev = up->reverse[rev_hash];

      if(!rev) {
        return_OVstatus_NOT_FOUND;
      } else {
        up_element *rev_elem = NULL;
        up_element *elem = up->elem;
        ov_word rev_last = 0;

        while(rev) {
          rev_elem = elem + (rev - 1);
          if(rev_elem->reverse_value == reverse_value)
            break;
          rev_last = rev;
          rev = rev_elem->reverse_next;
        }

        if(rev_elem) {
          ov_word rev_fwd_val = rev_elem->forward_value;
          ov_word fwd_hash = HASH(rev_fwd_val, mask);
          ov_word fwd = up->forward[fwd_hash];
          ov_word fwd_last = 0;
          up_element *fwd_elem = NULL;

          while(fwd) {
            fwd_elem = elem + (fwd - 1);
            if(fwd_elem == rev_elem)
              break;
            fwd_last = fwd;
            fwd = fwd_elem->forward_next;
          }

          if(rev && (rev == fwd)) {

            /* excise element */

            if(rev_last)
              up->elem[rev_last - 1].reverse_next = rev_elem->reverse_next;
            else
              up->reverse[rev_hash] = rev_elem->reverse_next;

            if(fwd_last)
              up->elem[fwd_last - 1].forward_next = fwd_elem->forward_next;
            else
              up->forward[fwd_hash] = fwd_elem->forward_next;

            /* store as inactive */

            rev_elem->active = OV_FALSE;
            rev_elem->forward_next = up->next_inactive;
            up->next_inactive = rev;
            up->n_inactive++;
            if(up->n_inactive > (up->size >> 1))        /* over half of bits are inactive */
              OVOneToOne_Pack(up);
            return_OVstatus_SUCCESS;
          }
        }
      }
    }
    return_OVstatus_NOT_FOUND;
  }
}

OVstatus OVOneToOne_DelForward(OVOneToOne * up, ov_word forward_value)
{
  if(!up) {
    return_OVstatus_NULL_PTR;
  } else {
    ov_word mask = up->mask;
    if(mask) {
      ov_word fwd_hash = HASH(forward_value, mask);
      ov_word fwd = up->forward[fwd_hash];
      if(!fwd) {
        return_OVstatus_NOT_FOUND;
      } else {
        up_element *fwd_elem = NULL;
        up_element *elem = up->elem;
        ov_word fwd_last = 0;

        while(fwd) {
          fwd_elem = elem + (fwd - 1);
          if(fwd_elem->forward_value == forward_value)
            break;
          fwd_last = fwd;
          fwd = fwd_elem->forward_next;
        }

        if(fwd_elem) {
          ov_word fwd_rev_val = fwd_elem->reverse_value;
          ov_word rev_hash = HASH(fwd_rev_val, mask);
          ov_word rev = up->reverse[rev_hash];
          ov_word rev_last = 0;
          up_element *rev_elem = NULL;

          while(rev) {
            rev_elem = elem + (rev - 1);
            if(rev_elem == fwd_elem)
              break;
            rev_last = rev;
            rev = rev_elem->reverse_next;
          }

          if(fwd && (fwd == rev)) {

            /* excise elements */

            if(fwd_last)
              up->elem[fwd_last - 1].forward_next = fwd_elem->forward_next;
            else
              up->forward[fwd_hash] = fwd_elem->forward_next;

            if(rev_last)
              up->elem[rev_last - 1].reverse_next = rev_elem->reverse_next;
            else
              up->reverse[rev_hash] = rev_elem->reverse_next;

            /* store as inactive */

            fwd_elem->active = OV_FALSE;
            fwd_elem->forward_next = up->next_inactive;
            up->next_inactive = fwd;
            up->n_inactive++;
            if(up->n_inactive > (up->size >> 1))        /* over half of bits are inactive */
              OVOneToOne_Pack(up);
            return_OVstatus_SUCCESS;
          }
        }
      }
    }
    return_OVstatus_NOT_FOUND;
  }
}

void OVOneToOne_Stats(OVOneToOne * up)
{
  if(up && up->mask) {
    int max_len = 0;
    ov_uword a;
    for(a = 0; a < up->mask; a++) {
      {
        ov_word index = up->forward[a];
        up_element *elem = up->elem;
        int cnt = 0;
        if(index) {
          up_element *cur_elem;
          while(index) {
            cur_elem = elem + (index - 1);
            index = cur_elem->forward_next;
            cnt++;
          }
          if(cnt > max_len)
            max_len = cnt;
        }
      }

      {
        ov_word index = up->reverse[a];
        up_element *elem = up->elem;
        int cnt = 0;
        if(index) {
          up_element *cur_elem;
          while(index) {
            cur_elem = elem + (index - 1);
            index = cur_elem->reverse_next;
            cnt++;
          }
          if(cnt > max_len)
            max_len = cnt;
        }
      }

    }
    fprintf(stderr, " OVOneToOne_Stats: MaxLen=%d ", max_len);
    fprintf(stderr, "active=%d n_inactive=%d ", (int)( up->size - up->n_inactive),
            (int) up->n_inactive);
    fprintf(stderr, "mask=0x%x n_alloc=%lu\n", (unsigned int) up->mask,
            (unsigned long) OVHeapArray_GET_SIZE(up->elem));
  }
}

OVstatus OVOneToOne_Set(OVOneToOne * up, ov_word forward_value, ov_word reverse_value)
{
  if(!up) {
    return_OVstatus_NULL_PTR;
  } else {
    ov_word mask = up->mask;
    ov_word fwd_hash = HASH(forward_value, mask);
    ov_word rev_hash = HASH(reverse_value, mask);
    up_element *fwd_elem = NULL;
    up_element *rev_elem = NULL;
    ov_word fwd;
    ov_word rev;
    if(!mask) {
      fwd = 0;
      rev = 0;
    } else {

      fwd = up->forward[fwd_hash];
      rev = up->reverse[rev_hash];

#ifdef DEBUG_OVOneToOne
      fprintf(stderr, "OVOneToOneSet-Debug: set %d,%d\n", forward_value, reverse_value);
      fprintf(stderr, "OVOneToOneSet-Debug: fwd_hash %d rev_hash %d mask %d size %d\n",
              fwd_hash, rev_hash, up->mask, up->size);
      fprintf(stderr, "OVOneToOneSet-Debug: before search fwd rev %d %d\n", fwd, rev);
#endif

      {                         /* find elements if they exist, and detect erroneous conditions */

        up_element *elem = up->elem;

#ifdef DEBUG_OVOneToOne
        {
          int a;
          for(a = 0; a < up->size; a++) {
            fprintf(stderr, "OVOneToOneSet-Debug: on entry %d forward_next: %d\n",
                    a + 1, elem[a].forward_next);
          }

          for(a = 0; a <= up->mask; a++) {
            fprintf(stderr,
                    "OVOneToOneSet-Debug: on entry %d forward hash %d: reverse_hash: %d\n",
                    a, up->forward[a], up->reverse[a]);
          }
        }
#endif

        while(fwd) {
          fwd_elem = elem + (fwd - 1);
          if(fwd_elem->forward_value == forward_value)
            break;
          fwd = fwd_elem->forward_next;
        }
        while(rev) {
          rev_elem = elem + (rev - 1);
          if(rev_elem->reverse_value == reverse_value)
            break;
          rev = rev_elem->reverse_next;
        }
      }

      if((fwd && (!rev)) || (rev && (!fwd))) {
        return_OVstatus_DUPLICATE;
      }
    }
    if(!(fwd || rev)) {
      ov_size new_index;
      /* new pair */
#ifdef DEBUG_OVOneToOne
      fprintf(stderr, "OVOneToOneSet-Debug: New pair.\n");
#endif
      if(up->n_inactive) {
        new_index = up->next_inactive;
        up->next_inactive = up->elem[new_index - 1].forward_next;
        up->n_inactive--;
      } else {
        if(up->elem && (!OVHeapArray_CHECK(up->elem, up_element, up->size))) {
          return_OVstatus_OUT_OF_MEMORY;
        } else {
          OVstatus result;
          if(OVreturn_IS_ERROR(result = Recondition(up, up->size + 1, OV_FALSE))) {
            return result;
          } else {
            /* guaranteed to succeed past this point, so we can increase size */
            new_index = ++up->size;
          }
        }
      }
      {
        up_element *elem = up->elem + (new_index - 1);
        elem->forward_value = forward_value;
        elem->reverse_value = reverse_value;
        elem->active = OV_TRUE;

        /* regenerate new hashes */
        mask = up->mask;
        fwd_hash = HASH(forward_value, mask);
        rev_hash = HASH(reverse_value, mask);

        {
          ov_word *forward_start_index = up->forward + fwd_hash;
          ov_word *reverse_start_index = up->reverse + rev_hash;

          elem->forward_next = *forward_start_index;
          *forward_start_index = new_index;     /* note the +1 offset */
          elem->reverse_next = *reverse_start_index;
          *reverse_start_index = new_index;     /* note the +1 offset */
        }

#ifdef DEBUG_OVOneToOne
        {
          int a;
          for(a = 0; a <= up->mask; a++) {
            fprintf(stderr, "OVOneToOneSet-Debug: forward[%d]=%d, reverse[%d]=%d\n",
                    a, up->forward[a], a, up->reverse[a]);
          }
        }
#endif

      }

    } else if(fwd_elem != rev_elem) {
      return_OVstatus_MISMATCH;
    } else {
      return_OVstatus_NO_EFFECT;
      /* exists and matched, so do nothing */
    }
  }
  return_OVstatus_SUCCESS;
}
