The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;
use warnings;

package WebService::TVDB::Series;
{
  $WebService::TVDB::Series::VERSION = '1.122800';
}

# ABSTRACT: Represents a TV Series

use WebService::TVDB::Actor;
use WebService::TVDB::Banner;
use WebService::TVDB::Episode;
use WebService::TVDB::Util qw(pipes_to_array);

use Carp qw(carp);
use File::Basename qw(dirname);
use File::Path qw(mkpath);
use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
use LWP::Simple ();
use XML::Simple qw(:strict);

# Assessors
# alphabetically, case insensitive
# First section from http://www.thetvdb.com/api/GetSeries.php?seriesname=...
# Second section from <langauge.xml>
# Third section are WebService::TVDB:: objects
# Forth section are API values
use Object::Tiny qw(
  banner
  FirstAired
  id
  IMDB_ID
  language
  Overview
  seriesid
  SeriesName
  zap2it_id

  added
  addedBy
  Actors
  Airs_DayOfWeek
  Airs_Time
  ContentRating
  fanart
  Genre
  Language
  lastupdated
  Network
  NetworkID
  poster
  Rating
  RatingCount
  Runtime
  SeriesID
  Status

  actors
  banners
  episodes

  _api_key
  _api_language
  _api_mirrors
  _max_retries
);

# the url for full series data
use constant URL => '%s/api/%s/series/%s/all/%s.zip';

# the local path for full series data
use constant CACHE_PATH => '%s/.tvdbcache/series/%s/all/%s.zip';

# xml files in the zip
use constant ACTORS_XML_FILE  => 'actors.xml';
use constant BANNERS_XML_FILE => 'banners.xml';

sub fetch {
    my ($self) = @_;

    my $url        = $self->_url;
    my $cache_path = $self->_cache_path;
    $cache_path =~ /\A(.*)\z/s or die; $cache_path = $1; # ensure its untainted
    my $dir        = dirname($cache_path);
    -e $dir or mkpath($dir) or die 'could not create ' . $dir;

    my $agent = $LWP::Simple::ua->agent;
    $LWP::Simple::ua->agent( "WebService::TVDB/$WebService::TVDB::VERSION" );
    # get the zip
    my $res = LWP::Simple::mirror( $url, $cache_path );
    my $retries = 0;
    until (  $res == LWP::Simple::RC_NOT_MODIFIED
          || LWP::Simple::is_success($res)
          || $retries == $self->_max_retries )
    {
        carp "failed to get URL $url: $res - retrying";

        # TODO configurable wait time
        sleep 1;
        $res = LWP::Simple::mirror( $url, $cache_path );

        $retries++;
    }
    $LWP::Simple::ua->agent( $agent );
    if ( $retries == $self->_max_retries ) {
        die "failed to get URL $url after $retries retries. Aborting.";
    }
    my $zip = Archive::Zip->new();
    unless ( $zip->read($cache_path) == AZ_OK ) {
        die 'could not read zip at ' . $cache_path;
    }

    # parse the xml files
    my $status;
    my $xml;
    my $parsed_xml;

    my $series_xml_file = $self->language . '.xml';
    ( $xml, $status ) = $zip->contents($series_xml_file);
    unless ( $status == AZ_OK ) {
        die 'could not read ' . $series_xml_file;
    }
    $parsed_xml = XML::Simple::XMLin(
        $xml,
        ForceArray    => ['Data'],
        KeyAttr       => 'Data',
        SuppressEmpty => 1
    );
    $self->_parse_series_data($parsed_xml);

    ( $xml, $status ) = $zip->contents(ACTORS_XML_FILE);
    unless ( $status == AZ_OK ) {
        die 'could not read ' . ACTORS_XML_FILE;
    }
    $parsed_xml = XML::Simple::XMLin(
        $xml,
        ForceArray    => ['Actor'],
        KeyAttr       => 'Actor',
        SuppressEmpty => 1
    );
    $self->_parse_actors($parsed_xml);

    ( $xml, $status ) = $zip->contents(BANNERS_XML_FILE);
    unless ( $status == AZ_OK ) {
        die 'could not read ' . BANNERS_XML_FILE;
    }
    $parsed_xml = XML::Simple::XMLin(
        $xml,
        ForceArray    => ['Banner'],
        KeyAttr       => 'Banner',
        SuppressEmpty => 1
    );
    $self->_parse_banners($parsed_xml);
}

sub get_episode {
    my ( $self, $season_number, $episode_number ) = @_;

    for my $episode ( @{ $self->episodes } ) {
        if ( $episode->SeasonNumber eq $season_number ) {
            if ( $episode->EpisodeNumber eq $episode_number ) {
                return $episode;
            }
        }

    }
}

# generates the url for full series data
sub _url {
    my ($self) = @_;
    return sprintf( URL,
        $self->_api_mirrors->get_mirror,
        $self->_api_key, $self->seriesid,
        $self->_api_language->{abbreviation} );
}

# generates the local path for full series data
# TODO configurable path
sub _cache_path {
    my ($self) = @_;
    require File::HomeDir;
    return sprintf( CACHE_PATH,
        File::HomeDir->my_home, $self->seriesid,
        $self->_api_language->{abbreviation} );
}

# parse <language>.xml
sub _parse_series_data {
    my ( $self, $xml ) = @_;

    # populate extra Series data
    while ( my ( $key, $value ) = each( %{ $xml->{Series} } ) ) {
        if ( $key eq 'Genre' || $key eq 'Actors' ) {
            $self->{$key} = pipes_to_array($value);
        }
        else {
            $self->{$key} = $value;
        }
    }

    # populate Episodes
    my @episodes;
    for ( @{ $xml->{Episode} } ) {
        push @episodes, WebService::TVDB::Episode->new( %{$_} );

    }
    $self->{episodes} = \@episodes;
    return $self->{episodes};
}

# parse actors.xml
sub _parse_actors {
    my ( $self, $xml ) = @_;

    my @actors;
    for ( @{ $xml->{Actor} } ) {
        push @actors, WebService::TVDB::Actor->new( %{$_} );

    }
    $self->{actors} = \@actors;
    return $self->{actors};
}

# parse banners.xml
sub _parse_banners {
    my ( $self, $xml ) = @_;

    my @banners;
    for ( @{ $xml->{Banner} } ) {
        push @banners, WebService::TVDB::Banner->new( %{$_} );

    }
    $self->{banners} = \@banners;
    return $self->{banners};
}

1;



=pod

=head1 NAME

WebService::TVDB::Series - Represents a TV Series

=head1 VERSION

version 1.122800

=head1 ATTRIBUTES

=head2 banner

=head2 FirstAired

=head2 id

=head2 IMDB_ID

=head2 language

=head2 Overview

=head2 seriesid

=head2 SeriesName

=head2 zap2it_id

=head2 added

Populated after fetch

=head2 addedBy

Populated after fetch

=head2 Actors

Populated after fetch

=head2 Airs_DayOfWeek

Populated after fetch

=head2 Airs_Time

Populated after fetch

=head2 ContentRating

Populated after fetch

=head2 fanart

Populated after fetch

=head2 Genre

Populated after fetch

=head2 Language

Populated after fetch

=head2 lastupdated

Populated after fetch

=head2 Network

Populated after fetch

=head2 NetworkID

Populated after fetch

=head2 poster

Populated after fetch

=head2 Rating

Populated after fetch

=head2 RatingCount

Populated after fetch

=head2 Runtime

Populated after fetch

=head2 SeriesID

Populated after fetch

=head2 Status

Populated after fetch

=head2 actors

An array of L<WebService::TVDB::Actor> objects. Populated after fetch.

=head2 banners

An array of L<WebService::TVDB::Banner> objects. Populated after fetch.

=head2 episodes

An array of L<WebService::TVDB::Episode> objects. Populated after fetch.

=head1 METHODS

=head2 fetch

Fecthes the full data from the TVDB, inluding all the episodes.

=head2 get_episode ( $season_number, $episode_number )

Gets a parcular episode. Returns a L<WebService::TVDB::Episode> object.

=head1 AUTHOR

Andrew Jones <andrew@arjones.co.uk>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2012 by Andrew Jones.

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


__END__