The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package OTRS::Repository::Source;

# ABSTRACT: Parser for a single otrs.xml file

use strict;
use warnings;

use Moo;
use HTTP::Tiny;
use XML::LibXML;
use Regexp::Common qw(URI);

our $VERSION = 0.05;

our $ALLOWED_SCHEME = 'HTTP';

has url      => ( is => 'ro', required => 1, isa     => sub { die "No valid URI" unless $_[0] =~ m{\A$RE{URI}{$ALLOWED_SCHEME}\z} } );
has content  => ( is => 'ro', lazy     => 1, builder => \&_get_content );
has tree     => ( is => 'ro', lazy     => 1, builder => \&_build_tree );
has error    => ( is => 'rwp' );
has packages => ( is => 'rwp', default => sub { {} }, isa => sub { die "No hashref" unless ref $_[0] eq 'HASH' } );
has parsed   => ( is => 'rwp', predicate => 1 );

sub find {
    my ($self, %params) = @_;

    return if !exists $params{name};
    return if !exists $params{otrs};

    my $package  = $params{name};
    my $otrs     = $params{otrs};

    if ( !defined $package || !defined $otrs ) {
        return;
    }

    if ( !$self->has_parsed ) {
        $self->_parse( %params );
    }

    my %packages = %{ $self->packages };

    return if !$packages{$package};
    return if !$packages{$package}->{$otrs};

    my $wanted = $params{version} || $packages{$package}->{$otrs}->{latest};
    return $packages{$package}->{$otrs}->{versions}->{$wanted};
}

sub list {
    my ($self, %params) = @_;

    if ( !$self->has_parsed ) {
        $self->_parse( %params );
    }

    my %packages = %{ $self->packages };
    my $otrs     = $params{otrs};

    my @packages = sort keys %packages;

    if ( $otrs ) {
        @packages = grep{ $packages{$_}->{$otrs} }@packages;
    }

    return @packages;
}

sub _parse {
    my ($self, %params) = @_;

    return if !$self->tree;

    my %packages = %{ $self->packages };

    my @repo_packages = $self->tree->findnodes( 'Package' );
    my $base_url      = $self->url;
    $base_url         =~ s{\w+\.xml\z}{};

    REPO_PACKAGE:
    for my $repo_package ( @repo_packages ) {
        my $name       = $repo_package->findvalue( 'Name' );
        my @frameworks = $repo_package->findnodes( 'Framework' );
        my $file       = $repo_package->findvalue( 'File' );

        my $version    = $repo_package->findvalue( 'Version' );

        FRAMEWORK:
        for my $framework ( @frameworks ) {
            my $otrs_version  = $framework->textContent;
            my $short_version = join '.', (split /\./, $otrs_version, 3)[0..1];
            my $saved_version = $packages{$name}->{$short_version}->{latest};

            if ( !$saved_version ) {
                $packages{$name}->{$short_version} = {
                    latest   => $version,
                    versions => {
                      $version => sprintf "%s%s", $base_url, $file,
                    },
                };
            }
            elsif ( $self->_version_is_newer( $version, $saved_version ) ) {
                $packages{$name}->{$short_version}->{latest} = $version;
                $packages{$name}->{$short_version}->{versions}->{$version} =
                    sprintf "%s%s", $base_url, $file;
            }
            else {
                $packages{$name}->{$short_version}->{versions}->{$version} =
                    sprintf "%s%s", $base_url, $file;
            }
        }
    }

    $self->_set_parsed( 1 );
    $self->_set_packages( \%packages );

    return 1;
}

sub _version_is_newer {
    my ($self, $new, $old) = @_;

    my @new_levels = split /\./, $new;
    my @old_levels = split /\./, $old;

    for my $i ( 0 .. ( $#new_levels > $#old_levels ? @new_levels : @old_levels ) ) {
        if ( !$old_levels[$i] || $new_levels[$i] > $old_levels[$i] ) {
            return 1;
        }
        elsif ( $new_levels[$i] < $old_levels[$i] ) {
            return 0;
        }
    }

    return 1;
}

sub _get_content {
    my $self = shift;
    my $res  = HTTP::Tiny->new->get( $self->url );

    $self->_set_error( undef );
    
    if ( $res->{success} ) {
        return $res->{content};
    }

    $self->_set_error( $res->{reason} );

    return '<otrs_packages></otrs_packages>';
}

sub _build_tree {
    my $self = shift;

    $self->_set_error( undef );

    my $tree;
    eval {
        my $parser = XML::LibXML->new->parse_string( $self->content );
        $tree      = $parser->getDocumentElement;
    } or $self->_set_error( $@ );

    return $tree;
}

1;

__END__

=pod

=head1 NAME

OTRS::Repository::Source - Parser for a single otrs.xml file

=head1 VERSION

version 0.05

=head1 AUTHOR

Renee Baecker <github@renee-baecker.de>

=head1 COPYRIGHT AND LICENSE

This software is Copyright (c) 2013 by Renee Baecker.

This is free software, licensed under:

  The Artistic License 2.0 (GPL Compatible)

=cut