import test from 'tape';
import sade from '../src';

function isShapely(t, tree, key) {
	t.is(typeof tree[key].usage, 'string', `~> tree[${key}].usage is a string`);
	t.ok(Array.isArray(tree[key].options), `~> tree[${key}].options is an array`);
	t.ok(Array.isArray(tree[key].examples), `~> tree[${key}].examples is an array`);
	t.is(typeof tree[key].default, 'object', `~> tree[${key}].default is an object`);
	t.is(typeof tree[key].alias, 'object', `~> tree[${key}].alias is an object`);
}

test('sade', t => {
	t.is(typeof sade, 'function', 'exports a function');
	t.end();
});

test('sade()', t => {
	let ctx = sade('foo');
	t.ok(ctx.constructor && ctx.constructor.name === 'Sade', 'returns instance of Sade');
	t.is(ctx.bin, 'foo', 'sets Program name to `foo`');
	t.is(ctx.ver, '0.0.0', 'defaults `ver` to `0.0.0`');
	t.is(ctx.curr, '', 'is empty command-name scope');
	t.is(ctx.default, '', 'has no default command (yet)');
	t.is(typeof ctx.tree, 'object', 'creates a `tree` object');
	let k, keys = Object.keys(ctx.tree);
	t.is(keys.length, 2, 'internal `tree` has two keys');
	for (k in ctx.tree) {
		isShapely(t, ctx.tree, k);
	}
	t.end();
});

test('prog.version (global)', t => {
	let ctx = sade('foo').version('1.0.0');
	t.is(ctx.ver, '1.0.0', 'sets a new version~!');
	t.end();
});

test('prog.option (global)', t => {
	let ctx = sade('foo');
	t.is(ctx.tree.__all__.options.length, 0, 'no global options (default)');
	ctx.option('--foo, -f', 'bar', 'baz.js');
	let arr = ctx.tree.__all__.options;
	t.is(arr.length, 1, 'adds an option successfully');
	let item = arr[0];
	t.ok(Array.isArray(item), 'options entry is also an array');
	t.is(item.length, 3, 'entry has 3 segments (flags, desc, default)');
	t.is(item[0], '-f, --foo', 'flips the flags order; alias is first');
	t.end();
});

test('prog.option (hypenated)', t => {
	let ctx = sade('foo');
	ctx.option('--foo-bar, -f');
	ctx.option('--foo-bar-baz');
	let arr = ctx.tree.__all__.options;
	t.is(arr[0][0], '-f, --foo-bar', 'keeps mid-hyphen; flips order so alias is first');
	t.is(arr[1][0], '--foo-bar-baz', 'keeps all mid-hyphens');
	t.end();
});

test('prog.describe (global)', t => {
	let ctx = sade('foo').describe('Who is on first. What is on second.');
	let arr = ctx.tree.__default__.describe;
	t.ok(Array.isArray(arr), 'adds a `describe` array for Program info');
	t.is(arr.length, 2, 'splits the description into 2 sentence items');
	t.end();
});

test('prog.example (global)', t => {
	let ctx = sade('foo').example('hello --local');
	let arr = ctx.tree.__default__.examples;
	t.ok(Array.isArray(arr), 'adds a `examples` array for Program info');
	t.is(arr.length, 1, 'contains the single example');
	t.is(arr[0], 'hello --local', 'does not manipulate contents (yet)')
	t.end();
});

test('prog.command', t => {
	let ctx = sade('foo').command('bar');
	let bar = ctx.tree.bar;
	t.ok(bar, 'adds `bar` key to the command tree');
	isShapely(t, ctx.tree, 'bar');
	t.is(bar.usage, 'bar', 'stores usage as is');

	// Options
	t.is(bar.options.length, 0, 'has no options initially');
	ctx.option('-f, --force', 'force');
	t.is(bar.options.length, 1, 'adds new Command option successfully');
	t.deepEqual(bar.alias, { f:['force'] }, 'adds option flag & alias');

	// Examples
	t.is(bar.examples.length, 0, 'has no examples initially');
	ctx.example('bar --force');
	t.is(bar.examples.length, 1, 'adds new Command exmaple successfully');
	t.is(bar.examples[0], 'bar --force', 'adds example, as written');

	// Description
	t.ok(bar.describe === void 0, 'has no description initially');
	ctx.describe('hello world');
	t.ok(Array.isArray(bar.describe), 'adds new Command description as Array');
	t.is(bar.describe[0], 'hello world', 'stores description, as written');

	// Add new Command
	ctx.command('quz');
	let quz = ctx.tree.quz;
	t.ok(quz, 'adds `quz` key to the command tree');
	isShapely(t, ctx.tree, 'quz');
	t.is(quz.usage, 'quz', 'stores usage as is');

	// Show that command state changed
	ctx.describe('this is quz');
	t.is(quz.describe[0], 'this is quz', 'adds description for second command');
	t.is(bar.describe[0], 'hello world', 'does not affect first Command');

	// Add third, with description & change default
	ctx.command('fizz <buzz>', 'FizzBuzz', { default:true });
	let fizz = ctx.tree.fizz;
	t.ok(fizz, 'adds `fizz` key to the command tree');
	isShapely(t, ctx.tree, 'fizz');
	t.is(fizz.usage, 'fizz <buzz>', 'stores usage as is');

	// Add Example
	ctx.example('fizz 15');
	t.is(fizz.examples.length, 1, 'adds new Command exmaple successfully');
	t.is(fizz.examples[0], 'fizz 15', 'adds example, as written');

	t.is(bar.examples.length, 1, '1st command example count unchanged');
	t.is(bar.examples[0], 'bar --force', 'first command example unaffected');
	t.is(quz.examples.length, 0, '2nd command example count unchanged');

	t.is(ctx.default, 'fizz', 'default command was updated~!');

	t.end();
});

test('prog.action', t => {
	t.plan(13);
	let a='Bob', b, c, d, e;

	let ctx = sade('foo')
		.command('greet <name>')
		.option('--loud', 'Be loud?')
		.option('--with-kiss, -k', 'Super friendly?')
		.action((name, opts) => {
			t.is(name, a, '~> receives the required value as first parameter');
			b && t.ok(opts.loud, '~> receives the `loud` flag (true) when parsed');
			c && t.ok(opts['with-kiss'], '~> receives the `with-kiss` flag (true) when parsed :: preserves mid-hyphen');
			d && t.is(opts['with-kiss'], 'cheek', '~> receives the `with-kiss` flag (`cheek`) when parsed :: preserves mid-hyphen');
			e && t.is(opts['with-kiss'], false, '~> receive the `--no-with-kiss` flag (false) :: preserves mid-hyphen');
			b = c = d = e = false; // reset
		});

	// Simulate `process.argv` entry
	let run = args => ctx.parse(['', '', 'greet', a].concat(args || []));

	let cmd = ctx.tree.greet;
	t.ok(cmd.handler, 'added a `handler` key to the command leaf');
	t.is(typeof cmd.handler, 'function', 'the `handler` is a function');

	run(); // +1 test
	(b=true) && run('--loud'); // +2 tests
	(c=true) && run('--with-kiss'); // +2 tests
	(d=true) && run('--with-kiss=cheek'); // +2 tests
	(d=true) && run(['--with-kiss', 'cheek']); // +2 tests
	(e=true) && run('--no-with-kiss'); // +2 tests
});

test('prog.action (multi requires)', t => {
	t.plan(7);

	let a='aaa', b='bbb', c=false;

	let ctx = sade('foo')
		.command('build <src> <dest>')
		.option('-f, --force', 'Force foo overwrite')
		.action((src, dest, opts) => {
			t.is(src, a, '~> receives `src` param first');
			t.is(dest, b, '~> receives `dest` param second');
			c && t.ok(opts.force, '~> receives the `force` flag (true) when parsed');
			c && t.ok(opts.f, '~> receives the `f` alias (true) when parsed');
		});

	t.is(ctx.tree.build.usage, 'build <src> <dest>', 'writes all required params to usage');

	let run = _ => ctx.parse(['', '', 'build', a, b, c && '-f']);

	run(); // +2 tests
	(c=true) && run(); // +4 tests
});

test('prog.action (multi optional)', t => {
	t.plan(7);

	let a='aaa', b='bbb', c=false;

	let ctx = sade('foo')
		.command('build [src] [dest]')
		.option('-f, --force', 'Force foo overwrite')
		.action((src, dest, opts) => {
			t.is(src, a, '~> receives `src` param first');
			t.is(dest, b, '~> receives `dest` param second');
			c && t.ok(opts.force, '~> receives the `force` flag (true) when parsed');
			c && t.ok(opts.f, '~> receives the `f` alias (true) when parsed');
		});

	t.is(ctx.tree.build.usage, 'build [src] [dest]', 'writes all positional params to usage');

	let run = _ => ctx.parse(['', '', 'build', a, b, c && '-f']);

	run(); // +2 tests
	(c=true) && run(); // +4 tests
});

test('prog.parse :: safe :: default', t => {
	let ctx = sade('foo').command('build', '', { default: true });

	let argv1 = ['', '', 'build'];
	let foo = ctx.parse(argv1, { lazy: true });
	t.deepEqual(argv1, ['', '', 'build'], '~> argv unchanged');
	t.deepEqual(foo.args, [{ _: [] }], '~> args correct');

	let argv2 = ['', ''];
	let bar = ctx.parse(argv2, { lazy: true });
	t.deepEqual(argv2, ['', ''], '~> argv unchanged');
	t.deepEqual(bar.args, [{ _: [] }], '~> args correct');

	t.end();
});

test('prog.parse :: safe :: alias', t => {
	let ctx = sade('foo').command('build').alias('b');

	let argv1 = ['', '', 'build'];
	let foo = ctx.parse(argv1, { lazy: true });
	t.deepEqual(argv1, ['', '', 'build'], '~> argv unchanged');
	t.deepEqual(foo.args, [{ _: [] }], '~> args correct');

	let argv2 = ['', '', 'b'];
	let bar = ctx.parse(argv2, { lazy: true });
	t.deepEqual(argv2, ['', '', 'b'], '~> argv unchanged');
	t.deepEqual(bar.args, [{ _: [] }], '~> args correct');

	t.end();
});

test('prog.parse :: safe :: default :: flags', t => {
	let ctx = sade('foo').command('build <dir>', '', { default: true });

	let argv1 = ['', '', '-r', 'dotenv', 'build', 'public', '--fresh'];
	let foo = ctx.parse(argv1, { lazy: true });
	t.deepEqual(argv1, ['', '', '-r', 'dotenv', 'build', 'public', '--fresh'], '~> argv unchanged');
	t.deepEqual(foo.args, ['public', { _: [], r: 'dotenv', fresh: true }], '~> args correct');

	let argv2 = ['', '', '-r', 'dotenv', 'public', '--fresh'];
	let bar = ctx.parse(argv2, { lazy: true });
	t.deepEqual(argv2, ['', '', '-r', 'dotenv', 'public', '--fresh'], '~> argv unchanged');
	t.deepEqual(bar.args, ['public', { _: [], r: 'dotenv', fresh: true }], '~> args correct');

	t.end();
});

test('prog.parse :: safe :: alias :: flags', t => {
	let ctx = sade('foo').command('build <dir>').alias('b');

	let argv1 = ['', '', '-r', 'dotenv', 'build', 'public', '--fresh'];
	let foo = ctx.parse(argv1, { lazy: true });
	t.deepEqual(argv1, ['', '', '-r', 'dotenv', 'build', 'public', '--fresh'], '~> argv unchanged');
	t.deepEqual(foo.args, ['public', { _: [], r: 'dotenv', fresh: true }], '~> args correct');

	let argv2 = ['', '', '-r', 'dotenv', 'b', 'public', '--fresh'];
	let bar = ctx.parse(argv2, { lazy: true });
	t.deepEqual(argv2, ['', '', '-r', 'dotenv', 'b', 'public', '--fresh'], '~> argv unchanged');
	t.deepEqual(bar.args, ['public', { _: [], r: 'dotenv', fresh: true }], '~> args correct');

	t.end();
});

test('prog.parse :: lazy', t => {
	t.plan(14);

	let val='aaa', f=false;

	let ctx = sade('foo')
		.command('build <src>')
		.option('--force').action((src, opts) => {
			t.is(src, val, '~> receives `src` param first');
			f && t.ok(opts.force, '~> receives the `force` flag (true) when parsed');
		});

	let run = _ => ctx.parse(['', '', 'build', val, f && '--force'], { lazy:true });

	let foo = run();
	t.is(foo.constructor, Object, 'returns an object');
	t.same(Object.keys(foo), ['args', 'name', 'handler'], 'contains `args`,`name`,`handler` keys');
	t.ok(Array.isArray(foo.args), '~> returns the array of arguments');
	t.is(foo.args[0], val, '~> preserves the `src` value first');
	t.is(foo.args[1].constructor, Object, '~> preserves the `opts` value last');
	t.ok(Array.isArray(foo.args[1]._), '~> ensures `opts._` is still `[]` at least');
	t.is(typeof foo.handler, 'function', '~> returns the action handler');
	t.is(foo.name, 'build', '~> returns the command name');

	foo.handler.apply(null, foo.args); // must be manual bcuz lazy; +1 test

	let bar = run(f=true);
	t.is(bar.constructor, Object, 'returns an object');
	t.is(bar.args[1].constructor, Object, '~> preserves the `opts` value last');
	t.is(bar.args[1].force, true, '~> attaches the `force:true` option');

	bar.handler.apply(null, bar.args); // manual bcuz lazy; +2 tests
});

test('prog.parse :: lazy :: single', t => {
	t.plan(14);

	let val='aaa', f=false;

	let ctx = sade('foo <src>').option('--force').action((src, opts) => {
		t.is(src, val, '~> receives `src` param first');
		f && t.ok(opts.force, '~> receives the `force` flag (true) when parsed');
	});

	let run = _ => ctx.parse(['', '', val, f && '--force'], { lazy:true });

	let foo = run();
	t.is(foo.constructor, Object, 'returns an object');
	t.same(Object.keys(foo), ['args', 'name', 'handler'], 'contains `args`,`name`,`handler` keys');
	t.ok(Array.isArray(foo.args), '~> returns the array of arguments');
	t.is(foo.args[0], val, '~> preserves the `src` value first');
	t.is(foo.args[1].constructor, Object, '~> preserves the `opts` value last');
	t.ok(Array.isArray(foo.args[1]._), '~> ensures `opts._` is still `[]` at least');
	t.is(typeof foo.handler, 'function', '~> returns the action handler');
	t.is(foo.name, '', '~> returns empty command name');

	foo.handler.apply(null, foo.args); // must be manual bcuz lazy; +1 test

	let bar = run(f=true);
	t.is(bar.constructor, Object, 'returns an object');
	t.is(bar.args[1].constructor, Object, '~> preserves the `opts` value last');
	t.is(bar.args[1].force, true, '~> attaches the `force:true` option');

	bar.handler.apply(null, bar.args); // manual bcuz lazy; +2 tests
});
