<?php

include('stream.php');

define('NOT_DBC_FILE', 'Není DBC soubor.');
define('FIELD_COUNT_NOT_MATCH', 'Počet sloupců neodpovídá délce formátu.');
define('RECORD_SIZE_NOT_MATCH', 'Velikost řádku neodpovídá zadanému formátu.');
 
class DBCFile extends FileStream
{
  private $HeaderSize = 20;
  private $Offsets;
  private $StringOffset;
  private $StringList = array();
  private $StringListOffset = array();
  private $Format;
   
  private $RecordSize;
  private $RecordCount;
  private $StringBlockSize;
  private $FieldCount;
      
  public function OpenFile($FileName, $Format)
  {
    parent::OpenFile($FileName);
    
    $this->Format = $Format;
    if($this->ReadUint() != 0x43424457) die(NOT_DBC_FILE);
       
    $this->RecordCount = $this->ReadUint();
    $this->FieldCount = $this->ReadUint();
    $this->RecordSize = $this->ReadUint();
    $this->StringBlockSize = $this->ReadUint();
     
    if(strlen($this->Format) != $this->FieldCount)
      die(FIELD_COUNT_NOT_MATCH);
     
    $this->GenerateOffsetTable($Format);
    if($this->Offsets[count($this->Offsets) - 1] != $this->RecordSize)
	  die(RECORD_SIZE_NOT_MATCH.$this->Offsets[count($this->Offsets) - 1].' <> '.$this->RecordSize); 
  }

  public function CreateFile($FileName, $Format)
  {
    parent::CreateFile($FileName);
    
    $this->WriteUint(0x43424457);
       
	$this->StringList = array();
	$this->StringOffset = 1;
    $this->Format = $Format;
    $this->GenerateOffsetTable($Format);
	$this->FieldCount = strlen($Format);
	$this->RecordCount = 0;
	$this->RecordSize = $this->Offsets[count($this->Offsets) - 1];
	$this->StringBlockSize = 0;

    $this->WriteUint($this->RecordCount);
    $this->WriteUint($this->FieldCount);
    $this->WriteUint($this->RecordSize);
    $this->WriteUint($this->StringBlockSize);         
  }
   
  private function GenerateOffsetTable($Format)
  {
    $this->Offsets = array();
	$this->Offsets[0] = 0;
    for($I = 0; $I < strlen($Format); $I++)
    {
      $this->Offsets[$I + 1] = $this->Offsets[$I];
      switch($Format[$I])
      {
        case "b": 
		case "X": 
		  $this->Offsets[$I + 1] += 1;	
		  break;
        case "x": 
		case "u": 
		case "i": 
		case "f": 
		case "s": 
		  $this->Offsets[$I + 1] += 4;	
		  break;
      } 
    }    
  }
  
  private function SeekPosi($Row, $Column)
  {
    $Position = $this->HeaderSize + $Row * $this->RecordSize + $this->Offsets[$Column];
    $this->Seek($Position);
  }
   
  public function GetByte($Row, $Column)
  {
    $this->SeekPosi($Row, $Column);  
    return($this->ReadByte());
  }   
   
  public function GetUint($Row, $Column)
  {
    $this->SeekPosi($Row, $Column);  
    return($this->ReadUint());
  }
   
  public function GetInt($Row, $Column)
  {
    $this->SeekPosi($Row, $Column);  
    return($this->ReadInt());
  }  
   
  public function GetFloat($Row, $Column)
  {
    $this->SeekPosi($Row, $Column);  
    return($this->ReadFloat());
  }

  public function SetByte($Row, $Column, $Value)
  {
    $this->SeekPosi($Row, $Column);  
    $this->WriteByte($Value);
  }   
   
  public function SetUint($Row, $Column, $Value)
  {
    $this->SeekPosi($Row, $Column);  
    $this->WriteUint($Value);
  }
   
  public function SetInt($Row, $Column, $Value)
  {
    $this->SeekPosi($Row, $Column);  
    $this->WriteInt($Value);
  }  
   
  public function SetFloat($Row, $Column, $Value)
  {
    $this->SeekPosi($Row, $Column);  
    $this->WriteFloat($Value);
  }
  
  public function GetString($Row, $Column)
  {
	$Offset = $this->GetUint($Row, $Column);
    
    $Position = $this->HeaderSize + $this->RecordCount * $this->RecordSize + $Offset;
	if($Position >= $this->GetSize()) return('');
    $this->Seek($Position);
     
    $String = '';
    while(($Char = $this->ReadChar()) != "\0")
    {
      $String .= $Char;
    }
    return($String);
  }    
  
  public function SetString($Row, $Column, $Value)
  {
	if(in_array($Value, $this->StringList))
	{ 
	  $this->SetUint($Row, $Column, $this->StringListOffset[array_search($Value, $this->StringList)]);
	} else 
	{
	  $this->SetUint($Row, $Column, $this->StringOffset);
	  $this->StringList[] = $Value;
	  $this->StringListOffset[] = $this->StringOffset;
	  $this->StringOffset += strlen($Value) + 1;
	}
  }

  public function Commit()
  {
	$this->Seek(0);
    $this->WriteUint(0x43424457);
    $this->WriteUint($this->RecordCount);
    $this->WriteUint($this->FieldCount);
    $this->WriteUint($this->RecordSize);
    $this->WriteUint($this->StringOffset);         
	$this->Seek($this->HeaderSize + $this->RecordCount * $this->RecordSize);
	$this->WriteByte(0);
	foreach($this->StringList as $Index => $Item)
	{		
      $this->WriteString($Item);
	}
  }    
   
  public function GetLine($Row)
  {
    $Line = array();
    for($I = 0; $I < $this->FieldCount; $I++)
    {
      switch($this->Format[$I])
      {
        case 'b': 
		  $Line[$I] = $this->GetByte($Row, $I); 
		  break;
        case 'u': 
		  $Line[$I] = $this->GetUint($Row, $I); 
		  break;
        case 'i': 
		  $Line[$I] = $this->GetInt($Row, $I); 
		  break;
        case 'f': 
		  $Line[$i] = $this->GetFloat($Row, $I); 
		  break;
        case 's': 
		  $Line[$I] = $this->GetString($Row, $I); 
		  break;
        case 'x': 
		case 'X': 
		default: 
		  break;
      }
    }
    return($Line);
  }

  public function SetLine($Row, $Line)
  {
    for($I = 0; $I < $this->FieldCount; $I++)
    {
      switch($this->Format[$I])
      {
        case 'b': 
		  $this->SetByte($Row, $I, $Line[$I]); 
		  break;
        case 'u': 
		  $this->SetUint($Row, $I, $Line[$I]); 
		  break;
        case 'i': 
		  $this->SetInt($Row, $I, $Line[$I]); 
		  break;
        case 'f': 
		  $this->SetFloat($Row, $I, $Line[$i]); 
		  break;
        case 's': 
		  $this->SetString($Row, $I, $Line[$I]); 
		  break;
        case 'x': 
		case 'X': 
		default: 
		  break;
      }
    }
    return($Line);
  }
   
  public function GetLineCols($Row, $Columns)
  {
    $Line = array();
    for($I = 0; $I < count($Columns); $I++)
    {
      switch($this->Format[$Columns[$I]])
      {
        case 'b': 
		  $Line[$I] = $this->GetByte($Row, $Columns[$I]); 
		  break;
        case 'u': 
		  $Line[$I] = $this->GetUint($Row, $Columns[$I]); 
		  break;
        case 'i': 
		  $Line[$I] = $this->GetInt($Row, $Columns[$I]); 
		  break;
        case 'f': 
		  $Line[$i] = $this->GetFloat($Row, $Columns[$I]); 
		  break;
        case 's': 
		  $Line[$I] = $this->GetString($Row, $Columns[$I]); 
		  break;
        case 'x': 
		case 'X': 
		default: 
		  break;
      }
    }
    return($Line);  
  }
   
  public function GetRecordCount() 
  { 
	return($this->RecordCount); 
  }

  public function SetRecordCount($Value) 
  { 
	$this->RecordCount = $Value;
  }

  public function GetFieldCount() 
  { 
	return($this->FieldCount); 
  }
}
 
?>
