<?php
/*
<class>
	<cate></cate>
	<name>宜配MVC</name>
	<author>强哥 2019-12-28</author>
</class>
*/
final class mvc 
{
	/* 配置 */
	public static $cfg 	        = null;
	/* 配置 */
	public static $req 	        = [];
	/* 加载类名 */
	public static $URL_CLASS 	= null;
	public static $action 	= null;
	/* 加载类路径 */
	public static $URL_CLASS_PATH = null;
	/* 加载类方法 */
	public static $URL_METHOD     = null;
	public static $method     = null;
    /* 加载传参 */ 
    public static $URL_PARAMS     = null;
    public static $params     = null;
    /* 转写规则 */
    public static $URL_ROUTE     = null;
    /* 当前语言 */
    public static $LANGUAGE       = '';
    /* 实际执行路径 */
    public static $PATH_INFO       = '';
	/* 类地图 */
	private static $classmap      = null;
	/* 当前页面缓存时间 */
	private static $cachetime     = 0;
	/* 当前页面缓存时间 */
	public static $composer     = 0;
	/* 数据库连接列表  */
	public static $dbList = [];
	/* 运行时间统计  */
	public static $runtimes = [];
	
	/*
	<method>
		<name>Init(初始化)</name>
		<remark>自动加载,初始化类地图</remark>
		<author>强哥 2013-4-4</author>
	</method>
	*/
	private static function initMaps() 
	{
		// 自动装载 # 注册__autoload()函数
		spl_autoload_register ( array ( __CLASS__, 'autoload' ) );
		//公共类映射地图
		 #公共类映射地图
		self::$classmap = array (
			/* 公共函数库 */
			'action'         => self::$cfg['PATH_MVC'] . 'base/action/baseAction.php',
			'jsTreeAction'   => self::$cfg['PATH_MVC'] . 'base/action/jsTreeAction.php',
			'objectAction'   => self::$cfg['PATH_MVC'] . 'base/action/objectAction.php',
			'mod'            => self::$cfg['PATH_MVC'] . 'base/mod/baseMod.php',
			'objectMod'      => self::$cfg['PATH_MVC'] . 'base/mod/objectMod.php',
			'LibDb'          => self::$cfg['PATH_MVC'] . 'lib/LibDb.php',
			'LibTimer'       => self::$cfg['PATH_MVC'] . 'lib/LibTimer.php',
			'LibTree'        => self::$cfg['PATH_MVC'] . 'lib/LibTree.php',
			'LibLRTree'      => self::$cfg['PATH_MVC'] . 'lib/LibLRTree.php',
			'LibBit'         => self::$cfg['PATH_MVC'] . 'lib/LibBit.php',
			'LibExcel'       => self::$cfg['PATH_MVC'] . 'lib/LibExcel.php',
			'db'             => self::$cfg['ROOT'] . 'cmd/cmdLibs/lib/db.php',
			'pdoHelper'      => self::$cfg['ROOT'] . 'cmd/cmdLibs/lib/pdoHelper.php',
			'sqler'          => self::$cfg['ROOT'] . 'cmd/cmdLibs/lib/sqler.php',
			'SqlFormatter'   => self::$cfg['ROOT'] . 'cmd/cmdLibs/lib/SqlFormatter.php',
		);
        if( isset(self::$cfg['CLASSMAP'])) {
            self::$classmap = array_merge(self::$classmap, self::$cfg['CLASSMAP']);
        }
        //加载 composer
        self::$composer =  require( self::$cfg['PATH_MVC'] . 'composer/vendor/autoload.php');
	}
	
	/*
	<method>
		<name>autoload(自动装载)</name>
		<remark></remark>
		<Parameter>
			<para name="$var" type="string">路径/类/方法 (如 pro/test/do1)</para>
		</Parameter>
		<author>强哥 2013-4-4</author>
	</method>
	*/
	public static function autoload($class)
	{
		/* 判断地图(类)是否已加载 */
		if (isset(self::$classmap[$class] ))
		{
			/* 自动加载主库 */
			$classfile = self::$classmap [$class];
			require_once ($classfile);
		}else{
			$classfile = self::$cfg['ROOT'] . 'mod/'. $class .'.php';
			if(file_exists($classfile)){
				require_once( $classfile );
			}else{
				throw new \Exception($classfile.' 文件不存在');
			}
		}
	}

    /**
     * 匹配网址路由规则
	  * 自定义规则
	  * 规则 => [目标，缓存时间]
	  * 例: /admin/index.php/test/123?s=1  规则 'test\/(\d+)$'=> ['test/index',0] 将 test/数字 转到 test/index 可以用 $_GET[preg_0] 获取到 123
	  * 例: /admin/index.php/test/123abc?s=1  规则 'test\/(\d+)(\w+)'=> ['test/index',0] 将 test/数字 转到 test/index 可以用 $_GET[preg_route][0] 获取到 123 $_GET[preg_route][1] 获取到 abc
	  * 缓存时间 单位 秒，0 为不缓存, 9 为永久缓存.
			'ROUTE' => [
				'test\/(\d+)(\w+)'=> ['test/index',0]
			],
     */
    private static function route($PathInfo){
		if(isset(self::$cfg['ROUTE']))
		{
			foreach(self::$cfg['ROUTE'] as $pattern=>$replace)
			{
				$pattern = '/'.$pattern.'/i';
				preg_match($pattern, $PathInfo,$m);
				if($m){
					array_shift($m);
					foreach($m as $v){
						$_GET['preg_route'][] = self::specialchars($v);
					}
					$PathInfo = $replace[0];
					self::$cachetime = $replace[1];
					self::$URL_ROUTE = $pattern . ' 转至 ' . $replace[0];
				}
			}
		}
        return $PathInfo;
    }

	/*
	<method>
		<name>AnalyzeUrl(分析网址)</name>
		<Parameter>
			<para name="$ClassMethod" type="string">路径/类/方法 (如test/do1)</para>
			<para name="$QueryString" type="string">网址传参 ( 如 b=2&c=3&a=1 )</para>
		</Parameter>
		<exp.>
			$ClassMethod = 'test/do1';
			$QueryString = 'b=2&c=3&a=1';
			$ReturnData  = self::AnalyzeUrl($ClassMethod, $QueryString);
			// array(
			//	'Class' => 'test', 'Func' => 'do1',
			//	'Params' => array ( 'b' => 2, 'c' => 2, 'a' => 1)
			//);
		</exp.>
		<return>
			@return array(
				'ClassPath'=>'路径','Class'=>'类名','Func'=>'类模块'
			);
		</return>
		<author>强哥 2019-12-30</author>
	</method>
	*/
    public static function analyze_url($PathInfo, $QueryString)
	{
        self::$PATH_INFO = self::route($PathInfo);
		self::$PATH_INFO = trim(self::$PATH_INFO, '/');
		/* 对Url网址进行拆分 */
		$path_info_arr = explode ( '/', self::$PATH_INFO );
        /* 返回类名 */
        $Class 	   = array_shift ( $path_info_arr );
        /* 返回方法名 */
        $Func 	   = array_shift ( $path_info_arr );
		/* return 返回包含路径, 类名, 类模块, 和传参数组 */
		return array (
			'Class'     => $Class,
			'Func'      => $Func,
		);
	}

	/*
	<method>
		<name>处理配置文件</name>
		<remark>
		配置文件结构
		项目\config\common.php
		项目\config\database.php
		项目\config\前端目录.php
		</remark>
	</method>
	*/
	public static function loadConfig(array $comCfg, string $dir) 
	{
		$dir = end(explode('/',str_replace ( '\\', '/',$dir)));
		$comCfg['SITE_ROOT'] = $comCfg['ROOT'].$dir.'/'; //子项目主目录
		$comCfg['SITE_PATH_CACHE'] = $comCfg['SITE_ROOT'].'cache/'; //子项目缓存目录
		$comCfg['SITE_PATH_TPL'] = $comCfg['SITE_ROOT'].'tpl/'; //子项目模板目录
		self::$cfg = $comCfg;
		$project  = require_once($comCfg['ROOT'].'config/'.$dir.'.php'); //注意:这里路径与文件不对不会报错
		$database = require_once($comCfg['ROOT'].'config/database.php'); //注意:这里路径与文件不对不会报错
		//self::$cfg = array_merge_recursive($comCfg, $database, $project);
		self::$cfg = array_merge($comCfg, $database, $project);
		$lib_database = require_once($comCfg['ROOT'].'cmd/cmdLibs/config_database.php'); //注意:这里路径与文件不对不会报错
		self::$cfg['libs']['database'] = $lib_database;
		require_once( self::$cfg['PATH_MVC'] . 'lib/LibFc.php'); //加载通用函数
		
	}

	/*
	<method>
		<name>创建并初始化MVC</name>
		<param comCfg>mvc配置内容</name>
		<param dir>项目配置目录</name>
		<remark>执行MVC函数,调用类模块,并将参数传递过去</remark>
		<return>@return void</return>
		<author>强哥 2013-4-4</author>
	</method>
	*/
	public static function create(array $comCfg, string $dir) 
	{
		/* 处理配置 */
		self::loadConfig($comCfg,$dir);
		/*定义一些常量*/
		define('DEBUG', self::$cfg['DEBUG']);
		self::runtimeAdd('开始'); //统计执行时间,在加载config之后，需要获取 DEBUG 配置

		/* 格式化_GET _POST */
		self::specialArray($_GET);
		self::specialArray($_POST);
		/* 初始化类地图 */
		self::initMaps();
		/* 加载通用函数库 */
		require self::$cfg['ROOT'] . 'cmd/cmdLibs/lib/func.php';
		//错误处理
        error_reporting(0); //使用自己的错误处理，禁用系统的
        if(IS_CLI){
            ini_set('xdebug.remote_enable',0);
            register_shutdown_function('\mvc::rare_shutdown_catch_error_cli');
        }else if(DEBUG){
            //bebug 模式，禁用opcache
            if(function_exists("opcache_reset")){
                opcache_reset();
            }
            register_shutdown_function('\mvc::rare_shutdown_catch_error_debug');
        }else{
            register_shutdown_function('\mvc::rare_shutdown_catch_error');
        }
	}


	/**
	 * 生产一个错误
	 * @param $type = debug,cli,product 分别为  调试模式,控制台,正式环境
	 * @return void
	 * @example $error  [
            'type'=> E_ERROR,
            'file'=> $caller['file'],
            'line'=> $caller['line'],
            'message' => $message,
            'backtrace' => $backtrace,
            'sql' => $this->lastSql,
        ];
	 */
    public static function errorMake($error){
		if(empty($error['type'])) $error['type'] = E_ERROR;
        if(DEBUG){
            $content = \mvc::errorReport('debug',$error) ?: '';
            if(IS_CLI){
                $f = fopen(DIR_RUNTIME.'/error.html', 'w');
                fwrite($f, $content);
                fclose($f);
                echo "\r\n==============================================\r\n";
                echo "FILE:".$error['file']."\r\n";
                echo "LINE:".$error['line']."\r\n";
                echo $error['message'];
                echo "\r\n----------------------------------------------\r\n";
            }else{
                echo $content;
            }
            die();
        }else{
            \mvc::errorReport('product',$error);
        }
    }  
    
    /**
     * 格式化错误信息
     * @param $type = debug,cli,product 分别为  调试模式,控制台,正式环境
     * @return void
     */
    public static function errorReport($type,$error){
        return require(self::$cfg['ROOT']."/cmd/cmdLibs/rare_error/".$type.".php");
    }   
    
    public static function rare_shutdown_catch_error_debug($error=null){
        $error = $error ?: error_get_last();
        $content = self::errorReport('debug',$error);
        if($content){
            echo $content;
            exit();
        }
    }   
    
    /**
     * 非Debug错误处理
     * @return void
     */
    public static function rare_shutdown_catch_error($error=null){
        $error = $error ?: error_get_last();
        $content = self::errorReport('product',$error);
        if($content){
            echo $content;
            exit();
        }
    }
    
    /**
     * 控制台任务错误处理
     * @return void
     */
    public static function rare_shutdown_catch_error_cli($error=null){
        $error = $error ?: error_get_last();
        $content = self::errorReport('cli',$error);
        if($content){
            echo $content;
            exit();
        }
    }
	
	/*
	<method>
		<name>Execute(执行MVC)</name>
		<param comCfg>mvc配置内容</name>
		<param dir>项目配置目录</name>
		<remark>执行MVC函数,调用类模块,并将参数传递过去</remark>
		<return>@return void</return>
		<author>强哥 2013-4-4</author>
	</method>
	*/
	public static function execute()
	{
		self::$cachetime =0;//禁用缓存
		/* 分拆网址 */
		$Info = self::analyze_url ( $_SERVER['PATH_INFO'], $_SERVER['QUERY_STRING'] );
		self::$URL_CLASS_PATH = isset($Info ['ClassPath']) ? $Info ['ClassPath'] : '' ;
		self::$URL_CLASS 	  = ( $Info ['Class'] == '' ) ? self::$cfg['ROOT_DEFAULT_FILE'][0] : $Info ['Class'];
		self::$URL_METHOD     = ( $Info ['Func']  == '' ) ? self::$cfg['ROOT_DEFAULT_FILE'][1] : $Info ['Func'];
		self::$URL_PARAMS     = $_GET;
		self::$action = self::$URL_CLASS;
		self::$method = self::$URL_METHOD;
		self::$params  = self::$URL_PARAMS;
		$className = self::$URL_CLASS.'Action';
		//读缓存
		if(self::$cachetime > 0){
			LibCache::GetPageCache( self::$URL_CLASS_PATH,$className,self::$URL_METHOD,serialize(self::$URL_PARAMS), self::$cachetime);
		}
		// 执行调用
		if (isset(self::$classmap[$className] )){ //查看地图中是否存在action
			$classfile = self::$classmap[$className];
		}else{
			if(self::$action=='core'){
				$classfile = self::$cfg['PATH_MVC'] . 'core/mvc_action.php';
			}else{
				$classfile = self::$cfg['PATH'] . 'action/' . $className . '.php';
			}
		}
		if(substr(self::$URL_METHOD,-5)=='.html'){
			require_once( self::$cfg['PATH'] . 'tpl/'. self::$URL_CLASS .'/'. substr(self::$URL_METHOD,0,-5).'.php');
			exit();
		}else if(file_exists($classfile)){
			require_once( $classfile );
		}else{
			throw new \Exception($classfile.' 文件不存在');
		}
		self::runtimeAdd('创建类: '. $className);
		
		$view = new $className();
		/* 检查类模块方法是否存在 */
		if (false == method_exists ( $className, self::$URL_METHOD )) {
			throw new \Exception ( $classfile.'<br>'.$className.' 类中方法 '.self::$URL_METHOD.' 不存在' );
		}
		$params = array ();
		if (! empty ( self::$URL_PARAMS )) 
		{
			if($_SERVER ['QUERY_STRING']!=''){
				/* 反射, 获取参数对应关系 */
				$reflector   = new ReflectionMethod ( $className, self::$URL_METHOD );
				$func_params = $reflector->getParameters ();
				foreach ( $func_params as $k => $v ) 
				{
					$pv = self::$URL_PARAMS [$v->name];
					$params [$k] = $pv;
				}
			}else{
				$params = self::$URL_PARAMS;
			}
		}
		/* 调用类模块 */
		//ob_start ();
		//ob_end_clean ();
		call_user_func_array ( array ( $view, self::$URL_METHOD ), $params );
		//$out  =  ob_get_contents();
		//ob_end_flush();
		self::runtimeAdd('调用 ' . $className . ' 的 ' . self::$URL_METHOD . ' 方法');
		/*
		if(DEBUG && DEVELOP && mvc::$cfg['RUNTIME_SHOW_CONSOLE']) {
			list($usec, $sec) = explode(" ", microtime());
			$msec = ((float)$usec + (float)$sec) * 1000;

			$runtimesFile = DIR_RUNTIME . 'runtimes.json';
			$runtimesJson = file_exists($runtimesFile) ? json_decode(file_get_contents($runtimesFile),true) : [];
			$key = $msec.'.'.md5($_SERVER['PATH_INFO'] . $_SERVER['QUERY_STRING']);
			$runtimesJson[$key] = [
				'className'=> $className,
				'function'=> $URL_METHOD,
				'params'=> $params,
				'runtimes'=> \mvc::runtimeCalc()
			];
			rsort($runtimesJson);
			file_put_contents($runtimesFile, json_encode($runtimesJson, JSON_UNESCAPED_UNICODE));
		}
		*/
		//写缓存
		if(self::$cachetime > 0){
			LibCache::SetPageCache(self::$URL_CLASS_PATH,$className,self::$URL_METHOD,serialize(self::$URL_PARAMS),$out);
		}
	}
	

	/**
	  * 对 GET POST REQUEST 数组参数进行转义
	  */
	private static function specialArray(&$arr){
		if($arr) foreach($arr as $k=>$v){
			if(is_array($v)){
				self::specialArray($arr[$k]);
			}else{
				$arr[$k] = self::specialchars($v);
    			self::$req[$k] = $arr[$k];
			}
		}
	}

	/**
	  * 对数据进行转义
	  */
	private static function specialchars($string)
	{
		return htmlspecialchars($string, ENT_QUOTES);
	}

	//mvc信息输出
	public static function mvc_echo($msg)
	{
		echo '<div style="clear:both;word-wrap: break-word;font-family: Courier New,Arial;font-size: 14px;border: 2px solid #c00;border-radius: 5px;margin:20px;padding: 20px;background-color:#FFFFE1">';
		echo $msg;
		echo '</div>';
	}


	//添加运行时间统计
	public static function runtimeAdd($remark = '')
	{
		if(!DEBUG){
			return;
		}
		// 获取当前时间戳和微秒数
		list($usec, $sec) = explode(" ", microtime());

		// 将微秒转换为毫秒
		$msec = round($usec * 1000);

		$backtrace = debug_backtrace();
		$caller = $backtrace[1];
		$timeMsec = ((float)$usec + (float)$sec) * 1000;

		$count = count(self::$runtimes);
		$timeTotalMsec = $count > 0 ? $timeMsec - self::$runtimes[$count - 1]['timeMsec'] : 0;
		self::$runtimes[] = [
			'timeTotalMsec' => $timeTotalMsec,
			'timeMsec' => $timeMsec,
			'class' => $caller['class'],
			'type' => $caller['type'],
			'function' => $caller['function'],
			'call' => $caller['class'] . $caller['type'] . $caller['function'],
			'file' => $caller['file'],
			'line' => $caller['line'],
			'remark' => $remark,
		];
	}

	//计算运行时间统计
	public static function runtimeCalc()
	{
		if (!DEBUG) {
			return;
		}
		$result = ['items'=>[]];
		list($usec, $sec) = explode(" ", microtime());
		$timeMsec = ((float)$usec + (float)$sec) * 1000;
		$timeAllMsec =  round($timeMsec - self::$runtimes[0]['timeMsec']);
		$timeAllSec =  floor($timeAllMsec / 1000);
		$days = floor($timeAllSec / (3600 * 24));
		$hours = floor(($timeAllSec % (3600 * 24)) / 3600);
		$minutes = floor(($timeAllSec % 3600) / 60);
		$seconds = $timeAllSec % 60;
		$millisecond = floor($timeAllMsec % 1000);
		$timeAllFullText =  '';
		if ($days) $timeAllFullText    .=  $days . ' 天 ';
		if ($hours) $timeAllFullText   .=  $hours . ' 小时 ';
		if ($minutes) $timeAllFullText .=  $minutes . ' 分钟 ';
		$timeAllFullText .=  $seconds . ' 秒 ';
		$timeAllFullText .=  $millisecond . ' 毫秒';

		$count = count(self::$runtimes);
		for ($i = 0; $i < $count; $i++) {
			$v = self::$runtimes[$i];
			$callFile = str_replace('\\', '/', $v['file']);
			$callFile = str_replace(\mvc::$cfg['ROOT'], '/', $callFile);
			$classFile = '';
			$startLine = '';
			$endLine = '';
			if ($v['class']) {
				$reflectionClass = new ReflectionClass($v['class']);
				$reflectionMethod = $reflectionClass->getMethod($v['function']);
				$classFile = $reflectionMethod->getFileName();
				$startLine = $reflectionMethod->getStartLine();
				$endLine = $reflectionMethod->getEndLine();
				$classFile = str_replace('\\', '/', $classFile);
				$classFile = str_replace(\mvc::$cfg['ROOT'], '/', $classFile);
			}

			$v['callFile'] = $callFile;
			$v['callLine'] = $v['line'];
			$v['classFile'] = $classFile;
			$v['funtionStartLine'] = $startLine;
			$v['funtionEndLine'] = $endLine;
			$v['timeTotalMsec'] = round($v['timeTotalMsec']);
			$v['timeTotalSec'] = round($v['timeTotalMsec'] / 1000);
			$result['items'][] = $v;
		}
		$result['timeAllMsec'] = $timeAllMsec;
		$result['timeAllSec'] = $timeAllSec;
		$result['timeAllFullText'] = $timeAllFullText;
		return $result;
	}

	/**
	 * 显示运行时间统计
	 *
	 * @param boolean $returnHtml 默认 false 直接输出html 如果为 true 则返回html文本,不直接输出 
	 * @param array $runtimes  来自 runtimeCalc() 如果不指定默认为null取当前结果
	 * @return void|string
	 */
	public static function runtimeView(bool $returnHtml=false, array $runtimes = null)
	{
		if (!DEBUG) {
			return;
		}
		if(is_null($runtimes)) $runtimes = self::runtimeCalc();
		$html = '<table class="runtime" cellspacing="0" cellpadding="0">
			<thead>
				<th>执行文件</th>
				<th>行</th>
				<th>调用方法</th>
				<th>描述</th>
				<th>执行时间(秒)</th>
				<th>毫秒</th>
				<th>类文件</th>
				<th>行</th>
			<thead>
			<tbody>
		';
		$count = count($runtimes['items']);
		for($i=0;$i < $count;$i++){
			$v = $runtimes['items'][$i];
			$html .= '<tr>';
			$html .= '<td>' . $v['callFile'] . '</td>';
			$html .= '<td>' . $v['callLine'] . '</td>';
			$html .= '<td>' . $v['call'] . '</td>';
			$html .= '<td>' . $v['remark'] . '</td>';
			$html .= '<td>' . $v['timeTotalSec'] . '</td>';
			$html .= '<td>' . round($v['timeTotalMsec'] % 1000) . '</td>';
			$html .= '<td>' . $v['classFile'] . '</td>';
			$html .= '<td>' . $v['funtionStartLine'] . '</td>';
			$html .= '</tr>';
		}
		$colspan = 8;
		$html .= '<tr class="sum">
				  	<td>总执行时间: <strong>' . $runtimes['timeAllFullText'] . '</strong></td>
				    <td colspan="' . ($colspan-1) . '"><runcall></runcall></td>
				  </tr>';
		//$html .= '<tr><td colspan="' . $colspan . '">总秒数: ' . $runtimes['timeAllSec'] . '</td></tr>';
		//$html .= '<tr><td colspan="' . $colspan . '">总毫秒: ' . $runtimes['timeAllMsec'] . '</td></tr>';
		$html .= "</tbody>";
		$html .= "</table>";
		if($returnHtml)
			return $html;
		else
			echo $html;
	}
}