<?php
// Xinfeng Chen, 2002.12.15

define('BIF_NULLPTR',0xFFFF);
define('BIF_PTR_SIZE',2);
define('BIF_PTRFIELD_SIZE',BIF_PTR_SIZE*2);
define('BIF_HEADER_SIZE',32);
// Header: magic(4),recSize(8),recNum(8),idxNum(4),resv(8)
// rec(-1): store recNum for each index
// rec(0): store ptrHead/ptrTail for each index
// rec(1--?): store ptrPrev/ptrNext for links of each index

class CBaseIndexFile {
 var $_fp=0, $_magic, $_recSize, $_recNum, $_idxNum;
 var $_curIdx=0, $_linkNum=0;
 var $_curId=BIF_NULLPTR, $_ptrPrev=BIF_NULLPTR, $_ptrNext=BIF_NULLPTR;

 function _readHeader(){
  fseek($this->_fp, 0); $buf=fread($this->_fp, BIF_HEADER_SIZE);
  list($this->_magic,$this->_recSize,$this->_recNum,$this->_idxNum)
   =explode('|',$buf);
 }

 function _writeHeader(){
  $buf='ZC10|'.$this->_recSize.'|'.$this->_recNum
    .'|'.$this->_idxNum.'|';
  fseek($this->_fp, 0); fwrite($this->_fp, str_pad($buf,BIF_HEADER_SIZE), BIF_HEADER_SIZE);
 }

 function _allocRecord($a_recId){
  flock($this->_fp, 2);
  $this->_readHeader();

  if($a_recId > $this->_recNum){
   $offset=($this->_recNum + 1 + 1)*$this->_recSize + BIF_HEADER_SIZE;
   $buf=str_repeat("\xFF",$this->_recSize);
   fseek($this->_fp,$offset);
   for($i=$this->_recNum;$i<$a_recId;$i++){fwrite($this->_fp, $buf);}

   $this->_recNum=$a_recId;
   $this->_writeHeader();
   fflush($this->_fp);
  }

  flock($this->_fp, 3);
 }

 function _readLink($a_recId, $a_ptrPrev, $a_ptrNext){
  $offset=$this->_curIdx*BIF_PTRFIELD_SIZE + ($a_recId+1)*$this->_recSize + BIF_HEADER_SIZE;
  fseek($this->_fp, $offset);
  $buf=fread($this->_fp, BIF_PTRFIELD_SIZE);
  $tmpArr=unpack('S*',$buf);
  $a_ptrPrev=$tmpArr[1];
  $a_ptrNext=$tmpArr[2];
 }

 function _writeLink($a_recId, $a_ptrPrev, $a_ptrNext){
  $offset=$this->_curIdx*BIF_PTRFIELD_SIZE + ($a_recId+1)*$this->_recSize + BIF_HEADER_SIZE;
  $buf=pack('S*',$a_ptrPrev,$a_ptrNext);
  fseek($this->_fp, $offset);
  fwrite($this->_fp, $buf, BIF_PTRFIELD_SIZE);
 }

 function _writePointer($a_recId, $a_pos, $a_ptrVal){
  $offset=$a_pos * BIF_PTR_SIZE + $this->_curIdx * BIF_PTRFIELD_SIZE
   + ($a_recId + 1) * $this->_recSize + BIF_HEADER_SIZE;
  $buf=pack('S',$a_ptrVal);
  fseek($this->_fp, $offset);
  fwrite($this->_fp, $buf, BIF_PTR_SIZE);
 }

 function openFile($a_filename){
  if(!($this->_fp=@fopen($a_filename, "r+b"))){return false;}
  $this->_readHeader();
  return ($this->_magic=='ZC10');
 }

 function closeFile(){
  fclose($this->_fp);
 }

 function selectIndex($a_idx) {
  if($a_idx<$this->_idxNum){
   $this->_curIdx=$a_idx;
   $this->_readLink(-1,&$this->_linkNum,$this->_linkNum);
   return true;
  }else{
   return false; 
  }
 }

 function getRecordCount() {return $this->_linkNum;}

 function getRecordId() {return $this->_curId;}

 function setRecord($a_recId) {
  if(($a_recId==BIF_NULLPTR)||($a_recId<=0)||($a_recId>$this->_recNum)){
   $this->_curId=$this->_ptrPrev=$this->_ptrNext=BIF_NULLPTR;
   return false;
  }

  $this->_readLink($a_recId, &$this->_ptrPrev, &$this->_ptrNext);
  $this->_curId=$a_recId;
  return true;
 }

 function moveTo($a_offset, $a_forward=true) {
  $this->_curId=0; //set to head/tail record
  $count=0;
  while(($count<=$a_offset)&&($this->_curId!=BIF_NULLPTR)){
   $this->_readLink($this->_curId,&$this->_ptrPrev,&$this->_ptrNext);
   $this->_curId=($a_forward) ? $this->_ptrNext : $this->_ptrPrev;
   $count++;
  }
  return $this->setRecord($this->_curId);
 }

 function moveFirst(){return $this->moveTo(0, true);}
 function moveLast(){return $this->moveTo(0, false);}
 function moveNext(){return $this->setRecord($this->_ptrNext);}
 function movePrev(){return $this->setRecord($this->_ptrPrev);}

 function setLink($a_recId, $a_destId, $a_insert=true) {
  if(($a_recId==BIF_NULLPTR)||($a_recId<=0)){return false;}
  if(($a_destId==BIF_NULLPTR)||($a_destId< 0)){return false;}
  if($a_recId > $this->_recNum){$this->_allocRecord($a_recId);}

  flock($this->_fp, 2);
  $this->_readLink(-1, &$this->_linkNum, $this->_linkNum);
  if(!$this->setRecord($a_recId)){flock($this->_fp,3); return false;}

  if(($this->_ptrPrev != BIF_NULLPTR)||($this->_ptrNext != BIF_NULLPTR)){
   $this->_writePointer($this->_ptrPrev, 1, $this->_ptrNext);
   $this->_writePointer($this->_ptrNext, 0, $this->_ptrPrev);
   $this->_writeLink($this->_curId, BIF_NULLPTR, BIF_NULLPTR);
   $this->_linkNum--;
  }

  $destPrev=$destNext=BIF_NULLPTR;
  $this->_readLink($a_destId, &$destPrev, &$destNext);
  if($a_insert){
   $this->_ptrPrev=$destPrev; $this->_ptrNext=$a_destId;
  }else{
   $this->_ptrPrev=$a_destId; $this->_ptrNext=$destNext;
  }
  $this->_writeLink($a_recId, $this->_ptrPrev, $this->_ptrNext);
  $this->_writePointer($this->_ptrNext, 0, $a_recId);
  $this->_writePointer($this->_ptrPrev, 1, $a_recId);

  $this->_linkNum++;
  $this->_writeLink(-1, $this->_linkNum, $this->_linkNum);

  fflush($this->_fp);
  flock($this->_fp, 3);
  return true;
 }

 function setLinkBefore($a_recId, $a_destId) {
  return $this->setLink($a_recId, $a_destId, true);
 }

 function setLinkAfter($a_recId, $a_destId) {
  return $this->setLink($a_recId, $a_destId, false);
 }

 function removeLink($a_recId) {
  if(($a_recId==BIF_NULLPTR)||($a_recId<=0)||($a_recId>$this->_recNum)) return false;

  flock($this->_fp, 2);
  $this->_readLink(-1, &$this->_linkNum, $this->_linkNum);
  $this->setRecord($a_recId);

  if(($this->_ptrPrev != BIF_NULLPTR) || ($this->_ptrNext != BIF_NULLPTR)){
   $this->_writePointer($this->_ptrPrev, 1, $this->_ptrNext);
   $this->_writePointer($this->_ptrNext, 0, $this->_ptrPrev);
   $this->_writeLink($this->_curId, BIF_NULLPTR, BIF_NULLPTR);

   $this->_linkNum--;
   $this->_writeLink(-1, $this->_linkNum, $this->_linkNum);
   fflush($this->_fp);
  }

  flock($this->_fp, 3);
  return true;
 }

 function createFile($a_fileName, $a_idxNum){
  $this->_fp=fopen($a_fileName,"wb");
  $this->_recSize=$a_idxNum * BIF_PTRFIELD_SIZE;
  $this->_recNum=0;
  $this->_idxNum=$a_idxNum;
  $this->_writeHeader();
  fwrite($this->_fp, str_repeat("\x00",$this->_recSize)); // rec(-1)
  fwrite($this->_fp, str_repeat("\x00",$this->_recSize)); // rec(0)
  fclose($this->_fp);
  return true;
 }

 function destroyFile($a_fileName){
  if(!is_file($a_fileName)){return false;}
  return unlink($a_fileName);
 }
}

?>