import fsys;
import web.json;

namespace fsys.asar;

var canonicalize = ..fsys.path.canonicalize;
var lower = ..string.lower;
var push = ..table.push;

class writer{
	ctor(){ 
		this.allFiles ={}
		this.map = {}
	}; 
	add  = function(path){
		path = canonicalize(path);
		if( this.filter && !this.filter(path) ) return;
		
		var lowPath = lower(path);
		if(this.map[lowPath] ) return;
		this.map[lowPath] = true;
		
		push(this.allFiles,path);
		if( !..fsys.isDir(path) ) return;
		
		return ..fsys.enum(path,"*.*",this._fsysEnumCallback,false);
	};
	_fsysEnumCallback = function(dir,filename,fullpath){
		return this.add(fullpath)
	};
	remove = function(path){
		if(this.rootDirectory  && !..string.indexOf(path,":") ){
			path = ..io.joinpath(this.rootDirectory,path)
		}
		path = lower( canonicalize(path) )
		var dir = ..fsys.path.addBackslash(path)
		 
		var t = this.allFiles;
		var count = 0;
		for(i=#t;1;-1){
			var f = lower(t[i]);  
			if((f===path) || (..string.startWith(f,dir) )){
				..table.remove(t,i); 
				this.map[f] = null;
				count++;
			}
		} 
		return count;
	};
	clear = function(){
		this.allFiles = {};
		this.map = {};
		this.totalSize = null;
		this.info = null;
	};
	pack = function(asarpath){  
		for(path,size,progress in this.eachPack(asarpath)){}
	};
	eachPack = function(asarpath,buf){
		if(! ( asarpath ?..string.endWith(asarpath,".asar",true) ) )
			error("文件后缀名必须是 .asar",2);
			
		return ..fiber.generator(this.eachPackeFiber,asarpath,buf);
	};
	createInfo = function(){
		
		if(!#this.allFiles) {
			return null, "未添加任何文件";
		}
		
		..table.sort(this.allFiles,function(b){
			return #owner < #b;
		})
		 
		if(!this.rootDirectory){
			if(!..fsys.isDir(this.allFiles[1] ) ){
				this.rootDirectory = ..fsys.getParentDir( this.allFiles[1] );
			}
			else {
				this.rootDirectory = this.allFiles[1];
			}
		}
		
		var allFiles = this.allFiles;
		var info = {files={}};
		var path,fullpath,filename;
		var offset = 0;
		for(i=1;#allFiles;1){
			
			fullpath = allFiles[i];
			path = ..fsys.path.relative(fullpath,this.rootDirectory,false); 
			if(!#path){
				 continue;
			};
			
			if( path == "." ) continue;
			
			var t =  ..string.split(path,"\/");
		 
		 	var f = info.files;
			for(i=1;#t-1;1){
				if!(#t[i]) continue; 
				if(!f[ t[i] ]) {
					f[ t[i] ] = { files = {} };
				};
				
				f = f[ t[i] ].files;
			}
			
			filename = t[ #t ];
			if( f[ filename ] ) continue;
			if( ..fsys.isDir(fullpath) ){
				f[ filename ] = { files = {} };
			}
			else {
				var fi = {};
				fi.offset = tostring(offset);
				
				var file = ..io.open(fullpath,"rb");
				if(!file) {
					return null, "打开文件失败" + fullpath;
				}
				
				fi.size = file.size(1);
				offset = offset + fi.size;
				
				f[ filename ] = fi;
				file.close(); 
			} 
		}
		this.totalSize = offset;
		this.info = info;	
		return info;	
	};
	treeData = function(){
		this.lasterr = null;
		var info,err = this.createInfo();
		if(!info) return null,err;
		
		var treeData = {}; 
		var enumInfo;
		enumInfo = function(info,td,parent){  
			for(k,v in info.files){
				if( v.files ){
					var children = {text = k;path = v.path}
					push(td,children); 
					enumInfo(v,children,v.path); 
				}
				else {
					push(td,{text = k;path = v.path;size = v.size}); 
				}  
			}
		} 
		enumInfo(this.info,treeData);
		return treeData; 
	};
	eachPackeFiber = function(asarpath,buf){
		this.lasterr = null;
		var info,err = this.createInfo();
		if(!info){
			this.lasterr = ..string.concat("创建asar文件头时遇到错误,",err);
			return null;
		}
		 
		var asarfile = ..io.open(asarpath,"w+b");
		if(!asarfile){
			this.lasterr = "创建asar文件失败";
			return null;
		}
		
		var json = ..web.json.stringify(info);
		var header = {
			struct headerSize = {
				INT pickleOjectSize = 4;
				INT size;	
			}
			struct headerJson = {
				INT pickleOjectSize = #json - (#json %4) + 4 + 4;
				INT size = #json;
			}
		}
		header.headerSize.size = header.headerJson.pickleOjectSize + 4;
		asarfile.write(header);
		asarfile.write(json);
		asarfile.seek("set",header.headerSize.size+8/*..raw.sizeof(h.headerSize)*/)
		
		if(!buf) buf = ..raw.buffer(0xA00000);
		var allFiles = this.allFiles;
		var totalReadSize = 0;
		var path,fullpath;
		for(i=1;#allFiles;1){
			
			fullpath = allFiles[i]; 
			path = ..fsys.path.relative(fullpath,this.rootDirectory,false);
			if( path == "." ) continue;
			
			if(! ..fsys.isDir(fullpath) ){   
				var file = ..io.open(fullpath,"rb");
				if(!file) {
					asarfile.close();
					this.lasterr = "打开文件失败" + fullpath;
					return null;
				}
				
				while( 
	    			var readSize;
	    			readSize = file.readBuffer(buf);
	    			readSize
				) { 
					totalReadSize = totalReadSize + readSize;
					asarfile.writeBuffer(buf,readSize);
					..fiber.yield (path,totalReadSize,totalReadSize/this.totalSize);
				}  
				file.close(); 
			} 
		}
		
		asarfile.close();
	}
}

/**intellisense()
fsys.asar.writer() = 创建asar文件打包生成器\n!fsys_asar_writer.
!fsys_asar_writer.add(__) = 添加文件或目录到asar文件
!fsys_asar_writer.remove(__) = 在已添加的asar的文件列表中移除文件或目录\n返回删除的文件数目
!fsys_asar_writer.pack(.(输出路径) = 直接打包为asar文件\n输出路径必须使用.asar后缀\n如果需要获取进度,请使用eachPack迭代器
!fsys_asar_writer.eachPack(输出路径,缓冲区) = @for(path,size,progress in ??.eachPack("/test.asar")){
	__/*path为正在处理的文件相对路径\nsize是已处理的文件大小\nprogress是进度,0到1之间的小数\n输出路径必须使用.asar后缀,可选在参数@2中指定缓冲区*/
}
!fsys_asar_writer.filter = @.filter = function(path){ 
	return __/*参数为待添加文件路径,返回布尔值控制是否继续添加*/;
}
!fsys_asar_writer.totalSize = 在写入asar文件之前,totalSize会更新为所有文件的字节数总和
!fsys_asar_writer.allFiles = 所有准备添加到asar文件中的文件路径
!fsys_asar_writer.rootDirectory = 指定根目录,如果不指定则自动生成此属性\n\n自动生成规则为：\n所有添加的文件路径中取最短路径,如果是目录则设为根路径,\n否则将其父目录设为根路径
!fsys_asar_writer.lasterr = 执行成功此属性为空,否则是错误信息
!fsys_asar_writer.treeData() = 对已添加的文件生成可在treeview控件中显示的数据表
!fsys_asar_writer.createInfo() = 创建文件目录,成功返回this.info\n失败返回null,错误信息
!fsys_asar_writer.info = createInfo函数生成的asar文件目录
!fsys_asar_writer.clear() = 清空所有文件
end intellisense**/
