//ntp 网络时间同步
import time.performance;
import wsock.udp.client;
import util.metaProperty;

namespace time.ntp;

class NtpTime {
    ctor(){
		this = {
			INT seconds;
			INT fraction;  
			@_metaProperty
		}
		
		this.time = ..time(,"!%c");
    } 
}

NtpTime._metaProperty = ..util.metaProperty( 
	time = {
		_get = function(){  
			if( owner.seconds === null ) return;
			var tm = ..time( ..wsock.htonl(owner.seconds) - 2208988800/*1900~1970*/,"!%c" ); 
			if(!tm) return;
			tm.milliseconds = ..wsock.htonl(owner.fraction) / 4294967.296/* (2**32/1000) */; 
			return tm;
		}
		_set = function( value ){
			if(!..time.istime(value)) error("属性值不是time对象",2);
			owner.fraction = ..wsock.ntohl( value.milliseconds * 4294967.296/* (2**32/1000) */ ); 
			owner.seconds = ..wsock.ntohl( tonumber(value) + 2208988800/*1900~1970*/ ); 
		} 	
	}; 
	size64 = function(){
		return ..math.size64(owner.fraction,owner.seconds);
	};
	_tostring = function(){
		return tostring(owner.time)
	};
)

var NtpTime = NtpTime;
class NTPData {
    BYTE header = 2#00011011;
    BYTE stratum = 1; 
    BYTE poll = 1;  
    BYTE precision = 1; 
    INT rootDelay;
    INT rootDispersion;
    INT referenceIdentifier; 
    struct referenceTimestamp = NtpTime();
    struct originateTimestamp = NtpTime();
    struct receiveTimestamp = NtpTime();
    struct transmitTimestamp = NtpTime(); 
}

getData = function(server,port,timeout,sendData){
	var udpClient = ..wsock.udp.client(); 
	if(!server){
		server = {"time1.aliyun.com";"time1.cloud.tencent.com";"time.apple.com"}
	}
	
	var data = NTPData(); 
	if(type(server)===type.table){
		udpClient.setTimeouts( timeout : 2000,timeout : 2000);
		
		var connected;
		for(i=1;#server;1){ 
			if( udpClient.connect(server[i],port : 123) ){
				data.server = server[i];
				connected = true; 
				break ;
			}
		}
		
		if(!connected){
			return;
		}
	}
	else {
		data.server = server;
		udpClient.setTimeouts( timeout : 3000,timeout : 3000);
		if( !udpClient.connect(server,port : 123) ){ 
			return null,..wsock.err.lasterr();
		}
	}
	
	if(sendData) ..table.assign( data,sendData);
	
	var tick = ..time.performance.tick(); 
	if( !udpClient.sendto( ..string.concat(..raw.tostring(data),data[["authenticator"]]) ) ){
		return null,wsock.err.lasterr();
	} 
	
	var recvbuf = udpClient.recvfrom()
	if( !recvbuf ){
		return null,..wsock.err.lasterr();
	}
	udpClient.close();
	
	..raw.convert(recvbuf,data);
	var r = data.receiveTimestamp.time;
	var t = data.transmitTimestamp.time;
	if(!(r&&t)) return null;
	
	var c = (tonumber(t) - tonumber(r))*1000 + ( t.milliseconds - r.milliseconds); 
	data.delayMilliseconds = ( ..time.performance.tick() - tick - c ) / 2;
	
	var s,ms = ..math.modf( ( (data.delayMilliseconds + t.milliseconds) / 1000)  ); 
	t.milliseconds = 0;
	data.time = t.addsecond( s );
	data.time.milliseconds = ms * 1000;
	
	if(data.time.year < 2019){
		var sec = tonumber( ..time.iso8601("2036-02-07 06:28:15") )
		data.time.addsecond(sec);
	}
	return data;
}

setprivilege('SeSystemtimePrivilege',true)
updateSystemTime = function(tm){
    if(tm){ return ::Kernel32.SetSystemTime(tm.utc()) }

	var data = getData();
	if(data && data.time ){
		::Kernel32.SetSystemTime(data.time);
		data.time.$updateDefault = data.time.update;
		data.time.update = function() { owner.$updateDefault();updateSystemTime(owner) }
		return data.time;
	}
}

/**intellisense(time.ntp)
getData(.("ntp服务器",端口,超时) = 除参数@1之外所有参数可选\n参数@1也可以用一个数组指定多个服务器,\n因为网络的不稳定性,该函数可能失败返回null
getData() = !wsockNtpData.
updateSystemTime() = 使用NTP服务器获取的时间校正系统时间,\n可选在参数中使用time对象指定要设置的时间\n需要以管理员权限启动当前进程,\n!time.
end intellisense**/

/**intellisense(!wsockNtpData)
time = 报文返回后计算出的当前时间\n!time.
header = 报文头
stratum = 时钟的层数，定义了时钟的准确度。层数为1的时钟准确度最高，从1到16依次递减
poll = 轮询时间，即发送报文的最小间隔时间 
precision = 时钟的精度 
rootDelay = 到主参考时钟的总往返延迟时间
rootDispersion = 本地时钟相对于主参考时钟的最大误差
referenceIdentifier = 参考时钟源的标识
referenceTimestamp = 最后一次更新的时间
originateTimestamp = 客户端报文发送时间
receiveTimestamp = NTP服务器接收报文时间
transmitTimestamp = NTP服务器报文发送时间
delayMilliseconds = 服务端到客户端延时值\n毫秒
referenceIdentifier.time = !time.
referenceTimestamp.time = !time.
originateTimestamp.time = !time.
receiveTimestamp.time = !time.
transmitTimestamp.time = !time.
end intellisense**/
