/usr/local/CPAN/NetHack-Item/NetHack/Item/Spoiler.pm


package NetHack::Item::Spoiler;
BEGIN {
  $NetHack::Item::Spoiler::VERSION = '0.13';
}
use strict;
use warnings;

use Module::Pluggable (
    search_path => __PACKAGE__,
    require     => 1,
    sub_name    => 'spoiler_types',
);

use Memoize;
memoize 'list';
memoize 'name_to_type_list';
memoize 'possibilities_to_appearances';
memoize 'plurals';
memoize 'plural_of_list';
memoize 'singular_of_list';
memoize 'all_identities';

my %artifact;

# actual item lookups {{{
sub spoiler_for {
    my $self = shift;
    my $name = shift;

    my $subspoiler = $self->name_to_class($name)
        or return;

    return $subspoiler->list->{$name};
}

sub list {
    my $self = shift;
    my ($items, %defaults) = $self->_list;
    my $type = lc $self;
    $type =~ s/.*:://;

    my @defer_appearance;

    # tag each item with its name, weight, appearances, etc
    for my $name (keys %$items) {
        my $stats = $items->{$name};
        $stats->{name}        = $name;
        $stats->{type}        = $type;
        $stats->{weight}    ||= $defaults{weight};
        $stats->{material}  ||= $defaults{material};
        $stats->{price}     ||= $defaults{price};
        $stats->{stackable} ||= $defaults{stackable};
        $stats->{glyph}     ||= $defaults{glyph};
        $stats->{plural}      = $defaults{plural}($name)
            if exists $defaults{plural};

        unless (exists $stats->{appearance} || exists $stats->{appearances}) {
            # the base item may not be processed yet, so we need to defer
            # checking this artifact's appearance for now..
            push @defer_appearance, $stats
                if $stats->{artifact} && $stats->{base};

            my $appearance = $defaults{appearance}
                          || $defaults{appearances}
                          || $name;

            if (ref $appearance eq 'ARRAY') {
                $stats->{appearances} = $appearance;
            }
            else {
                $stats->{appearance} = $appearance;
            }
        }
    }

    for my $stats (@defer_appearance) {
        $stats->{appearance} = $items->{ $stats->{base} }->{appearance};
        $stats->{appearances} = $items->{ $stats->{base} }->{appearances};
    }

    return $items;
}
# }}}
# names, appearances, and types {{{
sub name_to_type_list {
    my $self = shift;
    my %all_types;

    for my $class ($self->spoiler_types) {
        my $type = $class->type;

        my $list = $class->list;
        for (values %$list) {
            $all_types{$_->{name}} = $type;
            $all_types{$_} = $type
                for grep { defined }
                    $_->{appearance},
                    @{ $_->{appearances} || [] };

            $artifact{lc $_->{name}} = $_
                if $_->{artifact};
        }

        if ($class->can('extra_names')) {
            for ($class->extra_names) {
                $all_types{$_} = $type;
            }
        }
    }

    return \%all_types;
}

sub all_identities {
    my $self = shift;
    my @identities;

    for my $class ($self->spoiler_types) {
        my $list = $class->list;
        for (values %$list) {
            push @identities, $_->{name};
        }
    }

    return @identities;
}

sub name_to_type {
    my $self = shift;
    my $name = shift;

    my $list = $self->name_to_type_list;

    my $type = $list->{ $name || '' }
            || $list->{ $self->singularize($name) || '' };

    # handle e.g. "potion called fruit juice"
    $type ||= $name if $self->type_to_class($name)->can('list');

    return $type;
}

sub type_to_class {
    my $self = shift;
    my $type = shift;

    return __PACKAGE__ . "::\u\L$type";
}

sub name_to_class {
    my $self = shift;
    my $name = shift;

    my $type = $self->name_to_type($name);

    return undef if !$type;
    return $self->type_to_class($type);
}
# }}}
# possibilities and appearances {{{
sub possibilities_to_appearances {
    my $self = shift;
    my $list = $self->list;

    my %possibilities;

    for my $stats (values %$list) {
        next if $stats->{artifact} # artifacts are always known
             && $stats->{base};    # ..but we still want the special artifacts

        push @{ $possibilities{$_} }, $stats->{name}
            for grep { defined }
                     $stats->{appearance},
                     @{ $stats->{appearances} };
    }

    return \%possibilities;
}

sub possibilities_for_appearance {
    my $self = shift;
    my $appearance = shift;
    my $possibilities;

    my $subspoiler = $self->name_to_class($appearance)
        or return;

    $possibilities = [$appearance] if $subspoiler->list->{$appearance};
    $possibilities ||= $subspoiler->possibilities_to_appearances->{$appearance};
    $possibilities ||= [];

    return $possibilities;
}
# }}}
# singularize and pluralize {{{
sub plurals {
    my $self = shift;
    my $list = $self->list;
    my %plurals;

    for (values %$list) {
        $plurals{$_->{name}} = $_->{plural}
            if $_->{plural};
    }

    if ($self->can('extra_plurals')) {
        my $extra = $self->extra_plurals;
        @plurals{keys %$extra} = values %$extra;
    }

    return \%plurals;
}

sub plural_of_list {
    my $self = shift;
    my %all_plurals;

    for my $class ($self->spoiler_types) {
        my $plurals = $class->plurals;
        @all_plurals{keys %$plurals} = values %$plurals;
    }

    return \%all_plurals;
}

sub singular_of_list {
    my $self = shift;
    return { reverse %{ $self->plural_of_list } };
}

sub pluralize {
    my $self = shift;
    my $item = shift;

    $self->plural_of_list->{$item};
}

sub singularize {
    my $self = shift;
    my $item = shift;

    $self->singular_of_list->{$item};
}
# }}}
# japanese names {{{
sub japanese_to_english {
    return {
        "wakizashi"       => "short sword",
        "ninja-to"        => "broadsword",
        "nunchaku"        => "flail",
        "naginata"        => "glaive",
        "osaku"           => "lock pick",
        "koto"            => "wooden harp",
        "shito"           => "knife",
        "tanko"           => "plate mail",
        "kabuto"          => "helmet",
        "yugake"          => "leather gloves",
        "gunyoki"         => "food ration",
        "potion of sake"  => "potion of booze",
        "potions of sake" => "potions of booze",
    };
}
# }}}
# artifacts {{{
sub artifact_spoiler {
    my $self = shift;
    my $name = lc(shift);

    $name =~ s/^the\s+//;

    return $artifact{$name};
}
# }}}
# collapsing values {{{
sub collapse_value {
    my $self = shift;
    my $key  = shift;

    my @values = map { $self->spoiler_for($_)->{$key} } @_;
    my $value = shift @values;
    return undef if !defined($value);

    for (@values) {
        return undef if !defined($_) || $_ ne $value;
    }

    return $value;
}
# }}}

1;