package WWW::Github::Files;
use strict;
use warnings;
use LWP::UserAgent;
use JSON qw{decode_json};
use Carp;

our $VERSION = 0.01;

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

    die "Please pass a author name"
        unless exists $options{author};
    die "Please pass a resp name"
        unless exists $options{resp};
    die "Please pass either a branch name or a commit"
        unless exists $options{branch} or exists $options{commit};

    my $self = {};
    foreach my $key (qw( author resp token branch commit )) {
        next unless exists $options{$key};
        $self->{$key} = $options{$key};
    }
    if (not exists $self->{token}) {
        $self->{ua} = LWP::UserAgent->new();
    }
    $self->{apiurl} = 'https://api.github.com/repos/'.$options{author}.'/'.$options{resp};
    bless $self, $class;
}

sub open {
    my ($self, $path) = @_;
    croak("Path should start with '/'! |$path|")
        unless $path =~ m!^/!;
    my $commit = $self->__fetch_root();
    $path =~ s!/$!!;
    my $f_data = $self->geturl("/contents$path?ref=$commit");
    if (ref($f_data) eq 'ARRAY') {
        # a directory
        my ($name) = $path =~ m!/([^/]+)$!;
        my $dir = {
            FS => $self,
            content => $f_data,
            name => $name,
            path => substr($path, 1),
        };
        return bless $dir, 'WWW::Github::Files::Dir';
    }
    elsif ($f_data->{type} eq 'file') {
        return bless $f_data, 'WWW::Github::Files::File';
    }
    else {
        croak('unrecognised file type for $path');
    }
}

sub get_file {
    my ($self, $path) = @_;
    return $self->open($path)->read();
}

sub get_dir {
    my ($self, $path) = @_;
    return $self->open($path)->readdir();
}

sub __fetch_root {
    my $self = shift;
    my $root = $self->{root_commit};
    return $root if $root;

    if ($self->{branch}) {
        my $b_data = $self->geturl('/branches/'.$self->{branch});
        $root = $b_data->{commit}->{sha};
    }
    else {
        my $c_data = $self->geturl('/git/commits/'.$self->{commit});
        $root = $self->{commit};
    }
    $self->{root_commit} = $root;
    return $root;
}

sub geturl {
    my ($self, $url, $method) = @_;
    my $token = $self->{token} || $self->{ua};
    $method ||= 'get';
    my $res = $token->$method($self->{apiurl} . $url);
    if (!$res->is_success()) {
        die "Failed to read $self->{apiurl}$url from github: ".$res->message();
    }
    my $content = $res->content;
    return decode_json($content);
}

package WWW::Github::Files::File;
use MIME::Base64 qw{decode_base64};

sub is_file { 1 }
sub is_dir { 0 }

sub name { return $_[0]->{name} }
sub path { return '/'.$_[0]->{path} }

sub read {
    my $self = shift;
    if (not $self->{content}) {
        # this is a file object created from directory listing. 
        # need to fetch the content
        my $f_data = $self->{FS}->open('/'.$self->{path});
        $self->{$_} = $f_data->{$_} for (qw{ encoding content });
    }
    if ($self->{encoding} eq 'base64') {
        return decode_base64($self->{content});
    }
    else {
        die "can not handle encoding " . $self->{encoding} . " for file ". $self->{path};
    }
}

package WWW::Github::Files::Dir;

sub is_file { 0 }
sub is_dir { 1 }

sub name { return $_[0]->{name} }
sub path { return '/'.$_[0]->{path} }

sub readdir {
    my $self = shift;
    if (not $self->{content}) {
        # this is a file object created from directory listing. 
        # need to fetch the content
        my $f_data = $self->{FS}->open('/'.$self->{path});
        $self->{content} = $f_data;
    }
    my @files;
    foreach my $rec (@{ $self->{content} }) {
        $rec->{FS} = $self->{FS};
        if ($rec->{type} eq 'file') {
            push @files, bless($rec, 'WWW::Github::Files::File');
        }
        elsif ($rec->{type} eq 'dir') {
            push @files, bless($rec, 'WWW::Github::Files::Dir');
        }
        else {
            croak('unrecognised file type: '.$rec->{type});
        }
    }
    return @files;
}

1;

=head1 NAME

WWW::Github::Files - Read files and directories from Github

=head1 SYNOPSIS

    my $gitfiles = WWW::Github::Files->new(
        author => 'semuel',
        resp => 'site-lang-collab',
        branch => 'master',
    );

    my @files = $gitfiles->open('/')->readdir();

=head1 DESCRIPTION

Using Github API to browse a git resp easily and download files

This modules is a thin warper around the API, just to make life easier

=head1 CONSTRUCTOR OPTIONS

=over 4

=item author - resp author

=item resp - resp name

=item branch - The branch to read from

Mutual exlusive with 'commit'. 

On first access the object will "lock" on the latest commit in this branch,
and from this point will serve files only from this commit

=item commit - a specific commit to read from

The object will retrive files and directories as they were after this commit

=item token

Optional Net::Oauth2 Access Token, for using in API calls.
If not specified, will make anonymous calls using LWP

=back

=head1 METHODS

=head2 open(path)

receive path (which have to start with '/') and return file or dir object
for that location

=head2 get_file(path)

shortcut to $gitfiles->open(path)->read()

=head2 get_dir(path)

shortcut to $gitfiles->open(path)->readdir()

=head1 FILE OBJECT METHODS

=head2 name

The name of the file

=head2 path

full path (+name) of the file

=head2 is_file

=head2 is_dir

=head2 read

returns the content of the file

=head1 DIRECTORY OBJECT METHODS

=head2 name

The name of the directory

=head2 path

full path (+name) of the directory

=head2 is_file

=head2 is_dir

=head2 readdir

returns a list of file/dir objects that this directory contains

=head1 AUTHOR
 
Fomberg Shmuel, E<lt>owner@semuel.co.ilE<gt>
 
=head1 COPYRIGHT AND LICENSE
 
Copyright 2013 by Shmuel Fomberg.
 
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
 
=cut
