use strict;
use Test::More;
use Data::Dumper;
use Exception::Class::TryCatch 1.10;
use Getopt::Lucid ':all';
use Getopt::Lucid::Exception;
use t::ErrorMessages;

# Work around win32 console buffering that can show diags out of order
Test::More->builder->failure_output(*STDOUT) if $ENV{HARNESS_VERBOSE};

sub why {
    my %vars = @_;
    $Data::Dumper::Sortkeys = 1;
    return "\n" . Data::Dumper->Dump([values %vars],[keys %vars]) . "\n";
}

#--------------------------------------------------------------------------#
# Test cases
#--------------------------------------------------------------------------#

my ($num_tests, @bad_specs, @good_specs);

BEGIN {

    push @good_specs, {
        label => "single short switch",
        spec  => [
            Switch("-v"),
        ],
        cases => [
            {
                argv    => [ qw( -v ) ],
                result  => { "v" => 1 },
                desc    => "switch present"
            },
            {
                argv    => [ qw( ) ],
                result  => { "v" => 0 },
                desc    => "switch missing"
            },
            {
                argv    => [ qw( -t ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _invalid_argument("-t"),
                desc    => "invalid command line"
            },
        ]
    };

    push @good_specs, {
        label => "multiple short switches",
        spec  => [
            Switch("-v"),
            Switch("-t"),
        ],
        cases => [
            {
                argv    => [ qw( -v -t ) ],
                result  => { "v" => 1, "t" => 1 },
                desc    => "both switches present"
            },
            {
                argv    => [ qw( -v ) ],
                result  => { "v" => 1, "t" => 0 },
                desc    => "one switch present"
            },
            {
                argv    => [ qw( ) ],
                result  => { "v" => 0, "t" => 0 },
                desc    => "both switches missing"
            },
            {
                argv    => [ qw( -f ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _invalid_argument("-f"),
                desc    => "invalid command line"
            },
        ]
    };

    push @good_specs, {
        label => "bundled short switches",
        spec  => [
            Switch("-v"),
            Switch("-t"),
            Switch("-r"),
        ],
        cases => [
            {
                argv    => [ qw( -vrt ) ],
                result  => { "v" => 1, "r" => 1, "t" => 1 },
                desc    => "three switches present"
            },
            {
                argv    => [ qw( -vt ) ],
                result  => { "v" => 1, "r" => 0, "t" => 1 },
                desc    => "two switches present"
            },
            {
                argv    => [ qw( -vfrt ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _invalid_argument("-f"),
                desc    => "invalid command line"
            },
        ]
    };

    push @good_specs, {
        label => "short and long switches",
        spec  => [
            Switch("--verbose"),
            Switch("--test"),
            Switch("-r"),
        ],
        cases => [
            {
                argv    => [ qw( --verbose -r --test ) ],
                result  => { "verbose" => 1, "test" => 1, "r" => 1 },
                desc    => "three switches present (2 long and 1 short)"
            },
            {
                argv    => [ qw( -r --test ) ],
                result  => { "verbose" => 0, "test" => 1, "r" => 1 },
                desc    => "two switches present (long and short)"
            },
            {
                argv    => [ qw( --test ) ],
                result  => { "verbose" => 0, "test" => 1, "r" => 0 },
                desc    => "only long switch present"
            },
            {
                argv    => [ qw( --test -v ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _invalid_argument("-v"),
                desc    => "invalid command line"
            },
        ]
    };

    push @good_specs, {
        label => "option name aliasing",
        spec  => [
            Switch("--verbose|-v"),
            Switch("--test|--debug|-d"),
            Switch("-r|-s"),
        ],
        cases => [
            {
                argv    => [ qw( -v -s --debug ) ],
                result  => { "verbose" => 1, "test" => 1, "r" => 1 },
                desc    => "three switch aliases used"
            },
            {
                argv    => [ qw( -r -d ) ],
                result  => { "verbose" => 0, "test" => 1, "r" => 1 },
                desc    => "two switches present (alias and regular)"
            },
            {
                argv    => [ qw( --verbose -v ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _switch_twice("--verbose"),
                desc    => "switch used more than once"
            },
        ]
    };

    push @good_specs, {
        label => "'--' terminates options",
        spec  => [
            Switch("--verbose"),
            Switch("--test"),
            Switch("-r"),
        ],
        cases => [
            {
                argv    => [ qw( --verbose -r -- --test ) ],
                result  => { "verbose" => 1, "test" => 0, "r" => 1 },
                after   => [ qw( --test ) ],
                desc    => "stop after two"
            },
            {
                argv    => [ qw( -- -r --test ) ],
                result  => { "verbose" => 0, "test" => 0, "r" => 0 },
                after   => [ qw(-r --test ) ],
                desc    => "stop right away"
            },
        ]
    };

    push @good_specs, {
        label => "two counter",
        spec  => [
            Counter("--verbose|-v"),
            Counter("--count"),
        ],
        cases => [
            {
                argv    => [ qw( --count --verbose -vv --count ) ],
                result  => { "verbose" => 3, "count" => 2 },
                desc    => "one counter used twice, other used thrice"
            },
            {
                argv    => [ qw( --verbose -v ) ],
                result  => { "verbose" => 2, "count" => 0 },
                desc    => "one counter used twice"
            },
            {
                argv    => [ qw( -- ) ],
                result  => { "verbose" => 0, "count" => 0 },
                desc    => "no counters used"
            },
        ]
    };

    push @good_specs, {
        label => "parameter w/o '='",
        spec  => [
            Counter("--verbose|-v"),
            Param("--input|-i"),
        ],
        cases => [
            {
                argv    => [ qw( -- ) ],
                result  => { "verbose" => 0, "input" => undef },
                desc    => "no options"
            },
            {
                argv    => [ qw( --input 42 -vv ) ],
                result  => { "verbose" => 2, "input" => 42 },
                desc    => "counters and long-style parameter"
            },
            {
                argv    => [ qw( -vi 42 ) ],
                result  => { "verbose" => 1, "input" => 42 },
                desc    => "bundled counter and short-style parameter"
            },
            {
                argv    => [ qw( -i 42 --input 3 ) ],
                result  => { "verbose" => 0, "input" => 3 },
                desc    => "repeated param value"
            },
            {
                argv    => [ qw( --input ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _no_value("--input"),
                desc    => "missing param value"
            },
            {
                argv    => [ qw( -i -v ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _param_ambiguous("--input","-v"),
                desc    => "ambiguous param value"
            },
        ]
    };

    push @good_specs, {
        label => "parameter w '='",
        spec  => [
            Switch("--test|-t"),
            Counter("--verbose|-v"),
            Param("--input|-i"),
        ],
        cases => [
            {
                argv    => [ qw( -- ) ],
                result  => { "verbose" => 0, "input" => undef, "test" => 0 },
                desc    => "no options"
            },
            {
                argv    => [ qw( --input=42 -vv ) ],
                result  => { "verbose" => 2, "input" => 42, "test" => 0 },
                desc    => "counters and long-style parameter"
            },
            {
                argv    => [ qw( -vi=42 ) ],
                result  => { "verbose" => 1, "input" => 42, "test" => 0 },
                desc    => "bundled counter and short-style parameter"
            },
            {
                argv    => [ qw( -i=-v ) ],
                result  => { "verbose" => 0, "input" => "-v", "test" => 0 },
                desc    => "ambiguous param value ok w '='"
            },
            {
                argv    => [ qw( --test=42 ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _switch_value("--test","42"),
                desc    => "switch with equals"
            },
            {
                argv    => [ qw( --verbose=42 ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _counter_value("--verbose","42"),
                desc    => "counter with equals"
            },
        ]
    };

    push @good_specs, {
        label => "lists (w or w/o '=')",
        spec  => [
            Counter("--verbose|-v"),
            List("--input|-i"),
        ],
        cases => [
            {
                argv    => [ qw( -- ) ],
                result  => { "verbose" => 0, "input" => [] },
                desc    => "no options"
            },
            {
                argv    => [ qw( --input 42 -vv ) ],
                result  => { "verbose" => 2, "input" => [42] },
                desc    => "counters and one list arg"
            },
            {
                argv    => [ qw( --input 42 -vvi=twelve ) ],
                result  => { "verbose" => 2, "input" => [42,"twelve"] },
                desc    => "counters and two list args"
            },
            {
                argv    => [ qw( -i -v ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _param_ambiguous("--input","-v"),
                desc    => "ambiguous param value"
            },
            {
                argv    => [ qw( -i=-v --input=-i) ],
                result  => { "verbose" => 0, "input" => ["-v", "-i"] },
                desc    => "ambiguous param value ok w '='"
            },
        ]
    };

    push @good_specs, {
        label => "pass through non-switch args",
        spec  => [
            Switch("-v"),
        ],
        cases => [
            {
                argv    => [ qw( word1 -v word2 ) ],
                result  => { "v" => 1 },
                after   => [ qw( word1 word2 ) ],
                desc    => "switch in middle"
            },
            {
                argv    => [ qw( word1 -v -- -t ) ],
                result  => { "v" => 1 },
                after   => [ qw( word1 -t ) ],
                desc    => "switch before '--' and arg"
            },
            {
                argv    => [ qw( -v - ) ],
                result  => { "v" => 1 },
                after   => [ qw( - ) ],
                desc    => "single dash"
            },
            {
                argv    => [ qw( -v -t ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _invalid_argument("-t"),
                desc    => "invalid command line"
            },
        ]
    };

    push @good_specs, {
        label => "keypairs (w or w/o =)",
        spec  => [
            Counter("--verbose|-v"),
            Keypair("--input|-i"),
        ],
        cases => [
            {
                argv    => [ qw( -- ) ],
                result  => { "verbose" => 0, "input" => {} },
                desc    => "no options"
            },
            {
                argv    => [ qw( --input n=42 -vv ) ],
                result  => { "verbose" => 2, "input" => { n => 42} },
                desc    => "counters and one keypair arg"
            },
            {
                argv    => [ qw( --input n=42 -vvi=p=twelve ) ],
                result  => {
                    "verbose" => 2,
                    "input" => { n => 42, p => "twelve"}
                },
                desc    => "counters and two keypair args"
            },
            {
                argv    => [ qw( -i -v ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _keypair("--input"),
                desc    => "keypair missing value (no '=')"
            },
            {
                argv    => [ qw( -i=-v --input=-i) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _keypair("--input"),
                desc    => "keypair missing value (w '=')"
            },
            {
                argv    => [ qw( -i==-v ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _keypair("--input"),
                desc    => "keypair missing key (w '=')"
            },
        ]
    };

    push @good_specs, {
        label => "bareword options",
        spec  => [
            Counter("verbose|v"),
            Param("input|i"),
        ],
        cases => [
            {
                argv    => [ qw( -- ) ],
                result  => { "verbose" => 0, "input" => undef },
                desc    => "no options"
            },
            {
                argv    => [ qw( input 42 v v ) ],
                result  => { "verbose" => 2, "input" => 42 },
                desc    => "counters and longstyle parameter"
            },
            {
                argv    => [ qw( i 42 input 3 ) ],
                result  => { "verbose" => 0, "input" => 3 },
                desc    => "repeated param value"
            },
        ]
    };

    push @good_specs, {
        label => "required options",
        spec  => [
            Counter("--verbose|-v"),
            Param("--input|-i")
        ],
        cases => [
            {
                argv    => [ qw( -v ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _required("--input"),
                required => ['input'],
                desc    => "missing required option"
            },
            {
                argv    => [ qw( --input 42 -vv ) ],
                required => ['input'],
                result  => { "verbose" => 2, "input" => 42 },
                desc    => "required option present"
            },
            {
                argv    => [ qw( --input info -v ) ],
                required => ['input'],
                result  => { "verbose" => 1, "input" => 'info' },
                desc    => "required option param similar to option name"
            },
        ]
    };

    push @good_specs, {
        label => "default values",
        spec  => [
            Switch("--quick")->default(1),
            Counter("--verbose|-v")->default(1),
            Param("--input")->default(42),
            List("--lib")->default(qw( one two )),
            Keypair("--flag")->default(answer => 42),
        ],
        cases => [
            {
                argv    => [ qw(  ) ],
                result  => {
                    "quick" => 1,
                    "verbose" => 1,
                    "input" => 42,
                    "lib" => [qw( one two )],
                    "flag" => { answer => 42 },
                },
                desc    => "no options"
            },
            {
                argv    => [ qw( -v ) ],
                result  => {
                    "quick" => 1,
                    "verbose" => 2,
                    "input" => 42,
                    "lib" => [qw( one two )],
                    "flag" => { answer => 42 },
                },
                desc    => "one option given, other default"
            },
            {
                argv    => [ qw( --input 23 -vv ) ],
                result  => {
                    "quick" => 1,
                    "verbose" => 3,
                    "input" => 23,
                    "lib" => [qw( one two )],
                    "flag" => { answer => 42 },
                },
                desc    => "two options given"
            },
        ]
    };

    push @good_specs, {
        label => "case insensitive",
        spec  => [
            Counter("--verbose|-v")->default(1)->anycase,
            Param("--input|-i|-r")->default(42),
        ],
        cases => [
            {
                argv    => [ qw(  ) ],
                result  => { "verbose" => 1, "input" => 42, },
                desc    => "no options"
            },
            {
                argv    => [ qw( -v ) ],
                result  => { "verbose" => 2, "input" => 42, },
                desc    => "two lower case options given"
            },
            {
                argv    => [ qw( --verBose -vV -r 23 ) ],
                result  => { "verbose" => 4, "input" => 23, },
                desc    => "mixed cases for case insensitive (alt)"
            },
            {
                argv    => [ qw( --verBose -vV --input 23 ) ],
                result  => { "verbose" => 4, "input" => 23, },
                desc    => "mixed cases for case insensitive"
            },
            {
                argv    => [ qw( --INPUT ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _invalid_argument("--INPUT"),
                desc    => "bad case for case sensitive option"
            },
        ]
    };

    push @good_specs, {
        label => "validate w/ regex",
        spec  => [
            Counter("--verbose|-v")->default(1),
            Param("--input|-i", qr/\d+/)->default(42),
            List("--lib", qr/\w*/),
            Keypair("--def", qr/(os)*/, qr/^(win32|linux|mac)/),
        ],
        cases => [
            {
                argv    => [ qw(  ) ],
                result  => {
                    "verbose" => 1,
                    "input" => 42,
                    "lib" => [],
                    "def" => {},
                },
                desc    => "no options"
            },
            {
                argv    => [ qw( --input 23 ) ],
                result  => {
                    "verbose" => 1,
                    "input" => 23,
                    "lib" => [],
                    "def" => {},
                },
                desc    => "param input validates"
            },
            {
                argv    => [ qw( --lib foo --lib bar ) ],
                result  => {
                    "verbose" => 1,
                    "input" => 42,
                    "lib" => [qw(foo bar)],
                    "def" => {},
                },
                desc    => "list input validates"
            },
            {
                argv    => [ qw( --def os=linux ) ],
                result  => {
                    "verbose" => 1,
                    "input" => 42,
                    "lib" => [],
                    "def" => { os => "linux" },
                },
                desc    => "keypair input validates"
            },
            {
                argv    => [ qw( --input twenty-three ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _param_invalid("--input","twenty-three"),
                desc    => "param input not validating"
            },
            {
                argv    => [ qw( --lib foo --lib % ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _list_invalid("--lib","%"),
                desc    => "list input not validating"
            },
            {
                argv    => [ qw( --def os=amiga ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _keypair_invalid("--def","os","amiga"),
                desc    => "keypair value not validating"
            },
            {
                argv    => [ qw( --def arch=i386 ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _keypair_invalid("--def","arch","i386"),
                desc    => "keypair key not validating"
            },
        ]
    };

    push @good_specs, {
        label => "validate but allow missing options",
        spec  => [
            Param( "mode|m", qr/test|live/ )
        ],
        cases => [
            {
                argv    => [ qw() ],
                result  => {
                    "mode" => undef,
                },
                desc    => "no param validates"
            },
            {
                argv    => [ qw( --mode test ) ],
                result  => {
                    "mode" => 'test',
                },
                desc    => "param input validates"
            },
            {
                argv    => [ qw( --mode foo ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _param_invalid("mode","foo"),
                desc    => "param mode not validating"
            },
        ]
    };

    push @good_specs, {
        label => "validate w/ string alternation regex",
        spec  => [
            Param( "mode|m", qr/test|live/ ),
        ],
        cases => [
            {
                argv    => [ qw( --mode test ) ],
                required => ['mode'],
                result  => {
                    "mode" => 'test',
                },
                desc    => "param input validates"
            },
            {
                argv    => [ qw( --mode foo ) ],
                required => ['mode'],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _param_invalid("mode","foo"),
                desc    => "param mode not validating"
            },
        ]
    };

    push @good_specs, {
        label => "validate only keypair key",
        spec  => [
            Keypair("--def", qr/(os)*/),
        ],
        cases => [
            {
                argv    => [ qw( --def os=linux ) ],
                result  => {
                    "def" => { os => "linux" },
                },
                desc    => "keypair input validates"
            },
            {
                argv    => [ qw( --def arch=i386 ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _keypair_invalid("--def","arch", "i386"),
                desc    => "key not validating"
            },
        ]
    };

    push @good_specs, {
        label => "validate only keypair value",
        spec  => [
            Keypair("--def", undef, qr/(linux|win32)*/),
        ],
        cases => [
            {
                argv    => [ qw( --def os=linux ) ],
                result  => {
                    "def" => { os => "linux" },
                },
                desc    => "keypair input validates"
            },
            {
                argv    => [ qw( --def os=amiga ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _keypair_invalid("--def","os","amiga"),
                desc    => "value not validating"
            },
        ]
    };

    push @good_specs, {
        label => "validate w/ code refs",
        spec  => [
            Counter("--verbose|-v")->default(1),
            Param("--input|-i", sub { /\d+/ })->default(42),
            Param("--answer", sub { $_ < 43  })->default(23),
        ],
        cases => [
            {
                argv    => [ qw(  ) ],
                result  => { "verbose" => 1, "input" => 42, "answer" => 23 },
                desc    => "no options"
            },
            {
                argv    => [ qw( --input 23 ) ],
                result  => { "verbose" => 1, "input" => 23, "answer" => 23 },
                desc    => "input validates"
            },
            {
                argv    => [ qw( --answer 60 ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _param_invalid("--answer","60"),
                desc    => "input failing to validate"
            },
        ]
    };

    push @good_specs, {
        label => "single dependency",
        spec  => [
            Param("--question"),
            Switch("--guess")->needs("--answer"),
            Param("--answer|-a"),
        ],
        cases => [
            {
                argv    => [ qw( --question 5 ) ],
                result  => { "question" => 5, "guess" => 0, "answer" => undef },
                desc    => "single, unrelated option"
            },
            {
                argv    => [ qw( --guess ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _prereq_missing("--guess", "--answer"),
                desc    => "missing prereq"
            },
            {
                argv    => [ qw( --guess --answer 3 ) ],
                result  => { "question" => undef, "guess" => 1, "answer" => "3" },
                desc    => "prereq present"
            },
            {
                argv    => [ qw( --guess -a 3 ) ],
                result  => { "question" => undef, "guess" => 1, "answer" => "3" },
                desc    => "prereq present as alias"
            },
        ]
    };

    push @good_specs, {
        label => "multiple dependencies",
        spec  => [
            Switch("--guess")->needs(qw( --answer --wager )),
            Param("--wager|-w"),
            Param("--answer|-a"),
        ],
        cases => [
            {
                argv    => [ qw( ) ],
                result  => { "guess" => 0, "answer" => undef, wager => undef },
                desc    => "no options"
            },
            {
                argv    => [ qw( --guess ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _prereq_missing("--guess", "--answer"),
                desc    => "missing both prereqs"
            },
            {
                argv    => [ qw( --guess --answer 5 ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _prereq_missing("--guess", "--wager"),
                desc    => "missing one prereq"
            },
            {
                argv    => [ qw( --guess --answer 3 --wager 10 ) ],
                result  => { "guess" => 1, "answer" => 3, "wager" => 10 },
                desc    => "prereq present"
            },
        ]
    };

    push @good_specs, {
        label => "single dependency with alias",
        spec  => [
            Param("--question"),
            Switch("--guess")->needs("-a"),
            Param("--answer|-a"),
        ],
        cases => [
            {
                argv    => [ qw( --guess ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _prereq_missing("--guess", "--answer"),
                desc    => "missing prereq"
            },
            {
                argv    => [ qw( --guess --answer 3 ) ],
                result  => { "question" => undef, "guess" => 1, "answer" => "3" },
                desc    => "prereq present"
            },
            {
                argv    => [ qw( --guess -a 3 ) ],
                result  => { "question" => undef, "guess" => 1, "answer" => "3" },
                desc    => "prereq present as alias"
            },
        ]
    };

    push @good_specs, {
        label => "dash and underscore options",
        spec  => [
            Switch("--dash-style"),
            Switch("--underscore_style"),
        ],
        cases => [
            {
                argv    => [ qw( --dash-style ) ],
                result  => { "dash-style" => "1", "underscore_style" => "0" },
                desc    => "dash-style name works",
            },
            {
                argv    => [ qw( --underscore_style ) ],
                result  => { "dash-style" => "0", "underscore_style" => "1" },
                desc    => "underscore_style name works",
            },
        ]
    };

    push @good_specs, {
        label => "Null Param,List,Keypair",
        spec  => [
            Param("--param"),
            List("--list"),
            Keypair("--key-pair"),
        ],
        cases => [
            {
                argv    => [ qw( --param ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _no_value("--param"),
                desc    => "parameter with no value",
            },
            {
                argv    => [ qw( --list ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _no_value("--list"),
                desc    => "list with no value",
            },
            {
                argv    => [ qw( --key-pair ) ],
                exception   => "Getopt::Lucid::Exception::ARGV",
                error_msg => _no_value("--key-pair"),
                desc    => "keypair with no value",
            },

        ]
    };


    # Bad specification testing

    push @bad_specs, {
        spec  => [
            Switch("-v|--verbose"),
            Switch("--verbose"),
        ],
        exception => "Getopt::Lucid::Exception::Spec",
        error_msg => _name_not_unique("--verbose"),
        label => "duplicate name and alias in spec"
    };

    push @bad_specs, {
        spec  => [
            Switch("--quick|-q"),
            Switch("--quiet|-q"),
        ],
        exception => "Getopt::Lucid::Exception::Spec",
        error_msg => _name_not_unique("-q"),
        label => "duplicate aliases in spec"
    };

    push @bad_specs, {
        spec  => [
            Switch("--quick"),
            Switch("quick"),
        ],
        exception => "Getopt::Lucid::Exception::Spec",
        error_msg => _name_not_unique("quick"),
        label => "duplicate name bareword and long"
    };

    push @bad_specs, {
        spec  => [
            Switch("-vv"),
        ],
        exception => "Getopt::Lucid::Exception::Spec",
        error_msg => _name_invalid("-vv"),
        label => "bad option name in spec - short w/ > 1 letter"
    };

    push @bad_specs, {
        spec  => [
            Switch("--%%"),
        ],
        exception => "Getopt::Lucid::Exception::Spec",
        error_msg => _name_invalid("--%%"),
        label => "bad option name in spec - symbols"
    };

    push @bad_specs, {
        spec  => [
            { name => "-v", badtype => "badtype" },
        ],
        exception => "Getopt::Lucid::Exception::Spec",
        error_msg => _key_invalid("badtype"),
        label => "bad spec key"
    };

    push @bad_specs, {
        spec  => [
            { name => "-v", type => "badtype" }
        ],
        exception => "Getopt::Lucid::Exception::Spec",
        error_msg => _type_invalid("badtype"),
        label => "bad spec type"
    };

    push @bad_specs, {
        spec  => [
            { name => "-v", type => "alist" },
        ],
        exception => "Getopt::Lucid::Exception::Spec",
        error_msg => _type_invalid("alist"),
        label => "bad spec type w valid substring"
    };

    push @bad_specs, {
        spec  => [
            Keypair("-v")->default([]),
        ],
        exception => "Getopt::Lucid::Exception::Spec",
        error_msg => _default_keypair("-v"),
        label => "keypair default not hash reference"
    };

    push @bad_specs, {
        spec  => [
            Param("-v", qr/[a-z]+/)->default(1),
        ],
        exception => "Getopt::Lucid::Exception::Spec",
        error_msg => _default_invalid("-v","1"),
        label => "provided default not validating vs regex"
    };

    push @bad_specs, {
        spec  => [
            Switch("--guess")->needs(qw( --answer --wager )),
            Switch("--answer|-a"),
        ],
        exception => "Getopt::Lucid::Exception::Spec",
        error_msg => _unknown_prereq("--wager","--guess"),
        label => "unknown prereq",
    };
    

} #BEGIN

$num_tests = 2 * @bad_specs;
for my $t (@good_specs) {
    $num_tests += 1 + 2 * @{$t->{cases}};
}

plan tests => $num_tests;

#--------------------------------------------------------------------------#
# Test good specs
#--------------------------------------------------------------------------#

my $trial;

while ( $trial = shift @good_specs ) {
    try eval { Getopt::Lucid->new($trial->{spec}) };
    catch my $err;
    is( $err, undef, "$trial->{label}: spec should validate" );
    SKIP: {
        if ($err) {
            my $num_tests = 2 * @{$trial->{cases}};
            skip "because $trial->{label} spec did not validate", $num_tests;
        }
        for my $case ( @{$trial->{cases}} ) {
            my $gl = Getopt::Lucid->new($trial->{spec});
            @ARGV = @{$case->{argv}};
            my %opts;
            my $valid_args = $case->{required}  ? {requires => $case->{required}}
                                                : {};
            try eval { %opts = $gl->getopt->validate($valid_args)->options };
            catch my $err;
            if (defined $case->{exception}) { # expected
                ok( $err && $err->isa( $case->{exception} ),
                    "$trial->{label}: $case->{desc} throws exception" )
                    or diag why( got => ref($err), expected => $case->{exception});
                is( $err, $case->{error_msg},
                    "$trial->{label}: $case->{desc} error msg ok");
            } elsif ($err) { # unexpected
                fail( "$trial->{label}: $case->{desc}")
                    or diag "Threw exception: '$err'";
                pass("$trial->{label}: skipping \@ARGV check");
            } else { # no exception
                is_deeply( \%opts, $case->{result},
                    "$trial->{label}: $case->{desc}" ) or
                    diag why( got => \%opts, expected => $case->{result});
                my $argv_after = $case->{after} || [];
                is_deeply( \@ARGV, $argv_after,
                    "$trial->{label}: \@ARGV correct afterwards") or
                    diag why( got => \@ARGV, expected => $argv_after);
            }
        }
    }
}

#--------------------------------------------------------------------------#
# Test bad specs
#--------------------------------------------------------------------------#

while ( $trial = shift @bad_specs ) {
    try eval { Getopt::Lucid->new($trial->{spec}) };
    catch my $err;
    ok( $err && $err->isa( $trial->{exception} ),
        "$trial->{label} throws exception" )
        or diag why( got => ref($err), expected => $trial->{exception});
    is( $err, $trial->{error_msg}, "$trial->{label} error msg ok");
}


