<?php
/**
 * a.b.c 取相关数据
 * @param string $dns
 * @param array $arrDist 指定数组,默认为null
 * 'id' 取 req 等同于 'req.id'
 * 'cfg.net.db.main' 取数据库配置
 * @return mixed
 */
function I(string $dns,array $arrDist=null){
    if(!is_null($arrDist)){
        return getArrayFromStrLink($dns,$arrDist);
    }
    $split = explode('.',$dns);
    if(!in_array($split[0],['req','cfg','session','cookie','server','var','globals'])){
        $tag = 'req';
    }else{
        $tag = array_shift($split);
        $dns = implode('.',$split);
    }
    if($dns=='') $dns = '.';
    switch($tag){
        case 'req':
            return getArrayFromStrLink($dns,\mvc::$req);
            break;
        case 'cfg':
            return getArrayFromStrLink($dns,\mvc::$cfg);
            break;
        case 'session':
            return getArrayFromStrLink($dns,$_SESSION);
            break;
        case 'cookie':
            return getArrayFromStrLink($dns,$_COOKIE);
        case 'server':
            return getArrayFromStrLink($dns,$_SERVER);
        case 'var':
        case 'globals':
            return getArrayFromStrLink($dns,$GLOBALS);
            break;
    }
}

/**
* a.b.c 取 数组
*
* @param array $array 
* @param string $dns 例 net.redis.main
* @return mixed 配置
*/
function getArrayFromStrLink(string $dns,array $array){
    if($dns=='.'){
        return $array;
    }
    $arr = explode('.',$dns);
    foreach($arr as $v){
        if(isset($array[$v])){
            $array = $array[$v];
        }else{
            return null;//trigger_error(slg('未找到指定项').': '.$dns, E_USER_ERROR);
        }
    }
	return $array;
}


/**
 * 使用指定变量格式化字符串
 * 
 * qgprintf("sum({:1}) sum_{:2}",$var1,$var2);
 * qgprintf("sum({:a}) sum_{:b}",$arr); //$arr['a'=>1,'b'=>2]
 * qgprintf("sum({:a}) sum_{:b} in({:sets})",$arr); //$arr['a'=>1,'b'=>2,sets=[1,2,3,4]]
 * qgprintf("{:1} sum({:a}) sum_{:b}",HOST $arr); //HOST='http://xxx',$arr['a'=>1,'b'=>2]
 * @param mixed $var
 * @return string
 */
function qgprintf($rulestr,...$args):string
{
	$count = count($args);
	for($i= $count-1; $i > -1; $i--){
		$arg = $args[$i];
		if (is_array($arg)) {
			foreach ($arg as $k => $v) {
				$type = gettype($v);
				if ($type != 'string') {
					if (is_object($v)) {
						$v = get_class($v);
					} else if (is_array($v)) {
						foreach ($v as $k2 => $v2) {
							$type = gettype($v2);
							if ($type == 'array') {
								$v2 = 'Array';
							} else if ($type == 'object') {
								$v2 = get_class($v2);
							}
							$v[$k2] = $v2;
						}
						$v = implode(',', $v);
					}
				}
				$rulestr = str_replace('{:' . $k . '}', $v, $rulestr);
			}
		}else{
			$rulestr = str_replace('{:' . ($i + 1) . '}', $arg, $rulestr);
		}
	}
	return $rulestr;
}

/**
* 返回安全字符串
*
* @return string
*/
function safeString($str){
	return addslashes(trim($str));
}

/**
 * 取MD5中间的16位
 *
 * @param string $str
 * @return string
 */
function md4(string $str):string {
    return substr(md5($str),8,16);
}

/**
 * 重构返回结果数组
 *
 * @param array $data 源,一维或都二维数组
 * @param array|string $keyField 指定哪个/组字段做为数组的键
 * @param array|string|null $valueField 指定哪个/组字段做为数组的值, null 表示全部字段
 * @param Closure $func 回调数据处理,返回 false 则忽略, func($key,$value,$allkey,$row), 级联键 $allkey 为 一级.二级.三级
 * @return self
 * @example map('id')
 * @example map('id','name')
 * @example map('id','@','name') 不改变数组下标
 * @example map('id',['name','email'])
 * @example map(['id1','id2'],['name','email']) 多个字段做键时用 点 连接 id1.id2
 * @example map('username',['email','depa','items'=>['ck1','ck2']]) 支持嵌套
 * @example map('username',true) 第二个参数 true，表示有相同键的使用追加模式将变成三维数组 ['user001'=>[  ['a','b'],['c','d'] ], 'user002'=>...]
 */
function map(array $data, $keyField, $valueField=null, \Closure $func=null):array
{
    if(!is_array(current($data))){
        $data = [$data];
    }
    $result = [];
    foreach($data as $i=>$row){
        $key = '';
        if($keyField=='@'){
            $key = $i;
        }elseif(is_string($keyField)){
            $key = is_object($row) ? $row->{$keyField} : $row[$keyField];
        }else{
            foreach($keyField as $field){
                if($key!='') $key .= '.';
                $key .= is_object($row) ? $row->{$field} : $row[$field];
            }
        }
		if (is_null($valueField)) {
			if ($func && false === $func($key, $row, $key, $row)) continue;
			$result[$key] = $row;
		}else if ($valueField === true) {
			if ($func && false === $func($key, $row, $key, $row)) continue;
			$result[$key][] = $row;
        }elseif(is_string($valueField)){
            if(is_object($row)){
                $aTemp = (array)$row;
                $value =$aTemp[$valueField];
            }else{
                $value =$row[$valueField];
            }
            if($func && false === $func($valueField,$value,$key,$row)) continue;
            $result[$key] = $value;
        }else{
            $result[$key] = _mapRecursionRow($key,$valueField,$row,$func);
        }
    }
    return $result;
}

function _mapRecursionRow($parentKey,$fields,$row,\Closure $func=null){
    $data = [];
    foreach($fields as $k=>$field){
        if(is_string($field)){
            if(is_object($row)){
                $aTemp = (array)$row;
                $value =$aTemp[$field];
            }else{
                $value =$row[$field];
            }
            if($func && false === $func($field,$value,$parentKey.'.'.$field, $k,$row)) continue;
            if(is_numeric($k)){
                $data[$field] = $value;
            }else{
                $data[$k] = $value;
            }
       }else{
            $data[$k] = _mapRecursionRow($parentKey.'.'.$k,$field,$row,$func);
        }
    }
    return $data;
}

function dd(...$args){
    call_user_func_array('dump',$args);
    exit();
}

function dump(...$args){
    echo call_user_func_array('dumpResult',$args);
}

function dumpResult(...$args){
	$result = '';
	foreach($args as $index => $v){
		$result .= _dump($v,$index);
	}
	if(IS_CLI){
        $f = fopen(DIR_RUNTIME.'/dump.html', 'a');
        fwrite($f, $result);
        fclose($f);
        //控制台去格式化
		$result = preg_replace('/<script>.*<\/script>/s', '', $result);
		$result = preg_replace('/<style>.*<\/style>/s', '', $result);
		$result = strip_tags($result);
		$result = htmlspecialchars_decode($result);
		$result = PHP_EOL . trim($result);
	}
	return $result;
}

function _dump($variable,$index){
	$json = json_encode($variable);
	$backtrace = debug_backtrace();
    $i=1;
	$caller = $backtrace[$i];
    while(strpos($caller['file'],'func.php')>0){
        $i++;
        $caller = $backtrace[$i];
    }
	$fileArr = file($caller['file']);
	$code = trim($fileArr[$caller['line']-1]);
	$t = explode(',',$code);
    if(isset($t[$index])) $t[$index] = preg_replace('/(\$\w+)/',"<span class='dd-call-active'>$1</span>",$t[$index]);
    $code = implode(',',$t);
	unset($fileArr);
	$result = trim("<script>console.log($json);</script>
			<style>
				.dd pre{line-height: 1.5em;font-size:16px;font-family: monaco, Consolas, Menlo, Courier, monospace;padding:0;margin:0}
				.dd pre .dd-blue{color:blue}
				.dd pre .dd-to{color:#ccc}
				.dd pre .dd-num{color:#F00}
				.dd pre .dd-string{color:#690}
				.dd pre .dd-array{color:#990}
				.dd pre .dd-object{color:#C0C}
				.dd pre .dd-object-val{color:#000;font-size:11px;line-height:15px}
				.dd pre .dd-stdClass{color:#C00;font-weight:bold}
				.dd pre .dd-file{color:#ccc}
				.dd pre .dd-call{color:#609;font-weight:bold}
				.dd pre .dd-call-active{color:#f00}
				.dd pre .dd-code{color:#000;font-size:10px}
				.dd pre .dd-area-1{color:#f00;font-weight:bold}
				.dd pre .dd-area-2{color:#090;font-weight:bold}
				.dd pre .dd-area-3{color:#00f;font-weight:bold}
				.dd pre .dd-area-4{color:#F0F;font-weight:bold}
			</style>
			<div class='dd' style='background-color:#fffffe;
				color:#000;
				border: 1px #ddd solid;
				margin:10px 0;
				padding:0 10px;'>
			<pre style='max-height:auto;overflow:auto'>
	");
	$result .= '<span class="dd-call">'.$code.'</span>';
	$result .= '<span class="dd-file">'.$caller['file'].'('.$caller['line'].')</span>'.PHP_EOL;
	$result .= _print_r($variable);
	$result .= PHP_EOL .'</pre></div>' .PHP_EOL;
	return $result;
}
/**
* 自定义对象格式化输出 print_r
* @param mixed $variable 待打印的变量
* @return string
* @ignore
*/
function _print_r($variable,$tab=1){
	if(is_object($variable) && !($variable instanceOf Closure)){
		$arr = json_decode(json_encode($variable),true);
		if(!empty($arr)){
			$variable = $arr;
		}
	}
	if(!is_array($variable)){
		return _echoValueByType($variable);
	}
	$result = '';
	$result .= '<span class="dd-area-'.$tab.'">[</span>'.PHP_EOL;
	$reg=0;
	foreach($variable as $k=>$v){
		for($i=0;$i<$tab;$i++){
			$result .= "    ";
		}
		if(is_array($v)){
			$result .= '"<span class="dd-array">'.$k.'</span>" => ';
			$result .= _print_r($v,$tab+1);
		}else if(is_object($v)){
			$result .= '"<span class="dd-object">'.$k.'</span>" => ';
			$result .= _print_r($v,$tab+1);
		}else{
			$result .= '"'.$k.'" <span class="dd-to">=></span> ';
			$result .= _echoValueByType($v);
		}
		if($reg!=count($variable)-1) $result .= ',';
		$result .= PHP_EOL;
		$reg++;
	}
	for($i=0;$i<$tab-1;$i++){
		$result .= "    ";
	}
	$result .= '<span class="dd-area-'.$tab.'">]</span>';
	return $result;
}
/**
* 按值的类型输出对应的颜色
* @param mixed $value
* @return string
* @ignore
*/
function _echoValueByType($value){
	$type = gettype($value);
	if(is_null($value)){
		$cssClass = "dd-blue";
		$value = 'null';
	}else if(is_bool($value)){
		$cssClass = "dd-blue";
		$value = $value ? 'true' : 'false';
	}else if(is_float($value) || is_integer($value)){
		$cssClass = "dd-num";
	}else if(is_string($value) && substr($value,0,10)=='{stdClass:'){
		$cssClass = "dd-stdclass";
	}else if(is_string($value) && in_array(strtoupper(explode(' ',$value)[0]),['SELECT','INSERT','UPDATE','DELETE'])){
		$cssClass = "";
        $value = \SqlFormatter::format($value);
	}else if(is_string($value)){
		$cssClass = "dd-string";
		//$value = '"'.htmlspecialchars($value).'" ('.strlen($value).')';
		$value = '"'.htmlspecialchars($value);
	}else if($value instanceOf Closure){
		$cssClass = "dd-code";
		$value = phpCodeColor(closureToString($value));
	}else if(is_object($value)){
		$cssClass = "dd-object-val";
        $cssClass = "dd-stdClass";
        $class = new ReflectionClass($value);
        $value = "new {$class->name}()";
    }else{
        $cssClass = '';
    }
	return '<span class="'.$cssClass.'">'.$value.'</span>';
}


/**
* php代码变色
* @param string $code_string 代码行
* @return string 变色后的字符串
*/
function phpCodeColor($code_string){
	$code_string = trim($code_string);
	$code_string = preg_replace('/(\/\*[\s\S]*\*\/)/U', '<span style="color:green">$1</span>', $code_string); //注释变色
	$code_string = preg_replace('/\/\/(.*)?\n/', '<span style="color:green">//$1</span>', $code_string); //注释变色
	$code_string = preg_replace("/(\'([^\']+)?\')/s", '<span style="color:#FF00FF">$1</span>', $code_string); //单引号
	$code_string = preg_replace('/(\$[\w]+)/', '<span style="color:#008080">$1</span>', $code_string); //变量变色
	$code_string = preg_replace('/([{|}|(|)])/', '<span style="font-weight:bold;color:#800;padding-right:2px">$1</span>', $code_string); //{}()加粗
	$code_string = preg_replace('/(\\\\\w+)/', '<span style="color:#C00">$1</span>', $code_string); //命名空间引用
	//关键字
	$keywords = [
		'new','function','private','public','static','as','namespace','use','class','extends',
		'foreach','for','while','if','else','namespace','return','echo',
		'require','require_once','include','include_once','isset','empty','is_file','method_exists'
	];
	foreach($keywords as $key){
		$partern = '/\b'.$key.'\b[\s]*/i';
		$code_string = preg_replace($partern,'<span style="color:blue">'.$key.' </span>',$code_string);
	}
	//函数
	$functions = [
		'ReflectionClass','print_r','var_dump','dump','dd',
	];
	foreach($functions as $func){
		$partern = '/\b'.$func.'\b[\s]*/i';
		$code_string = preg_replace($partern,'<span style="color:red">'.$func.' </span>',$code_string);
	}
	return $code_string;
}

/**
 * 字母累加
 *
 * @param string $str 大写字母字符串
 * @param integer $addInt 要增加的数值
 */
function incString(string $str, int $addInt = 1) 
{
	$str = strtoupper($str);
	$base = '0ABCDEFGHIJKLMNOPQRSTUVWXYZ';
	$dec = 0;
	$length = strlen($str);
	for ($i = 0; $i < $length; $i++) {
		$char = $str[$i];
		$pos = strpos($base, $char);
		$dec += $pos * pow(27, $length - $i - 1);
	}
	$dec += $addInt;

	$result = '';
	while ($dec > 0) {
		$remainder = $dec % 27;
		$result = $base[$remainder] . $result;
		$dec = (int)($dec / 27);
	}
    return $result ?: 'A'; 
} 

/**
 * 写错误日志
 *
 * 日志将保存到 config/站点.php['dir']['log'] 配置的目录下的 error下，
 * 日志文件按日分隔
 *
 * @param string|array|object $msg 错误内容,
 * @param string $type 类型,如 program 或 database
 * @param string:array $alone 单独日志, "y-m-d" 时间格式 或者 ['y-m','y-m-d'] 第一个为目录，第二个为文件名
 * @return void
 */
function tolog($msg,string $type,$alone=''){
    if(empty($type)){
        trigger_error('tolog($msg,$type) $type 必须指定一个名称,例 product', E_USER_ERROR);
    }
	$msg = json_encode($msg,JSON_UNESCAPED_UNICODE);
	$message = sprintf("[%s] %s\t.\r\n", date("Y-m-d H:i:s"), $msg);
	if(!is_dir(DIR_LOG)) mkdir(DIR_LOG );
	if(empty($alone)){
		$logPath = DIR_LOG.'/'.date("Y-m");
		if(!is_dir($logPath)) mkdir($logPath );
		error_log($message , 3, $logPath."/".$type."-".date ( "Y-m-d" ).".log" );
	}else{
		$logPath = DIR_LOG.'/'.$type;
		if(!is_dir($logPath)) mkdir($logPath );
		if(is_array($alone)){
			$logPath = $logPath.'/'.date($alone[0]);
			if(!is_dir($logPath)) mkdir($logPath );
			error_log($message , 3, $logPath."/" . date ( $alone[1] ).".log" );
		}else{
			error_log($message , 3, $logPath."/" . date ( $alone ).".log" );
		}
	}
}