package WebService::Lingr;

use strict;
our $VERSION = '0.01';

use Carp;
use Data::Visitor::Callback;
use HTTP::Request::Common;
use LWP::UserAgent;
use JSON::Syck;
use URI;

our $APIBase = "http://www.lingr.com/api";

# scraped from Lingr wiki page
our $Methods = {
    'session.create' => 'POST',
    'session.destroy' => 'POST',
    'auth.login' => 'POST',
    'auth.logout' => 'POST',
    'explore.getHotRooms' => 'GET',
    'explore.getNewRooms' => 'GET',
    'explore.getHotTags' => 'GET',
    'explore.getAllTags' => 'GET',
    'explore.search' => 'GET',
    'explore.searchTags' => 'GET',
    'user.getInfo' => 'GET',
    'user.startObserving' => 'POST',
    'user.observe' => 'GET',
    'user.stopObserving' => 'POST',
    'room.getInfo' => 'GET',
    'room.enter' => 'POST',
    'room.getMessages' => 'GET',
    'room.observe' => 'GET',
    'room.setNickname' => 'POST',
    'room.say' => 'POST',
    'room.exit' => 'POST',
};

sub new {
    my($class, %args) = @_;

    my %self;
    $self{api_key} = $args{api_key} or croak "api_key is required.";
    $self{ua}      = $args{ua} || LWP::UserAgent->new(agent => __PACKAGE__ . "/$VERSION");

    my $self = bless \%self, $class;

    unless($args{no_create_session}) {
        my $res = $self->create_session;
        $self->{session} = $res->{session};
    }

    $self;
}

sub create_session {
    my $self = shift;
    $self->_call('session.create', { api_key => $self->{api_key} });
}

sub call {
    my($self, $method, $args) = @_;
    $self->_call($method, { %$args, session => $self->{session} });
}

sub _call {
    my($self, $method, $args) = @_;

    my @method = split /\./, $method;
    my $uri = URI->new($APIBase . "/" . join("/", @method));

    # downgrade all parameters to utf-8, if they're Unicode
    my $v = Data::Visitor::Callback->new(
        plain_value => sub {
            if (utf8::is_utf8($_)) {
                utf8::encode($_);
            }
        },
        ignore_return_values => 1,
    );

    $v->visit($args);

    my $req_method = $Methods->{$method} || do {
        Carp::carp "Don't know method '$method'. Defaults to GET";
        "GET";
    };

    $args->{format} = 'json';

    my $req;
    if ($req_method eq 'GET') {
        $uri->query_form(%$args);
        $req = HTTP::Request->new(GET => $uri);
    } else {
        $req = HTTP::Request::Common::POST( $uri, [ %$args ] );
    }

    my $res = $self->{ua}->request($req);
    $self->_parse_response($res);
}

sub _parse_response {
    my($self, $res) = @_;

    $res->is_success or croak "Request failed: " . $res->status_line;

    local $JSON::Syck::ImplicitUnicode = 1;
    my $data = JSON::Syck::Load($res->content);
    $data->{status} eq 'ok' or croak "Response error: $data->{error}->{message} ($data->{error}->{code})";

    return $self->{res} = $data;
}

sub response { $_[0]->{res} }

sub DESTROY {
    my $self = shift;
    $self->call('session.destroy');
}

1;
__END__

=for stopwords JSON API Lingr

=head1 NAME

WebService::Lingr - Low-level Lingr Chat API

=head1 SYNOPSIS

  use WebService::Lingr;

  # create a session using your API key
  my $lingr = WebService::Lingr->new(api_key => "YOUR_API_KEY");

  # enter the room 'MyFavoriteRoom' with nick 'api-dude'
  my $res = $lingr->call('room.enter', { id => 'MyFavoriteRoom', nickname => 'api-dude' });
  my $ticket  = $res->{ticket};
  my $counter = $res->{counter};

  # say "Hello world!"
  my $res = $lingr->call('room.say', { message => 'hello world', ticket => $ticket });

  # room.observe blocks
  while (1) {
      my $res = $lingr->call('room.observe', { ticket => $ticket, counter => $counter });
      for my $message (@{$res->{messages}}) {
          print "$message->{nick} says: $message->{content}\n";
      }
  }

  # room.getMessages doesn't, but you can call this method at most once per minute
  while (1) {
      my $res = $lingr->call('room.getMessages', { ticket => $ticket, counter => $counter });
      # do something ...
      sleep 60;
  }

=head1 DESCRIPTION

WebService::Lingr is a low-level Lingr API implementation in Perl. By
"low-level" it means that this module just gives you a straight
mapping of Perl object methods to Lingr REST API, session management
and data mapping via JSON.

For higher level event driven programming, you might want to use
POE::Component::Client::Lingr (unfinished).

=head1 AUTHOR

Tatsuhiko Miyagawa E<lt>miyagawa@bulknews.netE<gt>

=head1 LICENSE

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

=head1 SEE ALSO

L<http://wiki.lingr.com/dev/show/HomePage>

=cut
