<?php
class DB_Mssql {
  public $dbconn;
  public $dboption;
  public $query_print = false;
  public $query_record = true;

  public function __construct($db_option=""){
    global $__dbconn__;

    $_config_db_option = @parse_ini_file("db_config.php");
    $this->dboption = $db_option ?: $_config_db_option;

    if( !$db_option && $__dbconn__ ) { //기본 Connection을 이용
      $this->dbconn = $__dbconn__;
    }else{ //전용 Connection을 이용
      $this->dbConn($db_option);
    }
  }

  public function dbConn($db_option=""){
    $_config_db_option = @parse_ini_file("db_config.php");
    if( !$_config_db_option ){
      $_config_db_option = @parse_ini_file(_ROOT_PATH_."/config/db_config.php");
    }
    if( !$db_option ) $db_option = array();

    /*위에 있어서 필요없는 코드*/
    $db_option["database_type"]    = $db_option["database_type"]    ?: $_config_db_option["database_type"];
    $db_option["database_host"]    = $db_option["database_host"]    ?: $_config_db_option["database_host"];
    $db_option["database_port"]    = $db_option["database_port"]    ?: $_config_db_option["database_port"];
    $db_option["database_name"]    = $db_option["database_name"]    ?: $_config_db_option["database_name"];
    $db_option["database_user"]    = $db_option["database_user"]    ?: $_config_db_option["database_user"];
    $db_option["database_pass"]    = $db_option["database_pass"]    ?: $_config_db_option["database_pass"];
    $db_option["database_encode"]  = $db_option["database_encode"]  ?: $_config_db_option["database_encode"];
    $db_option["database_charset"] = $db_option["database_charset"] ?: $_config_db_option["database_charset"];
    $db_option["database_old"]     = $db_option["database_old"]     ?: $_config_db_option["database_old"];

    if( !$this->dboption ){
      $this->dboption = $db_option;
    }
    $addr   = "{$db_option['database_host']},{$db_option['database_port']}";
    $option = Array(
      "Database"     => $db_option['database_name'],
      "Uid"          => $db_option['database_user'],
      "PWD"          => $db_option['database_pass'],
      "CharacterSet" => $db_option['database_encode'],
      "TrustServerCertificate" => "yes",
      "ConnectionPooling" => 0,
    );
    $conn = sqlsrv_connect( $addr, $option);
    if( $conn ){
      $this->dbconn = $conn;
      if( defined("TB_query_log") ){
        $this->tableExists(TB_query_log);
      }
      return $conn;

    }else{
      die('Could not connect: ' . print_r(sqlsrv_errors(), true));
    }
  }

  public function qry($sql, $sqlrow="", $options="", $print=""){
    return $this->cqry($sql, $sqlrow, $options, $print);
  }

  public function cqry($sql, $sqlrow="", $options="", $print="", $rev=1){
    if( $rev === 1 ){
      $anrow = $this->anqry($sqlrow);
      $params = $anrow['params'];
    }else{
      $params = $sqlrow;
    }
    if( $print || $this->query_print ){
      echo $this->mkstr($sql,$params?:Array());
    }
    if( $print ){exit;}
    $stmt = @sqlsrv_query($this->dbconn, $sql, $params?:Array(), $options?:Array());
    if( $stmt ){
      $result = "SUCCESS";
      $rst = $stmt;
    }else{
      $bpage = debug_backtrace();
      $fname = $bpage[count($bpage)-1]['file'];

      if( !$GLOBALS['NO_SQL_ERR'] ){
        $result = print_r(sqlsrv_errors(), true);
        echo "<pre>{$fname}".PHP_EOL.$sql.PHP_EOL.print_r($params?:Array(),1).PHP_EOL.PHP_EOL.print_r(sqlsrv_errors(), true)."</pre>";
      }else{
        $result = "NO_SQL_ERROR";
      }
      $rst = false;
    }
    if( $this->query_record ){
      $this->putQueryLog( $sql, $params, $result );
    }
    return $rst;
  }

  public function query($sql, $params="", $options="", $print=""){
    $stmt = $this->qry($sql, $params?:Array(), $options?:Array(), $print);
    if( !$stmt ) return false;

    $rows = Array();
    while( $row = $this->fetch_array($stmt) ){
      $rows[] = $row;
    }
    return $rows;
  }

  public function call( $procedure, $params=array() ){
    $anrow = $this->anqry($params);
    $sql = "{CALL {$procedure} (".implode(",",$anrow['val']).")}";
    return $this->qry($sql, $params);
  }

  public function call_output( $procedure, $params=array() ){
    $anrow = $this->anqry($params);
    $sql = "{CALL {$procedure} ".implode(",",$anrow['val']).")}";
    return $this->cqry($sql, $params,"","",2);
  }

  public function call_next( $stmt ){
    return @sqlsrv_next_result($stmt);
  }

  public function call_all( $procedure, $params=array() ){
    $rst = $this->call( $procedure, $params );
    if( $rst ){
      $rows = Array();
      while( $row = $this->fetch_array($rst) ){
        $rows[] = $row;
      }
      return $rows;
    }
    return $rst;
  }

  public function exec( $procedure, $params=array() ){
    $anrow = $this->anqry($params);
    $sql = "EXEC {$procedure} ".implode(",",$anrow['val'])."";
    return $this->qry($sql, $params);
  }

  public function exec_output( $procedure, $params=array() ){ //$params => 숫자키 배열
    $anrow = $this->anqry($params);
    $sql = "EXEC {$procedure} ".implode(",",$anrow['val'])."";
    return $this->cqry($sql, $params,"","",2);
  }

  public function exec_output_merge( $procedure, $params=array() ){ //$params => 문자 키 배열. output요소가 array(array("값",SQLSRV_PARAM_OUT)) 가 되어야 한다.
    $oparams = Array();
    foreach( $params as $val ){
      $oparams[] = $val;
    }

    $anrow = $this->anqry($oparams);
    $sql = "EXEC {$procedure} ".implode(",",$anrow['val'])."";
    return $this->cqry($sql, $oparams,"","",2);
  }

  public function exec_next( $stmt ){
    return @sqlsrv_next_result($stmt);
  }

  public function exec_all( $procedure, $params=array() ){
    $rst = $this->exec( $procedure, $params );
    if( $rst ){
      $rows = Array();
      while( $row = $this->fetch_array($rst) ){
        $rows[] = $row;
      }
      return $rows;
    }
    return $rst;
  }

  public function queryFirstField($sql, $params="", $options="", $print=""){
    $stmt = $this->qry($sql, $params?:Array(), $options?:Array(), $print);

    if( !$stmt ) return false;

    return $this->result($stmt,0,0);
  }

  public function queryFirstRow($sql, $params="", $options="", $print=""){
    $stmt = $this->qry($sql, $params?:Array(), $options?:Array(), $print);

    if( !$stmt ) return false;

    return $this->fetch_array($stmt);
  }

  public function pqry($sql, $params="", $options=""){
    return $this->qry($sql, $params?:Array(), $options?:Array(), 1);
  }

  public function pquery($sql, $params="", $options=""){
    return $this->query($sql, $params?:Array(), $options?:Array(), 1);
  }

  public function fetch_row($stmt){
    if( $stmt ){
      return @sqlsrv_fetch_array($stmt, 1);
    }
    return "sqlsrv_fetch_array Error #1";
  }

  public function fetch_array($stmt){
    if( $stmt ){
      return @sqlsrv_fetch_array($stmt, 2);
    }
    echo "sqlsrv_fetch_array Error #2";
  }

  public function fetch_object($stmt){
    if( $stmt ){
      return @sqlsrv_fetch_object($stmt, 2);
    }
    echo "sqlsrv_fetch_object Error #2";
  }

  public function num_rows($stmt){
    if( $stmt ){
      return @sqlsrv_num_rows($stmt);
    }
    echo "sqlsrv_num_rows Error";
  }

  public function select_count($tb, $where="", $whererow="", $print=""){
    $where = $where?:"1=1";
    $sql = "select count(*) from {$tb} where {$where}";
    return $this->queryFirstField($sql, $whererow?:array(), $print);
  }

  public function insert($tb, $row, $print=""){
    $anrow = $this->anqry($row);
    $sql = "insert into {$tb} (".implode(",",$anrow['fld']).") values (".implode(",",$anrow['val']).")";
    $rst = $this->qry($sql, $anrow['params'], "", $print);
    return (bool)$rst;
  }

  public function update($tb, $row, $where="", $whererow="", $print=""){
    $where = $where?:"1=1";
    $anrow = $this->anqry($row);
    $sql = "update {$tb} set ";

    $setrow = array();
    foreach( (array)$anrow['fld'] as $k => $v ){
      $setrow[] = " {$v} = {$anrow['val'][$k]} ";
    }
    $sql.= implode(",",$setrow)." where {$where}";
    foreach( (array)$whererow as $k => $v ){
      $anrow['params'][] = $v;
    }
    $rst = $this->qry($sql, $anrow['params'], "", $print);
    return (bool)$rst;
  }

  public function insertupdate($tb, $ins_row, $upt_row, $where="", $whererow="", $print=""){
    $where = $where?:"1=1";
    $ins_anrow = $this->anqry($ins_row);
    $upt_anrow = $this->anqry($upt_row);
    $setrow = array();
    foreach( (array)$upt_anrow['fld'] as $k => $v ){
      $setrow[] = " {$v} = {$upt_anrow['val'][$k]} ";
    }

    $sql = "merge into {$tb} as A using (select 1 as dual) as B on ({$where}) ";
    $sql.= "when matched then ";
    $sql.= "update set ".implode(",",$setrow)." ";
    $sql.= "when not matched then ";
    $sql.= "insert (".implode(",",$ins_anrow['fld']).") values (".implode(",",$ins_anrow['val']).");";

    $params = array_merge($whererow, $upt_anrow['params'], $ins_anrow['params']);

    $rst = $this->qry($sql, $params?:Array(), "", $print);
    return (bool)$rst;
  }

  public function delete($tb, $where, $whererow="", $print=""){
    $where = $where?:"1=1";
    $sql = "delete from {$tb} where {$where}";
    $rst = $this->qry($sql, $whererow?:Array(), "", $print);
    return (bool)$rst;
  }

  public function insertId($tb){
    if( $tb ){
      $sql = "select ident_current(?)";
      return $this->queryFirstField($sql,array($tb));
    }else{
      $sql = "select @@identity";
      return $this->queryFirstField($sql);
    }
    //@@IDENTITY	현재 세션의 테이블에서 생성된 마지막 ID 값을 반환합니다. --// 현재 세션내 모든 테이블에서 발생하는 ID값중 마지막
    //SCOPE_IDENTITY()	현재 세션의 테이블에서 생성된 마지막 ID 값을 반환합니다. --// 현재 세션내 특
    //IDENT_CURRENT()	임의 세션 및 범위에 있는 특정 테이블에 생성된 값을 반환합니다.
  }

  public function anqry($row){ // 필드와 값 분리 + assign value
    $fld = array();
    $val = array();
    $params = array();
    foreach( (array)$row as $k => $v ){
      $fld[] = $k;
      if( $this->chkeval($v) ){
        $val[] = $this->valeval($v);
      }else{
        $val[] = "?";
        $params[] = $v;
      }
    }
    return Array( "fld"=>$fld, "val"=>$val, "params"=>$params );
  }

  public function result($stmt, $rows=0, $cols=0){
    $rows = intval($rows);
    for( $a=0 ; $a<=$rows ; $a++ ){
      if( is_int($cols) ){
        $arr = $this->fetch_row($stmt);
      }else{
        $arr = $this->fetch_array($stmt);
      }
    }
    return is_array($arr)?$arr[$cols]:$arr;
  }
  
  public function mkstr( $sql, $params="" ){ //쿼리 출력
    $params = $params?:Array();
    $str = $sql;
    for( $a=0 ; $a < substr_count($sql, "?") ; $a++ ){
      $str = $this->str_freplace("?","'{$params[$a]}'",$str);
    }
    return $str;
  }
  
  public function str_freplace( $needle, $replace, $str ){
    $pos = strpos($str, $needle);
    if( $pos !== false ){
      $nstr = substr_replace($str, $replace, $pos, strlen($needle));
    }
    return $nstr;
  }

  public function limit($start, $length){
    if( $length > 0 ){
      return " offset {$start} rows fetch next {$length} rows only ";
    }
    return '';
  }

  public function addLimit($sql, $ordby, $start, $length){
    $ordby = $ordby?:1;
    $start = $start?:0;
    $length = $length?:999999999998;

    if( $this->dboption["database_version"] && $this->dboption["database_version"] <= "2008" ){
      $start = $start+1;
      $length = $start+$length-1;
      $sql = "select * from ( select *, row_number() over(order by {$ordby}) as ROWNUM from ({$sql}) as tblsub ) as tblmain where ROWNUM between {$start} and {$length}";
    }else {
      $sql = "{$sql} order by {$ordby} offset {$start} rows fetch next {$length} rows only ";
    }

    return $sql;
  }

  public function sqleval($str){
    return chr(27).$str;
  }

  public function chkeval($str){
    if( !is_array($str) && preg_match("/^".chr(27)."/ims",$str) ){
      return true;
    }
    return false;
  }

  public function valeval($str,$val=""){
    return preg_replace("/^".chr(27)."/ims",$val,$str);
  }

  public function tableExists( $table ){
    if( !$table ) return false;

    $tbl = explode(".",$table);
    if( !$tbl[1] ){
      $database = "dbo";
      $table = $tbl[0];
    }else{
      $database = $tbl[0];
      $table = $tbl[1];
    }
    $cnt = $this->queryFirstField("select 1 from Information_schema.tables where TABLE_TYPE = ? and TABLE_NAME = ?", array(
      "BASE TABLE",
      $table
    ));
    if( $cnt ){
      return true;
    }else{
      if( !( $fs = fopen( TB_schema_file, "r") ) ) return false;

      while( $rw = fgets($fs, 255) ){
        $tbname = defined("TB_HEAD")?preg_replace("/^".TB_HEAD."/","",$table):$table;

        if( chop($rw) == "[{$tbname}]" ) break;
      }

      $query = "create table {$database}.{$table} ";
      while( $rw = fgets($fs, 255) ){
        if( chop($rw) != "-end-" ){
          $rw = preg_replace("/ comment[ \=].*\'.*\'/ims","",$rw);
          $rw = preg_replace("/CONSTRAINT pk\_/ims","CONSTRAINT pk_".TB_HEAD,$rw);
          $rw = preg_replace("/CONSTRAINT fk\_/ims","CONSTRAINT fk_".TB_HEAD,$rw);
          $rw = preg_replace("/CONSTRAINT uk\_/ims","CONSTRAINT uk_".TB_HEAD,$rw);
          $rw = preg_replace("/CONSTRAINT ik\_/ims","CONSTRAINT ik_".TB_HEAD,$rw);
          $query .= $rw;
        }else{
          break;
        }
      }
      fclose($fs);
      $rst = $this->qry($query);
      if( $rst ){
        include TB_data_file;
        if( isset($table_base_data[$tbname]) ){
          foreach( $table_base_data[$tbname] as $val ){
            $this->insert($table, $val);
          }
        }
      }
      return $rst;
    }
  }

  public function putQueryLog( $save_sql, $save_params, $result ){
    global $__dbconn__;
    if( !defined("TB_query_log") ) return false;
    if( preg_match("/Information_schema.tables/ims", $save_sql) ) return false;

    $this->delQueryLog(1);

    $qryrow = Array(
      "query_date"   => $this->sqleval("CURRENT_TIMESTAMP"),
      "query_result" => $result,
      "host_name"    => $this->dboption["database_host"],
      "db_name"      => $this->dboption["database_name"],
      "query_string" => $save_sql,
      "query_params" => @print_r($save_params, true),
      "visit_sess"   => $_SESSION['visit_sess'],
      "member_id"    => $GLOBALS['_Umem']['member_id'],
    );

    $anrow = $this->anqry($qryrow);
    $sql = "insert into ".TB_query_log." (".implode(",",$anrow['fld']).") values (".implode(",",$anrow['val']).")";
    return @sqlsrv_query($__dbconn__, $sql, $anrow['params']);
  }

  public function delQueryLog( $month = 6 ){
    global $__dbconn__;

    if( rand(1,100) === 50 ){ // 1% 확률로 삭제
      $month= -1*abs($month);
      $sql = "delete from ".TB_query_log." where query_date < dateadd(month, {$month}, getdate()) ";
      return @sqlsrv_query($__dbconn__, $sql);
    }
    return false;
  }

  public function callTransaction(){
    return sqlsrv_begin_transaction( $this->dbconn );
  }

  public function callCommit(){
    return sqlsrv_commit( $this->dbconn );
  }

  public function callRollBack(){
    return sqlsrv_rollback( $this->dbconn );
  }

  public function setPrint(){
    $this->query_print = true;
  }

  public function resetPrint(){
    $this->query_print = false;
  }

  public function setRecord(){
    $this->query_record = true;
  }

  public function resetRecord(){
    $this->query_record = false;
  }
}

class BaseDB {
  public $DB;
  public $lcrypt;
  public function __construct($db_option=""){
    $this->DB = new DB_Mssql($db_option);
    $this->lcrypt = new lcrypt();
  }
}