<?php
/**
 * @copyright (c) Kufer Software GmbH, Oderstraße 7, D-84453 Mühldorf
 */


class DBOperationMssqlSqlsrv extends DBOperation
{
  
  private $_lastSQLStatement = '';

  
  private $_fieldMetaData;
  
  private $_countFields;

  
  private $_countRows;

  public function __construct($connectionInfo, $pDBnr)
  {
    parent::__construct($connectionInfo, $pDBnr);
  }

  
  public function connect_db(string $pDbServer, string $pDbName, string $pDbUser, string $pDbPassword): void
  {
    static $iniSetCalled = false;
    if (!$iniSetCalled && is_numeric($this->connectionInfo->db_setClientBufferMaxKBSize) && $this->connectionInfo->db_setClientBufferMaxKBSize >= 10240) {
      ini_set('sqlsrv.ClientBufferMaxKBSize', $this->connectionInfo->db_setClientBufferMaxKBSize);
      $iniSetCalled = true;
    }

    $info = array('Database' => $pDbName, 'UID' => $pDbUser, 'PWD' => $pDbPassword);
    if (System::isUnicode() || !System::isWindows()) {
      $info['CharacterSet'] = 'UTF-8';
    }
    $this->connection = @sqlsrv_connect($pDbServer, $info);
    if (!is_resource($this->connection) && $this->connection === false) {
      $errors = sqlsrv_errors();
      $errorDetails = '';
      foreach ($errors as $error) {
        $errorDetails .= $error[0] . ': ' . $error[1] . ' - ' . $error[2] . "\n";
      }
      Logger::logg($errorDetails, 1250);
      $this->throwCriticalError('Die Verbindung zum MSSQL-Datenbankserver "' . $pDbServer . '" konnte nicht hergestellt werden! ' . "\n" . trim($errorDetails));
    }
    
    $this->execute('SET LANGUAGE deutsch;');
  }

  public function query(string $sql, bool $fatalError = true): bool
  {
    $this->openConnection();
    $this->_lastSQLStatement = $sql;
    
    $this->resultset = sqlsrv_query($this->connection, $this->encodeStringForDb($sql), array(), array('scrollable' => SQLSRV_CURSOR_CLIENT_BUFFERED));
    if ($this->resultset === false) {
      if ($fatalError) {
        $this->throwCriticalError('Das SQL-Statement enthält Fehler! <br/>', $sql);
      } else {
        return false;
      }
    }

    $this->_fieldMetaData = sqlsrv_field_metadata($this->resultset);
    $this->_countFields = count($this->_fieldMetaData);
    $this->_countRows = sqlsrv_num_rows($this->resultset);

    return true;
  }

  
  public function execute(string $sql): bool
  {
    $this->openConnection();
    $this->_lastSQLStatement = $sql;
    $res = sqlsrv_query($this->connection, $this->encodeStringForDb($sql));
    if ($res === false) {
      $this->throwCriticalError('Falsche Benutzung von execute(sql)');
    } else {
      $this->_fieldMetaData = array();
      $this->_countFields = 0;
      $this->_countRows = 0;
    }

    return true;
  }

  
  public function rowcount(): int
  {
    return $this->_countRows;
  }

  
  public function selectrow(int $rownr): bool
  {
    
    if ($rownr < $this->rowcount() && $rownr > -1) {
      if (sqlsrv_fetch($this->resultset, SQLSRV_SCROLL_ABSOLUTE, $rownr)) {
        $this->actualrow = $rownr;
        
        
        
        $this->currentRow = array_change_key_case($this->_fetch_array($this->resultset, SQLSRV_FETCH_BOTH, SQLSRV_SCROLL_ABSOLUTE, $rownr), CASE_LOWER);

        return true;
      } else {
        return false;
      }
    } else {
      return false;
    }
  }

  
  private function _fetch_array($stmt, int $fetchType = SQLSRV_FETCH_BOTH, int $row = SQLSRV_SCROLL_RELATIVE, int $offset = 0): array
  {
    $tmp = sqlsrv_fetch_array($stmt, $fetchType, $row, $offset);
    if (is_array($tmp)) {
      foreach ($tmp as $key => $value) {
        if ($value instanceof DateTime) {
          $tmp[$key] = $value->format('Y-m-d H:i:s');
        }
      }
    }

    return $tmp;
  }

  
  public function debug(): void
  {
    echo 'Anzahl Zeilen: ' . $this->rowcount() . "\n";
    for ($i = 0; $i < $this->rowcount(); $i++) {
      $this->selectrow($i);
      foreach ($this->_fieldMetaData as $meta) {
        $name = $meta['Name'];
        echo "$name: " . $this->fieldbyname($name) . ', ';
      }
      echo "\n";
    }
  }

  
  public function fieldExists(string $name): bool
  {
    $fieldMetaData = $this->_fieldMetaData;
    $numFields = $this->_countFields;
    $ret = false;
    for ($i = 0; $i < $numFields; ++$i) {
      if (StringOperations::strtolower($fieldMetaData[$i]['Name']) == StringOperations::strtolower($name)) {
        $ret = true;
        break;
      }
    }

    return $ret;
  }

  
  public function getError(): string
  {
    $error = sqlsrv_errors(SQLSRV_ERR_ERRORS);
    $firstError = $error[0];
    $tmp = 'Error # ' . $firstError['code'] . ': ' . $firstError['message'] . " \n - letztes abgegebenes SQL-Statement: " . $this->_lastSQLStatement . " <br />\n";
    foreach ($firstError as $key => $value) {
      if (!is_numeric($key)) {
        $tmp .= $key . ' = ' . $value . ' ; ';
      }
    }
    $tmp .= "<br />\n";

    return $tmp;
  }

  
  public function checkConnection(bool $pCheckTables = true): bool
  {
    $conInfo = DBConnectionInfo::getInstance();
    if ($this->dbnr == 1) {
      $dbserver = $conInfo->db_server;
      $dbname = $conInfo->db_name;
      $dbuser = $conInfo->db_user;
      $dbpass = $conInfo->db_password;
    } else {
      $dbserver = $conInfo->db2_server;
      $dbname = $conInfo->db2_name;
      $dbuser = $conInfo->db2_user;
      $dbpass = $conInfo->db2_password;
    }
    $Info = array('Database' => $dbname, 'UID' => $dbuser, 'PWD' => $dbpass);
    $connection = @sqlsrv_connect($dbserver, $Info);

    if (!$connection) {
      return false;
    }
    
    $res = @sqlsrv_query($connection, 'SET LANGUAGE deutsch;');
    if ($res) {
      sqlsrv_free_stmt($res);
    }

    if ($pCheckTables) {
      $res = @sqlsrv_query($connection, 'select top 1 id from aussenst');
      if (!$res) {
        return false;
      }

      sqlsrv_free_stmt($res);

      $res = @sqlsrv_query($connection, 'select top 1 id from kategorien');
      if (!$res) {
        return false;
      }

      sqlsrv_free_stmt($res);

      $res = @sqlsrv_query($connection, 'select top 1 id from web_tools');
      if (!$res) {
        return false;
      }
    }

    
    
    $this->connect_db($dbserver, $dbname, $dbuser, $dbpass);

    return true;
  }

  public function closeConnection(): void
  {
    @sqlsrv_close($this->connection);
    $this->connection = false;
    Connections::CloseConnection($this->dbnr);
  }

  
  public function checkTableExists(string $pTableName, bool $pRefreshCache = false): bool
  {
    if (self::$_tableExistsCache == null) {
      self::$_tableExistsCache = array();
    }

    if (!$pRefreshCache && isset(self::$_tableExistsCache[$pTableName])) {
      return self::$_tableExistsCache[$pTableName];
    }

    $sql = "SELECT * FROM information_schema.TABLES WHERE table_name = '" . $pTableName . "' and table_catalog = '" . $this->connectionInfo->db_name . "'";
    $this->clear();
    $this->query($sql);
    self::$_tableExistsCache[$pTableName] = ($this->rowcount() == 1);

    return self::$_tableExistsCache[$pTableName];
  }

  public function beginTransaction(): bool
  {
    $this->openConnection();

    return sqlsrv_begin_transaction($this->connection);
  }

  public function commitTransaction(): bool
  {
    return sqlsrv_commit($this->connection);
  }

  public function rollbackTransaction(): bool
  {
    return sqlsrv_rollback($this->connection);
  }

  private function getEncoderFromInternToDb(): \EncodeCharsetFromTo
  {
    static $encode = null;
    if (is_null($encode)) {
      $encode = new \EncodeCharsetFromTo(\CodingOperations::getInternalEncoding(), UTF_8);
    }

    return $encode;
  }

  public function decodeStringFromDb(string $value): string
  {
    if ($this->connectionInfo->db_isEncodingUtf8) {
      $encoder = $this->getEncoderFromInternToDb();

      return $encoder->decode($value);
    }

    return $value;
  }

  public function encodeStringForDb(string $value): string
  {
    if ($this->connectionInfo->db_isEncodingUtf8) {
      $encoder = $this->getEncoderFromInternToDb();

      return $encoder->encode($value);
    }

    return $value;
  }

  public function getEnclosedString4Sql(string $str): string
  {
    $isKuferSql5 = false;
    try {
      $isKuferSql5 = \System::isKuferSql5();
    } catch (\Throwable $ex) {
    }

    if ($isKuferSql5) {
      return 'N' . parent::getEnclosedString4Sql($str);
    } else {
      return parent::getEnclosedString4Sql($str);
    }
  }

  

  
  public function fieldbyname(string $name)
  {
    $normalfieldname = StringOperations::strtolower($name);

    if (isset($this->currentRow[$normalfieldname])) {
      $value = $this->currentRow[$normalfieldname];
      
      if (StringOperations::substr($value, StringOperations::strlen($value) - 1, 1) == \StringOperations::chr_windows1252(32)) {
        $value = StringOperations::substr($value, 0, StringOperations::strlen($value) - 1);
      }
      
      
      return $value;
    }

    return '';
  }

  
  public function getLastID(string $tablename)
  {
    $this->clear();
    $this->query('SELECT @@IDENTITY LID');
    $this->selectrow(0);

    return abs($this->fieldbyName('LID'));
  }

  public function getDBName(): string
  {
    return 'MSSQL';
  }

  public function getColumnsOfTable(string $table): array
  {
    $result = array();

    $this->clear();
    $this->query("SELECT name FROM sys.columns WHERE object_id = OBJECT_ID('dbo.$table')");
    for ($i = 0; $i < $this->rowcount(); ++$i) {
      $this->selectrow($i);
      $result[] = $this->fieldbyname('name');
    }

    return $result;
  }
}