/**
 *    WHAM - high-throughput sequence aligner
 *    Copyright (C) 2011  WHAM Group, University of Wisconsin
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*	$Id: interval.cpp 157 2012-07-25 05:58:09Z yinan $ */

#include <math.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "interval.h"
#include "error.h"

IntervalTree::IntervalTree() {

}

/*
 *	IntervalTree::IntervalTree
 *	allocate space for specified size of the interval tree.
 */
IntervalTree::IntervalTree(uint32 num, uint32 len) {
  uint32 k;

  lenNSeg = len;

  /* fanout of internal node and leaf node */
  fi = CACHE_LINE_SIZE / sizeof(IntervalIEntry);
  fl = CACHE_LINE_SIZE / sizeof(IntervalLEntry);

  k = (uint32) ceil(double(num) / fl);
  numLEntry = k * fl;
  numIEntry = 0;
  numLevel = 1;
  do {
    numLevel++;
    k = (uint32) ceil(double(k) / fi);
    numIEntry += k * fi;
  } while (k > 1);

  /*	allocate the space */
  ipool = new IntervalIEntry[numIEntry];
  lpool = new IntervalLEntry[numLEntry];
  level = new IntervalLevel[numLevel];

  curLNode = 0;
  curINode = 0;

  /* initialize the current entry in each level */
  level[numLevel - 1].curNode = 0;
  level[numLevel - 1].curEntry = 0;
  curLNode += fl;
  for (int i = 0; i < numLevel - 1; i++) {
    level[i].curNode = i * fi;
    level[i].curEntry = 0;
    curINode += fi;
  }
}

/*
 *	IntervalTree::append
 *	append a new entry (a pair of values) to the end of the leaf 
 *	level of interval tree.
 */

int IntervalTree::append(uint32 key, uint32 len, uint32 sid, uint32 offset) {
  uint32 curEntry;
  uint32 l = numLevel - 1;

  /*	if the current node is full */
  if (level[l].curEntry == fl) {
    level[l].curEntry = 0;
    level[l].curNode = curLNode;
    curLNode += fl;
  }

  /*	copy the value of the new entry */
  curEntry = level[l].curNode + level[l].curEntry;
  lpool[curEntry].key = key;
  lpool[curEntry].len = len;
  lpool[curEntry].sid = sid;
  lpool[curEntry].offset = offset;

  /*	update the current entry */
  level[l].curEntry++;

  /*	if the current node is full */
  if (level[l].curEntry == fl) {
    /*
     *	insert the current node into the above level as
     *	a new internal entry
     */
    append(key, level[l].curNode, l - 1);
  }

  return SUCCESS;
}

/*
 *	IntervalTree::append
 *	append a new entry (a pair of values) to the end of a internal 
 *	level of interval tree.
 */

int IntervalTree::append(uint32 key, uint32 offset, uint32 l) {
  uint32 curEntry;

  /*	if the current node is full */
  if (level[l].curEntry == fi) {
    level[l].curEntry = 0;
    level[l].curNode = curINode;
    curINode += fi;
  }

  /*	copy the value of the new entry */
  curEntry = level[l].curNode + level[l].curEntry;
  ipool[curEntry].key = key;
  ipool[curEntry].offset = offset;

  /*	update the current entry */
  level[l].curEntry++;

  /*	if the current node is full */
  if (level[l].curEntry == fi) {
    /*
     *	insert the current node into the above level as
     *	a new internal entry
     */
    if (l > 0)
      append(key, level[l].curNode, l - 1);
  }

  return SUCCESS;
}

/*	IntervalTree::flush
 *	insert the last entry and flush out the interval tree. Insert 
 *	the last node of the leaf level into its parent level.
 */
int IntervalTree::flush(uint32 key, uint32 len, uint32 sid, uint32 offset) {
  uint32 i;
  uint32 curEntry;
  uint32 l = numLevel - 1;

  /*	insert the last entry */
  append(key, len, sid, offset);

  /* set all remaining keys the same as the last key */
  for (i = level[l].curEntry; i < fl; i++) {
    curEntry = level[l].curNode + i;
    lpool[curEntry].key = key;
    lpool[curEntry].len = len;
    lpool[curEntry].sid = sid;
    lpool[curEntry].offset = offset;
  }

  /*
   *	insert the current node to parent level, and flush the
   *	parent level.
   */
  flush(key, level[l].curNode, l - 1);

  return 1;
}

/*	IntervalTree::flush
 *	Insert the last entry and flush out the interval tree. Insert 
 *	the last node of the current internal level into its parent 
 *	level, if the current level is not the root level.
 */
int IntervalTree::flush(uint32 key, uint32 offset, uint32 l) {
  uint32 i;
  uint32 curEntry;

  /*	insert the last entry */
  append(key, offset, l);

  /* set all remaining keys the same as the last key */
  for (i = level[l].curEntry; i < fi; i++) {
    curEntry = level[l].curNode + i;
    ipool[curEntry].key = key;
    ipool[curEntry].offset = offset;
  }

  /*
   *	insert the current node to parent level, and flush the
   *	parent level, if the current level is not the root level.
   */
  if (l > 0)
    flush(key, level[l].curNode, l - 1);

  return 1;
}

/*
 *	IntervalTree::lookup
 *	This function is used to return the offset in the compact sequence 
 *	given an offset in the original sequence. To do this, we search the 
 *	interval tree top down, find the entry with the greatest original 
 *	sequence offset that is less or equal to the search key. The compact 
 *	sequence offset of the found entry is returned.
 */
int IntervalTree::lookup(uint32 skey, uint32 & sid, uint32 & offset) {
  uint32 i, id = 0;
  uint32 key, low, high, mid, entry;

  key = skey + lenNSeg;

  /*
   *	search the internal nodes starting from the root node. With the
   *	node in each level, we find the entry with the greatest original
   *	sequence offset that is less or equal to the search key. Following
   *	the offset field of the found entry, we go to the next level.
   */
  for (i = 0; i < numLevel - 1; i++) {
    high = fi - 1;
    low = 0;

    /* binary search */
    while (high > low) {
      mid = low + ((high - low) / 2);

      if (key >= ipool[id + mid].key + 1)
        low = mid + 1;
      else
        high = mid;
    }

    id = ipool[id + low].offset;
  }

  high = fl - 1;
  low = 0;

  /* binary search in the leaf level */
  while (high > low) {
    mid = low + ((high - low) / 2);

    if (key >= lpool[id + mid].key + 1)
      low = mid + 1;
    else
      high = mid;
  }

  /* find the leaf entry */
  entry = id + low;
  if (key < lpool[id + low].key)
    entry--;
  sid = lpool[entry].sid;

  /*
   *	we compute the distance between the searched position
   *	and the last N segment position in compact sequence,
   *	add this distance to the last N segment position in the
   *	original sequence.
   */
  offset = key - lpool[entry].key + lpool[entry].offset;
  offset = offset >= lenNSeg ? offset - lenNSeg : 0;

  return 1;
}

/*
 *	IntervalTree::save
 *	save the in-memory interval tree to disk.
 */
int IntervalTree::save(char * path) {
  int ret;
  char fname[256];
  FILE * file;

  if (strlen(path) > 240)
    return ERR_PARA;

  sprintf(fname, "%s.interval.whm", path);
  file = fopen(fname, "wb");
  if (file == NULL)
  {
    elog(DEBUG1, "failed to open file: %s\n", fname);
    return ERR_PARA;
  }

  ret = fwrite(this, sizeof(IntervalTree), 1, file);
  if (ret != 1) {
    elog(DEBUG1, "ERROR: write head data file.\n");
    return ERR_FILE;
  }

  ret = fwrite(lpool, sizeof(IntervalLEntry), numLEntry, file);
  if (ret != numLEntry) {
    elog(DEBUG1, "ERROR: write leaf entry.\n");
    return ERR_FILE;
  }

  ret = fwrite(ipool, sizeof(IntervalIEntry), numIEntry, file);
  if (ret != numIEntry) {
    elog(DEBUG1, "ERROR: write interval entry.\n");
    return ERR_FILE;
  }

  ret = fflush(file);
  if (ret != 0) {
    elog(DEBUG1, "ERROR: write head data file.\n");
    return ERR_FILE;
  }

  ret = fclose(file);
  if (ret != 0) {
    return ERR_FILE;
  }

  return SUCCESS;
}

/*
 *	IntervalTree:load
 *	load the on-disk interval tree into memory.
 */
int IntervalTree::load(char * path) {
  int ret;
  char fname[256];
  FILE * file;

  if (strlen(path) > 240)
    return ERR_PARA;

  sprintf(fname, "%s.interval.whm", path);
  file = fopen(fname, "rb");
  if (file == NULL
    )
    return ERR_PARA;

  ret = fread(this, sizeof(IntervalTree), 1, file);
  if (ret != 1) {
    elog(DEBUG1, "ERROR: read interval tree structure data file.\n");
    return ERR_FILE;
  }

  lpool = new IntervalLEntry[numLEntry];
  if (lpool == NULL
    )
    return ERR_MEM;

  ret = fread(lpool, sizeof(IntervalLEntry), numLEntry, file);
  if (ret != numLEntry) {
    elog(DEBUG1, "ERROR: read leaf entry data file.\n");
    return ERR_FILE;
  }

  ipool = new IntervalIEntry[numIEntry];
  if (ipool == NULL
    )
    return ERR_MEM;

  ret = fread(ipool, sizeof(IntervalIEntry), numIEntry, file);
  if (ret != numIEntry) {
    elog(DEBUG1, "ERROR: read internal entry data file.\n");
    return ERR_FILE;
  }

  ret = fclose(file);
  if (ret != 0)
    return ERR_FILE;

  return SUCCESS;
}

