/*
 * Author: Ravneet Singh
 * Email: thecheatah@gmail.com
 * This code can only be used if Ravneet Singh has authorized to do so.
 */

function dataset(attributes, sorters)
  {
  this.attributes = new Array();
  this.sortingFns = new Array();

  this.data = new Array();
  this.length = 0;
  this.freeSpots = new Array();
  this.dataPos = new Array();
  this.ordering = new Array();
  this.sorts = new Array();

  this.inputAttributes = attributes;
  this.inputSorters = sorters;

  var defaultSorter = function(a, b)
    {
    if (a == b)
      {
      return 0;
      }
    if (a < b)
      {
      return -1;
      }
    return 1;
    }

  if (sorters == null)
    {
    sorters = new Array();
    }

  for(var i = 0; i < attributes.length; i++)
    {
    this.data[i] = new Array();
    this.attributes[attributes[i]] = i;
    this.sortingFns[attributes[i]] = (sorters[attributes[i]]==null?defaultSorter:sorters[attributes[i]]);
    }
  }

dataset.prototype.debug = function()
  {
  var text = "";

  for(var i = 0; i < this.length; i++)
    {
    for(var attr in this.attributes)
      {
      var attrId = this.attributes[attr];
      text = text + attr + "=\"" + this.data[attrId][i] + "\", ";
      }
    text = text + "\n";
    }


  return text;
  }

dataset.prototype.setSort = function(attr, asc)
  {
  for(var i = 0; i < this.sorts.length; i++)
    {
    if (this.sorts[i]['attr'] == attr)
      {
      removeFromArray(this.sorts, i);
      break;
      }
    }
  this.sorts.unshift({'attr':attr, 'asc':(asc==false?false:true)});
  this.resortData();
  }


dataset.prototype.getSort = function(attr)
  {
  for(var i = 0; i < this.sorts.length; i++)
    {
    if (this.sorts[i]['attr'] == attr)
      {
      return (this.sorts[i]['asc']?'asc':'desc');
      }
    }
  return null;
  }

dataset.prototype.getSortPos = function(attr)
  {
  for(var i = 0; i < this.sorts.length; i++)
    {
    if (this.sorts[i]['attr'] == attr)
      {
      return i;
      }
    }
  return null;
  }

dataset.prototype.clearSort = function()
  {
  this.sorts = new Array();
  }

dataset.prototype.insertionSortLeft = function(pos)
  {
  var tmp;
  while(pos > 0 && this.compair(this.ordering[pos-1], this.ordering[pos]) > 0)
    {
    this.swapPositions(pos-1, pos);
    pos--;
    }
  }

dataset.prototype.insertionSort = function()
  {
  for(var i = 0; i < this.ordering.length; i++)
    {
    this.insertionSortLeft(i);
    }
  }

dataset.prototype.heapSort = function()
  {
  if (this.length == 0)
    {
    return;
    }
  var heapSize = this.length;
  for(var i = Math.floor((heapSize-1)/2); i >= 0; i--)
    {
    this.heapSortSiftDown(i, heapSize);
    }

  for(var i = 0; i < this.length; i++)
    {
    this.swapPositions(0, (this.length - i - 1));
    heapSize--;
    this.heapSortSiftDown(0, heapSize);
    }
  }

dataset.prototype.heapSortSiftDown = function(node, heapSize)
  {
  var leftNode = null;
  var rightNode = null;
  var largest = null;

  while(true)
    {
    leftNode = ((node+1)*2)-1;
    rightNode = leftNode+1;
    if (rightNode < heapSize)
      {
      largest = (this.compair(this.ordering[leftNode], this.ordering[rightNode]) > 0?leftNode:rightNode);
      }else if(leftNode < heapSize)
      {
      largest = leftNode;
      }else
      {
      break;
      }

    if (this.compair(this.ordering[node], this.ordering[largest]) >= 0)
      {
      break;
      }

    this.swapPositions(node, largest);
    node = largest;
    }

  }

dataset.prototype.resortData = function()
  {
  this.heapSort();
  }

dataset.prototype.swapPositions = function(p1, p2)
  {
  var tmp = this.ordering[p1];
  this.ordering[p1] = this.ordering[p2];
  this.ordering[p2] = tmp;
  this.dataPos[this.ordering[p1]] = p1;
  this.dataPos[this.ordering[p2]] = p2;
  }

dataset.prototype.compair = function(ai, bi)
  {
  var result;
  var attr;
  for(var i = 0; i < this.sorts.length; i++)
    {
    attr = this.sorts[i]['attr'];
    attrID = this.attributes[attr];
    result = this.sortingFns[attr](this.data[attrID][ai], this.data[attrID][bi]);
    if (result != 0)
      {
      if (!this.sorts[i]['asc'])
        {
        result = result*-1;
        }
      return result;
      }
    }
  return 0;
  }

dataset.prototype.clear = function()
  {
  this.length = 0;
  this.freeSpots = new Array();
  this.dataPos = new Array();
  this.ordering = new Array();
  for(var i = 0; i < this.data.length; i++)
    {
    this.data[i] = new Array();
    }
  }

dataset.prototype.add = function(set)
  {
  var freeIndex = this.data[0].length;
  if (this.freeSpots.length != 0)
    {
    freeIndex = this.freeSpots.shift();
    }
  for(var attr in set)
    {
    if (this.attributes[attr] != null)
      {
      this.data[this.attributes[attr]][freeIndex] = set[attr];
      }
    }
  this.length = this.length+1;
  var pos = this.ordering.length;
  this.ordering[pos] = freeIndex;
  this.dataPos[freeIndex] = pos;
  this.insertionSortLeft(pos);
  return freeIndex;
  }

dataset.prototype.insert = function(row)
  {
  this.add(row);
  }

dataset.prototype.update = function(where, values)
  {
  for(var i = 0; i < this.data[0].length; i++)
    {
    var matchesAll = true;

    for(var attr in where)
      {
      if (this.data[this.attributes[attr]][i] != where[attr])
        {
        matchesAll = false;
        break;
        }
      }

    if (matchesAll)
      {
      for(var attr in values)
        {
        this.data[this.attributes[attr]][i] = values[attr];
        }
      }
    }
  }


dataset.prototype.deleteRows = function(where)
  {
  for(var i = 0; i < this.data[0].length; i++)
    {
    var matchesAll = true;

    for(var attr in where)
      {
      if (this.data[this.attributes[attr]][i] != where[attr])
        {
        matchesAll = false;
        break;
        }
      }


    if (matchesAll)
      {
      this.remove(i);
      }
    }
  }


dataset.prototype.select = function(values, where)
  {
  var result = new dataset(values, this.inputSorters);

  for(var i = 0; i < this.data[0].length; i++)
    {
    var matchesAll = true;

    for(var attr in where)
      {
      if (this.data[this.attributes[attr]][i] != where[attr])
        {
        matchesAll = false;
        break;
        }
      }


    if (matchesAll)
      {
      var row = {};
      for(var j = 0; j < values.length; j++)
        {
        row[values[j]] = this.data[this.attributes[values[j]]][i];
        }
      result.insert(row);
      }
    }


  return result;
  }

dataset.prototype.selectArray = function(values, where)
  {
  return this.select(values, where).getByRange(0);
  }

dataset.prototype.remove = function(index)
  {
  for(var i = 0; i < this.data.length; i++)
    {
    this.data[i][index] = null;
    }
  var pos = this.dataPos[index];
  for(var i = pos; i < (this.ordering.length-1); i++)
    {
    this.ordering[i] = this.ordering[i+1];
    this.dataPos[this.ordering[i]] = i;
    }
  this.ordering.pop();
  this.length = this.length-1;
  this.dataPos[index] = null;
  this.freeSpots.push(index);
  }

dataset.prototype.get = function(index)
  {
  var row = new Array();
  for(attr in this.attributes)
    {
    row[attr] = this.data[this.attributes[attr]][index];
    }
  return row;
  }

dataset.prototype.getByPos = function(pos, retObj)
  {
  var result;

  if (pos >= this.ordering.length)
    {
    result = null;
    }else
    {
    result = this.get(this.ordering[pos]);
    }

  if (retObj == null)
    {
    return result;
    }else
    {
    retObj.run(result);
    }
  }

dataset.prototype.getByRange = function(start, count, retObj)
  {
  if (count == null)
    {
    count = this.length;
    }
  var result = new Array();
  var row = null;
  for(var i = 0; i < count; i++)
    {
    row = this.getByPos(start+i);
    if (row != null)
      {
      result[result.length] = this.getByPos(start+i);
      }else
      {
      break;
      }
    }
  if (retObj == null)
    {
    return result;
    }else
    {
    retObj.run(result);
    }
  }


