<?php
/*
 *	file name : ListDatabase.php
 *	this class made by Lee Han-gil.
 *	start date : 2003 - 02 - 15
 *	
 *	jedit option
 *	:folding=explicit:collapseFolds=1:
 */
	class ListDatabase
	{
//{{{		vars for file pointer
		var $fpList;		// list
		var $fpNPage;	// list - page // pgin
		var $fpPage;	// page
		var $fpIndex;	// index
		var $fpRows;	// rows
		var $fpEmpty;	// emt
//}}}
//{{{		vars for db information
		var $dbname;
		var $totalRow;
		var $totalSize;
		var $first;
		var $last;
		var $totalPage;
		var $emptyNum;
		var $rowSize;
//}}}
//{{{		vars for table information
		var $colsNum;
		var $colsNo;	// name->number;
		var $colsType;	// name->type
		var $NUMBER=1;
		var $STRING=2;
//}}}
//{{{		initialize function
		
		function ListDatabase($dbname)
		{
			$this->yx = 256;
			$this->xx = 256*256;
			$this->wx = $this->xx*256;
			$this->dbname = $dbname;
		}
//}}}
//{{{		debug code
		function debug($s)
		{
			//echo "\{$s}";
		}
//}}}
//{{{		function named createLDB()
		
		function createLDB($fields, $types)
		{
			$this->fpList = fopen("$this->dbname.list", "w");
			if ( $this->fpList == false ) return false;
			$this->set4byteNumber($this->fpList, 0);		// space for total data number
			$this->set4byteNumber($this->fpList, 0);		// space for total size
			$this->set4byteNumber($this->fpList, 0);		// space for first number
			$this->set4byteNumber($this->fpList, 0);		// space for last number
			fclose($this->fpList);
			
			$this->fpPage = fopen("$this->dbname.page", "w");
			if ( $this->fpPage == false ) return false;
			$this->set4byteNumber($this->fpPage, 0);		// space for total page
			fclose($this->fpPage);
			
			$this->fpEmpty = fopen("$this->dbname.emt", "w");
			if ( $this->fpEmpty == false ) return false;
			$this->set4byteNumber($this->fpEmpty, 0);		// space for total empty ps
			$this->set4byteNumber($this->fpEmpty, 0);		// space for row size
			fclose($this->fpEmpty);
			
			
			$this->fpNPage	= fopen("$this->dbname.pgin", "w");
			if ( $this->fpNPage == false ) return false;
			fclose($this->fpNPage);
			
			$this->fpIndex	= fopen("$this->dbname.index", "w");
			if ( $this->fpIndex == false ) return false;
			fclose($this->fpIndex);
			
			$this->fpRows	= fopen("$this->dbname.rows", "w");
			if ( $this->fpRows == false ) return false;
			fclose($this->fpRows);
			
			$fpTable = fopen("$this->dbname.table", "w");
			if ( $fpTable == false ) return false;
			for ( $i=0 ; $i<count($fields) ; $i++ )
			{
				if ( $types[$i] == "number" ) $type = $this->NUMBER;
				else $type = $this->STRING;
				fwrite($fpTable, $fields[$i]."=".$type."\n");
			}
			fclose($fpTable);
			return true;
		}
//}}}
//{{{		function named (check & set & cancel OpenWriteMode)
		function isOpenWriteMode($dbname)
		{
			return false;
		}
		
		function setOpenWriteMode($dbname)
		{
		}
		
		function cancelOpenWriteMode($dbname)
		{
		}
//}}}
//{{{		function named (open & close LDB)
		//boolean... char
		function openLDB($mode)
		{
			if ( $mode == 'w' )
			{
				if ( $this->isOpenWriteMode($dbname) ) return false;
				else $this->setOpenWriteMode($dbname);
			}
			
			if ( !is_writeable("$this->dbname.list")
				|| !is_writeable("$this->dbname.pgin")
				|| !is_writeable("$this->dbname.page")
				|| !is_writeable("$this->dbname.index")
				|| !is_writeable("$this->dbname.rows")
				|| !is_writeable("$this->dbname.emt")
				|| !is_writeable("$this->dbname.table")
			) return false;
			
			$this->fpList	= fopen("$this->dbname.list", "r+");
			$this->fpNPage	= fopen("$this->dbname.pgin", "r+");
			$this->fpPage	= fopen("$this->dbname.page", "r+");
			$this->fpIndex	= fopen("$this->dbname.index", "r+");
			$this->fpRows	= fopen("$this->dbname.rows", "r+");
			$this->fpEmpty	= fopen("$this->dbname.emt", "r+");
			
			$table = file("$this->dbname.table");
			$this->colsNum = count($table);
			
			for ( $i=0 ; $i<$this->colsNum ; $i++ )
			{
				$col = strtok($table[$i], "=");
				$type = strtok("");
				$this->colsNo[$col] = $i;
				$this->colsType[$col] = $type;
			}
			
			if (	$this->fpList == false
				|| $this->fpNPage == false
				|| $this->fpPage == false
				|| $this->fpIndex == false
				|| $this->fpRows == false
				|| $this->fpEmpty == false
			) return false;
			
			$this->totalRow		= $this->get4byteNumber($this->fpList);
			$this->totalSize		= $this->get4byteNumber($this->fpList);
			$this->first		= $this->get4byteNumber($this->fpList);
			$this->last		= $this->get4byteNumber($this->fpList);
			$this->totalPage	= $this->get4byteNumber($this->fpPage);
			$this->emptyNum	= $this->get4byteNumber($this->fpEmpty);
			$this->rowSize		= $this->get4byteNumber($this->fpEmpty);
			
			return true;
		}
		
		function closeLDB()
		{
			fseek($this->fpList, 0);
			fseek($this->fpPage, 0);
			fseek($this->fpEmpty, 0);
			
			$this->set4byteNumber($this->fpList, $this->totalRow);
			$this->set4byteNumber($this->fpList, $this->totalSize);
			$this->set4byteNumber($this->fpList, $this->first);
			$this->set4byteNumber($this->fpList, $this->last);
			$this->set4byteNumber($this->fpPage, $this->totalPage);
			$this->set4byteNumber($this->fpEmpty, $this->emptyNum);
			$this->set4byteNumber($this->fpEmpty, $this->rowSize);
			
			fclose($this->fpList);
			fclose($this->fpNPage);
			fclose($this->fpPage);
			fclose($this->fpIndex);
			fclose($this->fpRows);
			fclose($this->fpEmpty);
			
			$this->cancelOpenWriteMode($dbname);
		}
//}}}
//{{{		function named getListRange(start, end)
		
		//{{{ for get Range
		function getNnd($nd)
		{
			$num = $nd%100;
			$page = ($nd-$num)/100;

			if ( $page == 0 )
			{
				$N = $this->first;
				$num--;
			}
			else
			{
				$this->f4seek($this->fpPage, $page);
				$N = $this->get4byteNumber($this->fpPage);
			}
			for ( $i=0 ; $i<$num ; $i++)
			{
				$N = $this->getNextRow($N);
			}
			return $N;
		}
		//}}}
		
		//long[] ... long, long
		function getListRange($start, $n)
		{
			$list[$i] = $this->getNnd($start);
			for ( $i=1 ; $i<$n ; $i++ )
			{
				$list[$i] = $this->getNextRow($list[$i-1]);
				if ( $list[$i] == 0 ) break;
			}
			return $list;
		}
		
		function getSearchList($field, $keyword)
		{
			$n = $this->first;
			while ( $n!=0 )
			{
				$value = $this->getValue($n, $field);
				if ( strpos($value, $keyword) === true ) $list[] = $n;
				$n = $this->getNextRow($n);
			}
			return $list;
		}
//}}}
//{{{		function named insert/dropRow(nd)
		//long ... long, int, void
		function insertRow($nd)
		{
			$this->totalSize++;
			$new = $this->totalSize;
			if ( $nd == -1 )
			{
				$nd = $this->last;
				$this->listLink($nd, $new);
				$this->pageRighting($nd, $new);
			}
			else
			{
				$this->listLink($nd, $new);
				$this->pageRighting($nd, $new);
			}
			$this->totalRow++;
			
			return $new;
		}

		//boolean ... long
		function dropRow($nd)
		{
			while ( list($name, $no) = each($this->colsNo) )
			{
				$type = $this->colsType[$name];
				if ( $type == $this->STRING )
				{
					$this->indexMove($nd, $fieldName);
					$this->deleteRowText($this->get4byteNumber($this->fpIndex));
				}
				else if ( $type == $this->NUMBER )
				{
					$this->indexMove($nd, $fieldName);
					$this->set4byteNumber($this->fpIndex, 0);
				}
			}
			$this->listUnlink($nd);
			$this->pageLefting($nd);
			$this->totalRow--;
		}
//}}}
//{{{		function named set/getValue
		//{{{ function named indexMode for set/getValue
		function indexMove($nd, $name)
		{
			$this->f4seek($this->fpIndex, ($nd)*$this->colsNum+($this->colsNo[$name]));
		}
		//}}}
		//lboolean ... long, int, void
		function setValue($nd, $fieldName, $value)
		{
			$this->indexMove($nd, $fieldName);
			if ( $this->colsType[$fieldName] == $this->NUMBER )
			{
				$this->set4byteNumber($this->fpIndex, $value);
			}
			else
			{
				$ps = $this->get4byteNumber($this->fpIndex);
				if ( $ps != 0 ) $this->deleteRowText($ps);
				//$this->f4seekOn($this->fpIndex, -1);
				$this->indexMove($nd, $fieldName);
				$this->set4byteNumber($this->fpIndex, $this->setRowText($value) );
			}
		}
		
		//void ... long, 
		function getValue($nd, $fieldName)
		{
			$this->indexMove($nd, $fieldName);
			if ( $this->colsType[$fieldName] == $this->NUMBER )
			{
				return $this->get4byteNumber($this->fpIndex);
			}
			else
			{
				return $this->getRowText( $this->get4byteNumber($this->fpIndex) );
			}
		}
//}}}
//{{{		with rows text file
		//boolean ...
		function setEmpty($ps)
		{
			$this->emptyNum++;
			$this->f4seek($this->fpEmpty, $this->emptyNum);
			$this->set4byteNumber($this->fpEmpty, $ps);
		}
		
		//long...
		function getEmptyPs()
		{
			if ( $this->emptyNum == 0 )
			{
				$this->rowSize++;
				return $this->rowSize;
			}
			$this->f4seek($this->fpEmpty, $this->emptyNum);
			$this->emtpyNum--;
			return $this->get4byteNumber($this->fpEmpty);
		}
		
		//long ... string
		function setRowText($text)
		{
			if ( ($textLEN = strlen($text)) == 0 ) return 0;
			$ps = $this->getEmptyPs();
			$startPs = $ps;
			for ( $i=0; 1; $i++)
			{
				fseek($this->fpRows, $ps*54);
				fwrite($this->fpRows, substr($text, $i*50, 50), 50);
				if ( $i+1>(int)$textLEN/50 )
				{
					$this->set4byteNumber($this->fpRows, 0);
					break;
				}
				$ps = $this->getEmptyPs();
				$this->set4byteNumber($this->fpRows, $ps);
			}
			if ( $textLEN%50 != 0) fwrite($this->fpRows, chr(0), 1);
			return $startPs;
		}
		
		//string ... long
		function getRowText($ps)
		{
			while ( $ps != 0 )
			{
				fseek($this->fpRows, $ps*54);
				$text .= fread($this->fpRows, 50);
				$ps = $this->get4byteNumber($this->fpRows);
			}
			return ereg_replace('^'.chr(0), '', $text);
		}
		
		//long ... 
		function deleteRowText($ps)
		{
			while ( $ps == 0 )
			{
				fseek($this->fpRows, $ps*54+50);
				$this->setEmpty($ps);
				$ps = $this->get4byteNumber($this->fpRows); 
			}
		}
//}}}
//{{{		function named getTotal/First/LastRow
		function getTotalRow()
		{
			return $this->totalRow;
		}
		
		function getLastRow()
		{
			return $this->last;
		}
		
		function getFirstRow()
		{
			return $this->first;
		}
//}}}
//{{{		function named getNext/BeforeRow
		function getNextRow($ps)
		{
			$this->f4seek($this->fpList, $this->listNextPs($ps));
			return $this->get4byteNumber($this->fpList);
		}
		
		function getBeforeRow($ps)
		{
			$this->f4seek($this->fpList, $this->listBeforePs($ps));
			return $this->get4byteNumber($this->fpList);
		}
///}}}
//{{{		link, unlink, increas, decreas
		
		//{{{ function named listBefore/NextPs for listLink/Unlink
		// memo
		// nd(before) = 4+(ps-1)*2 = 2+ps*2 (in list)
		// nd(next) = 4+(ps-1)*2+1 = 3+ps*2 (in list)
		function listBeforePs($ps)
		{
			return 2+$ps*2;
		}
		
		function listNextPs($ps)
		{
			return 3+$ps*2;
		}
		//}}}
		
		function listLink($ps, $target)
		{
			$next_of_ps = $this->getNextRow($ps);
			
			if ( $ps != 0 )
			{
				if ( $next_of_ps != 0 )
				{
					$this->f4seek($this->fpList, $this->listBeforePs($next_of_ps));	// go before of (next of ps)
					$this->set4byteNumber($this->fpList, $target);				// write on before = target
					$this->f4seek($this->fpList, $this->listNextPs($target));		// go next of target
					$this->set4byteNumber($this->fpList, $next_of_ps);			// write on next = next of ps
				}
				else
				{
					$this->last = $target;
				}
				$this->f4seek($this->fpList, $this->listNextPs($ps));	// go next of ps
				$this->set4byteNumber($this->fpList, $target);		// write on next = target
			}
			else
			{
				$this->f4seek($this->fpList, $this->listBeforePs($this->first));	// go before of first
				$this->set4byteNumber($this->fpList, $target);				// write on before = target
				
				$this->f4seek($this->fpList, $this->listNextPs($target));		// go before of first
				$this->set4byteNumber($this->fpList, $this->first);			// write on before = target
				
				if ( $this->first == 0 ) $this->last = $target;
				$this->first = $target;
			}
			$this->f4seek($this->fpList, $this->listBeforePs($target));	// go next of target
			$this->set4byteNumber($this->fpList, $ps);				// write on before = ps
		}
		
		function listUnlink($target)
		{
			$before_of_target = $this->getBeforeRow($target);			// get before of target
			$next_of_target = $this->get4byteNumber($this->fpList);	// get next of target
			
			if ( $before_of_target != 0 )
			{
				$this->f4seek($this->fpList, $this->listNextPs($before_of_target));	// go next of (before of target)
				$this->set4byteNumber($this->fpList, $next_of_target);			// write on next = next of target
			}
			else
			{
				$this->first = $next_of_target;
			}
			
			if ( $next_of_target != 0 )
			{
				$this->f4seek($this->fpList, $this->listBeforePs($next_of_target));	// go before of (next of target)
				$this->set4byteNumber($this->fpList, $before_of_target);		// write on before = before of target
			}
			else
			{
				$this->last = $before_of_target;
			}
		}
		
		//{{{ function named get/setPageMark, get/setBelongPage for pageShifting
		function getPageMark($page)
		{
			$this->f4seek($this->fpPage, $page);
			return $this->get4byteNumber($this->fpPage);
		}
		
		function setPageMark($page, $row)
		{
			$this->f4seek($this->fpPage, $page);
			$this->set4byteNumber($this->fpPage, $row);
		}
		
		function getBelongPage($row)
		{
			$this->f4seek($this->fpNPage, $row);
			return $this->get4byteNumber($this->fpNPage);
		}
		
		function setBelongPage($row, $page)
		{
			$this->f4seek($this->fpNPage, $row);
			$this->set4byteNumber($this->fpNPage, $page);
		}
		//}}}
		
		function pageRighting($ps, $next)
		{
			//$this->debug("1:$ps/$next");
			$p_belong = $this->getBelongPage($ps);
			$r_mark = $this->getPageMark($p_belong);
			
			//$this->debug("2:$r_mark(mark)/$ps(ps)/$p_belong(belong)/$this->totalPage");
			if ( $r_mark == $ps && $p_belong!=$this->totalPage ) $p_belong++;
			$this->setBelongPage($next, $p_belong);
			
			//$this->debug("3:$ps, $next, $p_belong, $this->totalPage");
			$this->f4seek($this->fpPage, $p_belong);
			for ( $i = $p_belong; $i < $this->totalPage ; $i++ )
			{
				$r_iMarks[] = $this->get4byteNumber($this->fpPage);
			}
			
			//$this->debug(4);
			$this->f4seek($this->fpPage, $p_belong);
			for ( $i = $p_belong, $ai=0 ; $i < $this->totalPage ; $i++, $ai++ )
			{
				//$this->debug(5);
				$this->set4byteNumber($this->fpPage, $this->getBeforeRow($r_iMarks[$ai]));
				$this->setBelongPage($r_iMarks[$ai], $i+1);
			}
			
			//$this->debug(6);
			if ( $this->totalRow%100 == 0 )
			{
				$this->setPageMark($this->totalPage, $this->getBeforeRow($this->last));
				$this->totalPage++;
			}
			
			//$this->debug(7);
			$this->setPageMark($this->totalPage, $this->last);
			$this->setBelongPage($this->last, $this->totalPage);
		}
		
		function pageLefting($ps)
		{
			$p_belong = $this->getBelongPage($ps);
			$r_mark = $this->getPageMark($p_belong);
			
			if ( $r_mark == $ps ) $p_belong++;
			$this->setBelongPage($next, $p_belong);
			
			$this->f4seek($this->fpPage, $p_belong);
			for ( $i = $p_belong; $i < $this->totalPage ; $i++ ) $r_iMarks[] = $this->get4byteNumber($this->fpPage);
			
			$this->f4seek($this->fpPage, $p_belong);
			for ( $i = $p_belong, $ai=0 ; $i < $this->totalPage ; $i++, $ai++ )
			{
				$this->set4byteNumber($this->fpPage, $this->getNextRow($r_iMarks[$ai]));
				$this->setBelongPage($r_iMarks[$ai], $i);
			}
			
			if ( $this->totalRow%100 == 1 )
			{
				$this->setPageMark($this->totalPage, 0);
				$this->totalPage--;
				$this->setBelongPage($this->last, $this->totalPage);
			}
			$this->setPageMark($this->totalPage, $this->last);
		}
//}}}
//{{{		basic functions named f4seek, f4seekOn, get4byteNumber, set4byteNumber
		
		function f4seek($fp, $ps)
		{
			//need for check;
			rewind($fp);
			fseek($fp, $ps*4);
		}
		
		function f4seekOn($fp, $ps)
		{
			//fseek($fp, ftell($fp)+$ps*4); // for php4.0 rc1 ÀÌÀü ¹öÁ¯
			fseek($fp, $ps*4, SEEK_CUR); // for php4.0 rc1 ÀÌÈÄ ¹öÁ¯
		}
		
		var $yx;
		var $xx;
		var $wx;
		
		function get4byteNumber($fp)
		{
			$w = ord(fgetc($fp));
			$x = ord(fgetc($fp));
			$y = ord(fgetc($fp));
			$z = ord(fgetc($fp));
			return $w*$this->wx + $x*$xthis->x + $y*$this->yx + $z;
		}
		function set4byteNumber($fp, $n)
		{
			$w = (int)($n/$this->wx);
			$x = (int)($n%$this->wx/$this->xx);
			$y = (int)($n%$this->wx%$this->xx/$this->yx);
			$z = (int)($n%$this->wx%$this->xx%$this->yx);
			//fputs($fp, chr($w).chr($x).chr($y).chr($z),4);
			fwrite($fp, chr($w).chr($x).chr($y).chr($z),4);
		}
//}}}
	}
//{{{	test code
	
	/*
	//createDB();
	//listTestCode();
	dataTestCode();
	debugPrint();
	//*/
	function createDB()
	{
		$ldb = new ListDatabase("./test");
		$ldb->createLDB(array("name", "title"), array("string", "string"));
	}
//{{{ listTestCode
	function listTestCode()
	{
		$ldb = new ListDatabase("./test");
		if ( $ldb->openLDB("w") == false) echo "false";
		
		//*
		for ($i=0; $i<200; $i++)
		{
			echo $ldb->insertRow(-1).",";
			echo "<br>";
		}
		//*/
		//*
		$in = $ldb->insertRow(0);
		echo $in.",";
		//echo $ldb->dropRow($in).",";
		//*/
		echo $ldb->insertRow(-1).",";
		$ldb->closeLDB();
	}
//}}}
	function dataTestCode()
	{
		$ldb = new ListDatabase("./test");
		if ( $ldb->openLDB("w") == false) echo "false";
		
		$n = $ldb->insertRow(-1);
		echo "$n";
		//*
		$ldb->setValue($n, "name", "2hangulee------------------------------------------------------------------------------------1");
		$ldb->setValue($n, "title", "2magunara");
		
		echo "<br><br>";
		echo $ldb->getValue($n, "name")."<br>";
		echo $ldb->getValue($n, "title").";";
		//*/
		$ldb->closeLDB();
	}
//}}}
//{{{ debug chek out
	function debugPrint()
	{
		$ldb = new ListDatabase("./test");
		if ( $ldb->openLDB("w") == false) echo "false";
		echo "<br>------------------<br>";
		echo "totalRow=".$ldb->totalRow."<br>";
		echo "totalSize=".$ldb->totalSize."<br>";
		echo "first=".$ldb->first."<br>";
		echo "last=".$ldb->last."<br>";
		echo "totalPage=".$ldb->totalPage."<br>";
		echo "emptyNum=".$ldb->emptyNum."<br>";
		echo "rowSize=".$ldb->rowSize."<br>";
		echo "------------------<br>";
		debugHexCode($ldb);
		$ldb->closeLDB();
	}
	
	function debugHexCode($ldb)
	{
		
		echo "<br>------------------List{totalRow/totalSize/first/last}<br>";
		putNumToFile($ldb, $ldb->fpList);
		
		echo "<br>------------------Index<br>";
		putCharToFile($ldb, $ldb->fpIndex);
		
		echo "<br>------------------Empty{emptyNum/rowSize}<br>";
		putCharToFile($ldb, $ldb->fpEmpty);
		
		echo "<br>------------------Page{totalPage}<br>";
		putNumToFile($ldb, $ldb->fpPage);
		
		echo "<br>------------------NPage<br>";
		putCharToFile($ldb, $ldb->fpNPage);
		
		echo "<br>------------------Rows<br>";
		putCharToFile($ldb, $ldb->fpRows);
	}
	
	function putNumToFile($ldb, $fp)
	{
		fseek($fp, 0);
		$i=0;
		while(!feof($fp))
		{
			if ( $i%2 == 0 ) echo "[";
			echo $ldb->get4byteNumber($fp);
			
			if ( $i%2 == 1 ) echo "]";
			else echo ", ";
			if ( $i%24 == 1 ) echo "<br>";
			$i++;
		}
	}
	
	function putCharToFile($ldb, $fp)
	{
		fseek($fp, 0);
		$i=0;
		while(!feof($fp))
		{
			if ( $i%4 == 0 ) echo "[";
			echo bin2hex(fgetc($fp));
			if ( $i%4 == 3 ) echo "]";
			else echo ", ";
			if ( $i%20 == 3 ) echo "<br>";
			$i++;
		}
		echo "]";
	}
//}}}
?>
