The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package WoW::Armory::API;

use strict;
use warnings;

our $VERSION = '1.031';

use base 'Exporter';

our @EXPORT = qw(
    WOW_CHARACTER_FIELDS WOW_GUILD_FIELDS
    WOW_ARENA_TEAM_2 WOW_ARENA_TEAM_3 WOW_ARENA_TEAM_5
);

use URI::Escape;
use LWP::UserAgent;
use JSON::XS;

use constant WOW_REGIONS =>
{
    us  => { api_host => 'us.battle.net'       , locales => [qw(en_US es_MX pt_BR)] },
    eu  => { api_host => 'eu.battle.net'       , locales => [qw(en_GB es_ES fr_FR ru_RU de_DE pt_PT it_IT)] },
    kr  => { api_host => 'kr.battle.net'       , locales => [qw(ko_KR)] },
    tw  => { api_host => 'tw.battle.net'       , locales => [qw(zh_TW)] },
    cn  => { api_host => 'www.battlenet.com.cn', locales => [qw(zh_CN)] },
};

use constant WOW_API_METHODS =>
[
    ['achievement'                 , 1],
    ['auction/data'                , 1],
    ['battlePet/ability'           , 1],
    ['battlePet/species'           , 1],
    ['battlePet/stats'             , 1, [qw(level breedId qualityId)]],
    ['challenge'                   , 1],
    ['challenge/region'            , 0],
    ['character'                   , 2, [qw(fields)]],
    ['item'                        , 1],
    ['item/set'                    , 1],
    ['guild'                       , 2, [qw(fields)]],
    ['arena'                       , 3],
    ['pvp/arena'                   , 2, [qw(page size asc)]],
    ['pvp/ratedbg/ladder'          , 0, [qw(page size asc)]],
    ['quest'                       , 1],
    ['realm/status'                , 0],
    ['recipe'                      , 1],
    ['spell'                       , 1],
    ['data/battlegroups'           , 0],
    ['data/character/races'        , 0],
    ['data/character/classes'      , 0],
    ['data/character/achievements' , 0],
    ['data/guild/rewards'          , 0],
    ['data/guild/perks'            , 0],
    ['data/guild/achievements'     , 0],
    ['data/item/classes'           , 0],
    ['data/talents'                , 0],
    ['data/pet/types'              , 0],
];

use constant WOW_CHARACTER_FIELDS => [qw(achievements appearance feed guild hunterPets items mounts pets petSlots
    professions progression pvp quests reputation stats talents titles)];

use constant WOW_GUILD_FIELDS => [qw(members achievements news challenge)];

use constant WOW_ARENA_TEAM_2 => '2v2';
use constant WOW_ARENA_TEAM_3 => '3v3';
use constant WOW_ARENA_TEAM_5 => '5v5';

sub _init_package {
    my $class = shift;

    for my $def (@{WOW_API_METHODS()}) {
        no strict 'refs';
        *{$class.'::Get'.join('', map { ucfirst $_ } split('/', $def->[0]))} = sub {
            return shift->DoApiCall(
                join('/', $def->[0], splice(@_, 0, $def->[1])),
                map { my $t = shift; defined $t ? ($_ => $t) : () } @{$def->[2]||[]},
            );
        };
    }
}

sub new {
    my $proto = shift;

    my $class = ref $proto || $proto;
    my $self = bless {}, $class;

    return $self->_init(@_);
}

sub _init {
    my ($self, %opts) = @_;

    $self->{ua} = LWP::UserAgent->new;

    $self->SetRegion('us');

    $self->SetRegion($opts{Region}) if exists $opts{Region};
    $self->SetLocale($opts{Locale}) if exists $opts{Locale};

    return $self;
}

sub GetRegions {
    return {regions => [
        map {{region => $_, %{WOW_REGIONS()->{$_}}}} keys %{WOW_REGIONS()}
    ]};
}

sub SetRegion {
    my ($self, $region, $locale) = @_;
    return if !WOW_REGIONS()->{$region};
    $self->{region} = $region;
    $self->SetLocale($locale);
}

sub HasLocale {
    my ($self, $locale) = @_;
    return !!grep {$_ eq $locale}
        @{WOW_REGIONS()->{$self->{region}}{locales}}
}

sub SetLocale {
    my ($self, $locale) = @_;
    if (!defined $locale) {
        $self->{locale} = undef;
        return;
    }
    return if !$self->HasLocale($locale);
    $self->{locale} = $locale;
}

sub GetApiHost {
    my ($self) = @_;
    return WOW_REGIONS()->{$self->{region}}{api_host};
}

sub DoApiCall {
    my ($self, $method, @opts) = @_;

    push @opts, (locale => $self->{locale}) if $self->{locale};
    push @opts, (rand => rand);

    my $query = join('&', map {
        uri_escape_utf8(shift @opts).'='.uri_escape_utf8(shift @opts)
    } (1..scalar(@opts)/2));

    my $url = 'http://'.$self->GetApiHost."/api/wow/${method}?${query}";
    my $res = $self->{ua}->get($url);

    #return undef if !$res->is_success;

    my $data = eval { decode_json $res->decoded_content };

    return $@ ? undef : $data;
}

__PACKAGE__->_init_package;

1;

=head1 NAME

WoW::Armory::API - Perl interface to WoW API

=head1 SYNOPSIS

    use WoW::Armory::API;

    $api = WoW::Armory::API->new(Region => 'eu', Locale => 'ru_RU');

    $char_data = $api->GetCharacter('realm', 'Character', 'items,pets,mounts');
    $guild_data = $api->GetGuild('realm', 'Guild');

    print $char_data->{items}{head}{name};
    print $guild_data->{name};

    use WoW::Armory::Class::Character;
    use WoW::Armory::Class::Guild;

    $char = WoW::Armory::Class::Character->new($char_data);
    $guild = WoW::Armory::Class::Guild->new($guild_data);

    print $char->items->head->name;
    print $guild->name;

=head1 METHODS

=head2 Constants

=head3 WOW_CHARACTER_FIELDS

    @fields = @{WOW_CHARACTER_FIELDS()};
    $data = $api->GetCharacter($realmId, $characterName, join(',', @fields));

=head3 WOW_GUILD_FIELDS

    @fields = @{WOW_GUILD_FIELDS()};
    $data = $api->GetGuild($realmId, $guildName, join(',', @fields));

=head3  WOW_ARENA_TEAM_2

    $data = $api->GetArena($realmId, WOW_ARENA_TEAM_2, $teamName);

=head3 WOW_ARENA_TEAM_3

    $data = $api->GetArena($realmId, WOW_ARENA_TEAM_3, $teamName);

=head3 WOW_ARENA_TEAM_5

    $data = $api->GetArena($realmId, WOW_ARENA_TEAM_5, $teamName);

=head2 Constructor

=head3 new()

    $api = WoW::Armory::API->new;
    $api = WoW::Armory::API->new(Region => $regionId, Locale => $locale);

=head2 General

=head3 GetRegions()

    $data = WoW::Armory::API->GetRegions();
    $data = $api->GetRegions();

=head3 SetRegion()

    $api->SetRegion($regionId);
    $api->SetRegion($regionId, $locale);

=head3 HasLocale()

    $hasLocale = $api->HasLocale($locale);

=head3 SetLocale()

    $api->SetLocale($locale);

=head3 GetApiHost()

    $host = $api->GetApiHost();

=head3 DoApiCall()

    $data = $api->DoApiCall($method, @params);

=head2 WoW API

All of these methods return the appropriate data structure or undef.
See L<http://blizzard.github.com/api-wow-docs/> for more details.

=head3 GetAchievement()

    $data = $api->GetAchievement($achievementId);

=head3 GetAuctionData()

    $data = $api->GetAuctionData($realmId);

=head3 GetBattlePetAbility()

    $data = $api->GetBattlePetAbility($abilityId);

=head3 GetBattlePetSpecies()

    $data = $api->GetBattlePetSpecies($speciesId);

=head3 GetBattlePetStats()

    $data = $api->GetBattlePetStats($speciesId);
    $data = $api->GetBattlePetStats($speciesId, $level, $breedId, $qualityId);

=head3 GetChallenge()

    $data = $api->GetChallenge($realmId);

=head3 GetChallengeRegion()

    $data = $api->GetChallengeRegion();

=head3 GetCharacter()

    $data = $api->GetCharacter($realmId, $characterName);
    $data = $api->GetCharacter($realmId, $characterName, $fields);

=head3 GetItem()

    $data = $api->GetItem($itemId);

=head3 GetItemSet()

    $data = $api->GetItemSet($itemSetId);

=head3 GetGuild()

    $data = $api->GetGuild($realmId, $guildName);
    $data = $api->GetGuild($realmId, $guildName, $fields);

=head3 GetArena()

    $data = $api->GetArena($realmId, $teamSize, $teamName);

=head3 GetPvpArena()

    $data = $api->GetPvpArena($battleGroup, $teamSize);
    $data = $api->GetPvpArena($battleGroup, $teamSize, $page, $pageSize, $asc);

=head3 GetPvpRatedbgLadder()

    $data = $api->GetPvpRatedbgLadder();
    $data = $api->GetPvpRatedbgLadder($page, $pageSize, $asc);

=head3 GetQuest()

    $data = $api->GetQuest($questId);

=head3 GetRealmStatus()

    $data = $api->GetRealmStatus();

=head3 GetRecipe()

    $data = $api->GetRecipe($recipeId);

=head3 GetSpell()

    $data = $api->GetSpell($spellId);

=head3 GetDataBattlegroups()

    $data = $api->GetDataBattlegroups();

=head3 GetDataCharacterRaces()

    $data = $api->GetDataCharacterRaces();

=head3 GetDataCharacterClasses()

    $data = $api->GetDataCharacterClasses();

=head3 GetDataCharacterAchievements()

    $data = $api->GetDataCharacterAchievements();

=head3 GetDataGuildRewards()

    $data = $api->GetDataGuildRewards();

=head3 GetDataGuildPerks()

    $data = $api->GetDataGuildPerks();

=head3 GetDataGuildAchievements()

    $data = $api->GetDataGuildAchievements();

=head3 GetDataItemClasses()

    $data = $api->GetDataItemClasses();

=head3 GetDataTalents()

    $data = $api->GetDataTalents();

=head3 GetDataPetTypes()

    $data = $api->GetDataPetTypes();

=head1 SEE ALSO

L<http://blizzard.github.com/api-wow-docs/>

=head1 REPOSITORY

The source code for the WoW::Armory::API is held in a public git repository
on Github: L<https://github.com/Silencer2K/perl-wow-api>

=head1 AUTHOR

Aleksandr Aleshin F<E<lt>silencer@cpan.orgE<gt>>

=head1 COPYRIGHT

This software is copyright (c) 2012 by Aleksandr Aleshin.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut