// List files in one or more Synchronet v3.19 file base directories
// Note: '-strip' option requires v3.20 or later

load("file_size.js");
load("string.js");

"use strict";

function datestr(t)
{
	if(date_fmt)
		return strftime(date_fmt, t);
	return system.datestr(t);
}

function archive_date(file)
{
	try {
		var list = Archive(file).list();
	} catch(e) {
		return file_date(file);
	}
	var t = 0;
	for(var i = 0; i < list.length; i++)
		t = Math.max(list[i].time, t);
	return t;
}

function is_user_accessible_dir(dir)
{
	if(user.number === 0)
		return true;
	return user.compare_ars(file_area.dir[dir].ars)
		&& user.compare_ars(file_area.dir[dir].download_ars);
}

var out = js.global.stdout ? stdout : console;
var sort_prop;
var exclude_list = [];
var options = { sort: false, case_sensitive: true };
var json_space;
var detail = -1;
var dir_list = [];
var filespec = "";
var extdesc_prefix;
var both_desc = false;
var since = 0;
var props = [];
var fmt;
var name_len = 12;
var date_fmt;
var size_fmt = 2;
var size_fmts = [
//	 bytes,		precision,	minlen
	[undefined, undefined,	10 	],
	[false,		0,			5 ],
	[false,		1,			6 ],
	[false,		2,			7 ],
	[1024,		0,			5 ],
	[1024,		1,			6 ],
	[1024,		2,			7 ],
	[1024*1024,	0,			5 ],
	[1024*1024,	1,			6 ],
	[1024*1024,	2,			7 ],
	[1024*1024,	3,			8 ],
	[true,		undefined,	7 ],
	[false,		undefined,	7 ],
	];
for(var i = 0; i < argc; i++) {
	var arg = argv[i];
	if(arg[0] == '-') {
		var opt = arg;
		while(opt[0] == '-')
			opt = opt.slice(1);
		if(opt == '?' || opt.toLowerCase() == "help") {
			writeln("usage: [-options] [dir-code [...]] [include-filespec=*]");
			writeln("options:");
			writeln("  -all            list files in all libraries/directories");
			writeln("  -lib=<name>     list files in all directories of specified library");
			writeln("  -ex=<filespec>  add filename or spec to excluded filename list");
			writeln("  -hdr            include header for each directory");
			writeln("  -sort[=prop]    enable/configure file sorting");
			writeln("  -i              perform case-insensitive global sorting");
			writeln("  -reverse        reverse the sort order");
			writeln("  -out=<file>     write output to specified filename (rather than stdout)");
			writeln("  -json[=spaces]  use JSON formatted output");
			writeln("  -utf8           use UTF-8 encoded output (instead of CP437)");
			writeln("  -strip          strip Ctrl-A (attribute) sequences from output");
			writeln("  -user=<name>    specify user to use for directory access checks");
			writeln("  -new=<days>     include new files uploaded in past <days>");
			writeln("  -p=<list>       specify comma-separated list of property names to print");
			writeln("  -cdt            include credit value instead of file size");
			writeln("  -desc           include file summary / description");
			writeln("  -ext            include extended file descriptions");
			writeln("  -ext=<prefix>   specify extended file description prefix");
			writeln("  -fmt=<fmt>      specify output format string (printf syntax)");
			writeln("  -date=<fmt>     specify date/time display format (strftime syntax)");
			writeln("  -size=<fmt>     specify size/byte display format (0-" + (size_fmts.length - 1) + ")");
			writeln("  -name=<len>     specify filename length (default: 12)");
			writeln("  -pad            pad filename with spaces");
			writeln("  -adate          use latest archived file date/time instead of file time");
			writeln("  -v              increase verbosity (detail) of output");
			exit(0);
		}
		if(opt.indexOf("ex=") == 0) {
			exclude_list.push(opt.slice(3));
			continue;
		}
		if(opt.indexOf("lib=") == 0) {
			var lib = opt.slice(4);
			if(!file_area.lib[lib]) {
				alert("Library not found: " + lib);
				exit(1);
			}
			for(var j = 0; j < file_area.lib[lib].dir_list.length; j++) {
				var dir = file_area.lib[lib].dir_list[j].code;
				if(is_user_accessible_dir(dir))
					dir_list.push(dir);
			}
			options.auto = true;
			continue;
		}
		if(opt.indexOf("new=") == 0) {
			var days  = parseInt(opt.slice(4), 10);
			since = time() - (days * (24 * 60 * 60));
			continue;
		}
		if(opt.indexOf("pre=") == 0) {
			precision = parseInt(opt.slice(4), 10);
			continue;
		}
		if(opt.indexOf("date=") == 0) {
			date_fmt = opt.slice(5);
			continue;
		}
		if(opt.indexOf("name=") == 0) {
			name_len = parseInt(opt.slice(5), 10);
			continue;
		}
		if(opt.indexOf("sort=") == 0) {
			sort_prop = opt.slice(5);
			options.sort = true;
			continue;
		}
		if(opt.indexOf("user=") == 0) {
			user.number = system.matchuser(opt.slice(5));
			if(user.number < 1)
				alert("Invalid user specified: " + opt.slice(5));
			continue;
		}
		if(opt.indexOf("out=") == 0) {
			out = new File(opt.slice(4));
			if(!out.open("w")) {
				alert("Error " + out.error + " opening " + out.name);
				exit(1);
			}
			continue;
		}
		if(opt == "i") {
			options.case_sensitive = false;
			continue;
		}
		if(opt[0] == 'v') {
			var j = 0;
			while(opt[j++] == 'v')
				detail++;
			continue;
		}
		if(opt.indexOf("ext=") == 0) {
			extdesc_prefix = opt.slice(4);
			detail = FileBase.DETAIL.EXTENDED;
			continue;
		}
		if(opt == "ext") {
			if(detail == FileBase.DETAIL.NORM)
				both_desc = true;
			detail = FileBase.DETAIL.EXTENDED;
			continue;
		}
		if(opt == "desc") {
			if(detail >= FileBase.DETAIL.EXTENDED)
				both_desc = true;
			else
				detail = FileBase.DETAIL.NORM;
			continue;
		}
		if(opt.indexOf("p=") == 0) {
			props = props.concat(opt.slice(2).split(','));
			continue;
		}
		if(opt == "json") {
			fmt = "json";
			json_space = 4;
			continue;
		}
		if(opt.indexOf("json=") == 0) {
			fmt = "json";
			json_space = parseInt(opt.slice(5), 10);
			continue;
		}
		if(opt.indexOf("fmt=") == 0) {
			fmt = opt.slice(4);
			continue;
		}
		if(opt.indexOf("size=") == 0) {
			opt = opt.slice(5);
			size_fmt = parseInt(opt, 10);
			if(isNaN(size_fmt))
				size_fmt = opt;
			else if(size_fmt >= size_fmts.length)
				size_fmt = 0;
			continue;
		}
		if(opt == "all") {
			for(var dir in file_area.dir) {
				if(is_user_accessible_dir(dir))
					dir_list.push(dir);
			}
			continue;
		}
		options[opt] = true;
		continue;
	}
	if(file_area.dir[arg])
		dir_list.push(arg);
	else
		filespec = arg;
}
if(fmt != "json") {

	if(options.hdr || props.length)
		detail = Math.max(FileBase.DETAIL.MIN, detail);

	if(!props.length) {
		var f = "%-" + name_len + "s";
		var offset = name_len;
		props.push("name");
		if(detail >= FileBase.DETAIL.MIN) {
			props.push("size");
			if(typeof size_fmt == 'string')
				f += size_fmt;
			else {
				f += " %" + size_fmts[size_fmt][2] + "s";
				offset += size_fmts[size_fmt][2] + 1;
			}
		}
		if(detail > FileBase.DETAIL.EXTENDED) {
			props.push("time");
			f += " %-8s";
			offset += 9;
		}
		if(detail >= FileBase.DETAIL.NORM) {
			props.push("desc");
			f += " %-58s";
		}
		if(detail >= FileBase.DETAIL.EXTENDED) {
			props.push("extdesc");
			if(extdesc_prefix === undefined)
				extdesc_prefix = format("%*s| ", offset - 1, "");
			if(both_desc)
				f += "%s";
		}
		if(!fmt)
			fmt = f;
	}
	if(!fmt)
		fmt = "%s ".repeat(props.length);
}

if(!dir_list.length) {
	var code;
	while(!file_area.dir[code] && !js.terminated) {
		for(var d in file_area.dir)
			writeln(d);
		code = prompt("Directory code");
	}
	dir_list.push(code);
}

log("Reading " + dir_list.length + " file areas...");
var dir_code;
var file_list = [];
for(var i = 0; i < dir_list.length; i++) {
	dir_code = dir_list[i];
	var base = new FileBase(dir_code);
	if(!base.open())
		throw new Error(base.last_error);
	if(detail < 0)
		file_list = file_list.concat(base.get_names(filespec, since, options.sort));
	else {
		var list = base.get_list(filespec, detail, since, options.sort);
		for(var j = 0; j < list.length; j++) {
			list[j].dir = dir_code;
			if(list[j].extdesc) {
				list[j].extdesc = "\n" + truncsp(list[j].extdesc);
				js.flatten_string(list[j].extdesc);
			}
			if(options.adate)
				list[j].time = archive_date(base.get_path(list[j]));
			if(options.cdt)
				list[j].size = list[j].cost;
		}
		file_list = file_list.concat(list);
	}
	base.close();
}

if(!file_list.length)
	exit(0);

if(!options.hdr && options.sort && sort_prop !== undefined) {
	if(!options.case_sensitive) {
		log("Sorting " + file_list.length + " files...");
		if(typeof file_list[0] == "string")
			file_list.sort(
				function(a, b) {
					if (a.toLowerCase() < b.toLowerCase()) return -1;
					if (a.toLowerCase() > b.toLowerCase()) return 1;
					return 0;
				}
			);
		else {
			file_list.sort(
				function(a, b) {
					var a = a[sort_prop];
					var b = b[sort_prop];
					if(typeof a == "string") {
						var a = a.toLowerCase();
						var b = b.toLowerCase();
					}
					if (a < b) return -1;
					if (a > b) return 1;
					return 0;
				}
			);
		}
	}
	else {
		log("Sorting " + file_list.length + " files...");
		if(typeof file_list[0] == "string")
			file_list.sort();
		else {
			file_list.sort(
				function(a, b) {
					if (a[sort_prop] < b[sort_prop]) return -1;
					if (a[sort_prop] > b[sort_prop]) return 1;
					return 0;
				}
			);
		}
	}
}
if(options.reverse)
	file_list.reverse();

log("Formatting " + file_list.length + " files for output...");
if(fmt == "json") {
	out.writeln(JSON.stringify(file_list, null, json_space));
	exit(0);
}
var total_files = 0;
var bytes = 0;
var output = [];
dir_code = undefined;
for(var i = 0; i < file_list.length; i++) {
	var file = file_list[i];
	switch(typeof file) {
		case "string":
			if(excluded(file))
				continue;
			break;
		case "object":
			if(excluded(file.name))
				continue;
			bytes += file.size;
			break;
		default:
			throw new Error(typeof file);
			break;
	}
	if(typeof file == "object" && options.hdr && dir_code != file.dir) {
		dir_code = file.dir;
		var dir = file_area.dir[dir_code];
		if(i)
			output.push("");
		var hdr = format("%-15s %-40s Files: %d", dir.lib_name, dir.description, dir.files);
		output.push(hdr);
		output.push(format("%.*s", hdr.length
			, "-------------------------------------------------------------------------------"));
	}
	output.push(list_file(file, fmt, props));
	++total_files;
}

for(var i in output) {
	if(options.utf8)
		output[i] = utf8_encode(output[i]);
	if(options.strip)
		output[i] = strip_ctrl_a(output[i]);
	out.writeln(output[i]);
}
if(bytes)
	out.writeln(format("%s bytes in %u files", file_size_str(bytes), total_files));
else if(total_files)
	out.writeln(format("%u files", total_files));

function archive_contents(path, list)
{
	var output = [];
	for(var i = 0; i < list.length; i++) {
		var fname = path + list[i];
		writeln(fname);
		output.push(fname);
		var contents;
		try {
			contents = Archive(fname).list();
		} catch(e) {
//			alert(e);
			continue;
		}
		for(var j = 0; j < contents.length; j++)
			output.push(contents[j].name + " " + contents[j].size);
	}
	return output;
}

function list_file(file, fmt, props)
{
	if(typeof file == 'string')
		return file;
	if(fmt === undefined)
		fmt = "%s";
	var a = [fmt];
	for(var i in props) {
		var name = props[i];
		var p = file[name];
		switch(typeof p) {
			case "undefined":
				if(name == 'desc')
					a.push(file.name);
				else
					a.push('');
				break;
			case "string":
				if(name == 'name')
					a.push(FileBase().format_name(p, name_len, options.pad));
				else if(name == 'extdesc') {
					p = p.replace(/\r/g, '');
					if(both_desc)
						a.push(p.replace(/([^\n]+)/g, (extdesc_prefix + "$&")).trimRight());
					else
						a.push(p.trim().replace(/\n/g, '\n' + extdesc_prefix).trimRight());
				}
				else if(both_desc || name !== 'desc' || !file.extdesc)
					a.push(p);
				break;
			case "number":
				switch(name) {
					case 'time':
					case 'added':
					case 'last_downloaded':
						a.push(datestr(p));
						break;
					case 'crc32':
						a.push(format("%08lx", p));
						break;
					case 'crc16':
						a.push(format("%04lx", p));
						break;
					case 'size':
					case 'cost':
						if(typeof size_fmt == 'string') {
							a.push(format(size_fmt, p));
							break;
						}
						if(size_fmts[size_fmt][0] !== undefined
							|| size_fmts[size_fmt][1] !== undefined) {
							a.push(file_size_str(p
								,/* unit: */ size_fmts[size_fmt][0]
								,/* precision: */ size_fmts[size_fmt][1]));
							break;
						}
					default:
						a.push(p);
						break;
				}
				break;
		}
	}
	return format.apply(this, a);
}

function excluded(fname)
{
	for(var i = 0; i < exclude_list.length; i++)
		if(wildmatch(fname, exclude_list[i]))
			return true;
	return false;
}
