The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package JS::YUI::Loader::Manifest;

use strict;
use warnings;

use Moose;

use Algorithm::Dependency::Ordered;
use Algorithm::Dependency::Source::HoA;
# TODO use Hash::Dirty

has catalog => qw/is ro required 1 isa JS::YUI::Loader::Catalog/;
has loader => qw/is ro isa JS::YUI::Loader/;
has collection => qw/is ro required 1 lazy 1/, default => sub { {} };
has dirty => qw/is rw required 1 lazy 1 default 1/;
has include => qw/is ro required 1 lazy 1/, default => sub {
    my $self = shift;
    require JS::YUI::Loader::IncludeExclude;
    return JS::YUI::Loader::IncludeExclude->new(manifest => $self, do_include => 1);
};
has exclude => qw/is ro required 1 lazy 1/, default => sub {
    my $self = shift;
    require JS::YUI::Loader::IncludeExclude;
    return JS::YUI::Loader::IncludeExclude->new(manifest => $self, do_include => 0);
};

sub schedule {
    my $self = shift;
    my @schedule = $self->_calculate;
    return wantarray ? @schedule : \@schedule;
}

my $dependency;
sub _calculate {
    my $self = shift;
    if (! $self->{schedule} || $self->dirty) {
        $dependency ||= Algorithm::Dependency::Ordered->new(
            source => Algorithm::Dependency::Source::HoA->new($self->catalog->dependency_graph),
        );
        my $schedule = $dependency->schedule(keys %{ $self->collection }) || [];
        my @schedule = map { $self->catalog->entry($_) } @$schedule;
        my (@css_schedule, @js_schedule);
        for (@schedule) {
            if ($_->css) {
                push @css_schedule, $_;
            }
            else {
                push @js_schedule, $_;
            }
        }
#        my @css_schedule = grep { $_->css } @schedule;
#        my @js_schedule = grep { $_->js } @schedule;
        @css_schedule = sort { $a->rank <=> $b->rank } @css_schedule;

        for (@js_schedule) {
            push @css_schedule, $self->catalog->entry($_->name . "-skin") if $_->skin;
        }
    
        $self->{schedule} = [ map { $_->name } @css_schedule, @js_schedule ];
    }
    return @{ $self->{schedule} };
}

sub parse {
    my $self = shift;
    my @_collection = map { split m/\n/ } @_;

    my @collection;
    for (@_collection) {
        next if m/^\s*#/;
        next if m/^\s*<!--/;
        next if m/^\s*$/;
        chomp;

        my $name = $_;

        if      ($name =~ m/^\s*<script/) { ($name) = $name =~ m{src="(?:[^"]*)([^/]+)\.js"} }
        elsif   ($name =~ m/^\s*<link/)   { ($name) = $name =~ m{href="(?:[^"]*)([^/]+)\.css"} }

        $name =~ s/-beta\b//;
        $name =~ s/-min\b//;
        $name =~ s/-debug\b//;

        push @collection, $name;

    }

    $self->select(@collection);
}

sub select {
    my $self = shift;
    for my $name (@_) {
        warn "Can't find \"$name\" in the catalog" and next unless $self->catalog->entry($name);
        $self->collection->{$name} = "";
    }
}

sub clear {
    my $self = shift;
    $self->{collection} = {};
}

1;

__END__

BEGIN { 
    my $json = JSON->new;
    my $catalog = $json->decode(map { local $_ = $_; s/'/"/g; $_ } (<<_END_));
{
        'animation': {
            'type': 'js',
            'path': 'animation/animation-min.js',
            'requires': ['dom', 'event']
        },

        'autocomplete': {
            'type': 'js',
            'path': 'autocomplete/autocomplete-min.js',
            'requires': ['dom', 'event'],
            'optional': ['connection', 'animation'],
            'skinnable': true
        },

        'base': {
            'type': 'css',
            'path': 'base/base-min.css',
            'after': ['reset', 'fonts', 'grids']
        },

        'button': {
            'type': 'js',
            'path': 'button/button-min.js',
            'requires': ['element'],
            'optional': ['menu'],
            'skinnable': true
        },

        'calendar': {
            'type': 'js',
            'path': 'calendar/calendar-min.js',
            'requires': ['event', 'dom'],
            'skinnable': true
        },

        'charts': {
            'type': 'js',
            'path': 'charts/charts-experimental-min.js',
            'requires': ['element', 'json', 'datasource']
        },

        'colorpicker': {
            'type': 'js',
            'path': 'colorpicker/colorpicker-min.js',
            'requires': ['slider', 'element'],
            'optional': ['animation'],
            'skinnable': true
        },

        'connection': {
            'type': 'js',
            'path': 'connection/connection-min.js',
            'requires': ['event']
        },

        'container': {
            'type': 'js',
            'path': 'container/container-min.js',
            'requires': ['dom', 'event'],
            'optional': ['dragdrop', 'animation', 'connection'],
            'supersedes': ['containercore'],
            'skinnable': true
        },

        'containercore': {
            'type': 'js',
            'path': 'container/container_core-min.js',
            'requires': ['dom', 'event'],
            'pkg': 'container'
        },

        'cookie': {
            'type': 'js',
            'path': 'cookie/cookie-beta-min.js',
            'requires': ['yahoo']
        },

        'datasource': {
            'type': 'js',
            'path': 'datasource/datasource-beta-min.js',
            'requires': ['event'],
            'optional': ['connection']
        },

        'datatable': {
            'type': 'js',
            'path': 'datatable/datatable-beta-min.js',
            'requires': ['element', 'datasource'],
            'optional': ['calendar', 'dragdrop'],
            'skinnable': true
        },

        'dom': {
            'type': 'js',
            'path': 'dom/dom-min.js',
            'requires': ['yahoo']
        },

        'dragdrop': {
            'type': 'js',
            'path': 'dragdrop/dragdrop-min.js',
            'requires': ['dom', 'event']
        },

        'editor': {
            'type': 'js',
            'path': 'editor/editor-beta-min.js',
            'requires': ['menu', 'element', 'button'],
            'optional': ['animation', 'dragdrop'],
            'skinnable': true
        },

        'element': {
            'type': 'js',
            'path': 'element/element-beta-min.js',
            'requires': ['dom', 'event']
        },

        'event': {
            'type': 'js',
            'path': 'event/event-min.js',
            'requires': ['yahoo']
        },

        'fonts': {
            'type': 'css',
            'path': 'fonts/fonts-min.css'
        },

        'get': {
            'type': 'js',
            'path': 'get/get-min.js',
            'requires': ['yahoo']
        },

        'grids': {
            'type': 'css',
            'path': 'grids/grids-min.css',
            'requires': ['fonts'],
            'optional': ['reset']
        },

        'history': {
            'type': 'js',
            'path': 'history/history-min.js',
            'requires': ['event']
        },

         'imagecropper': {
             'type': 'js',
             'path': 'imagecropper/imagecropper-beta-min.js',
             'requires': ['dom', 'event', 'dragdrop', 'element', 'resize'],
             'skinnable': true
         },

         'imageloader': {
            'type': 'js',
            'path': 'imageloader/imageloader-min.js',
            'requires': ['event', 'dom']
         },

         'json': {
            'type': 'js',
            'path': 'json/json-min.js',
            'requires': ['yahoo']
         },

         'layout': {
             'type': 'js',
             'path': 'layout/layout-beta-min.js',
             'requires': ['dom', 'event', 'element'],
             'optional': ['animation', 'dragdrop', 'resize', 'selector'],
             'skinnable': true
         }, 

        'logger': {
            'type': 'js',
            'path': 'logger/logger-min.js',
            'requires': ['event', 'dom'],
            'optional': ['dragdrop'],
            'skinnable': true
        },

        'menu': {
            'type': 'js',
            'path': 'menu/menu-min.js',
            'requires': ['containercore'],
            'skinnable': true
        },

        'profiler': {
            'type': 'js',
            'path': 'profiler/profiler-beta-min.js',
            'requires': ['yahoo']
        },


        'profilerviewer': {
            'type': 'js',
            'path': 'profilerviewer/profilerviewer-beta-min.js',
            'requires': ['profiler', 'yuiloader', 'element'],
            'skinnable': true
        },

        'reset': {
            'type': 'css',
            'path': 'reset/reset-min.css'
        },

        'reset-fonts-grids': {
            'type': 'css',
            'path': 'reset-fonts-grids/reset-fonts-grids.css',
            'supersedes': ['reset', 'fonts', 'grids', 'reset-fonts'],
            'rollup': 4
        },

        'reset-fonts': {
            'type': 'css',
            'path': 'reset-fonts/reset-fonts.css',
            'supersedes': ['reset', 'fonts'],
            'rollup': 2
        },

         'resize': {
             'type': 'js',
             'path': 'resize/resize-beta-min.js',
             'requires': ['dom', 'event', 'dragdrop', 'element'],
             'optional': ['animation'],
             'skinnable': true
         },

        'selector': {
            'type': 'js',
            'path': 'selector/selector-beta-min.js',
            'requires': ['yahoo', 'dom']
        },

        'simpleeditor': {
            'type': 'js',
            'path': 'editor/simpleeditor-beta-min.js',
            'requires': ['element'],
            'optional': ['containercore', 'menu', 'button', 'animation', 'dragdrop'],
            'skinnable': true,
            'pkg': 'editor'
        },

        'slider': {
            'type': 'js',
            'path': 'slider/slider-min.js',
            'requires': ['dragdrop'],
            'optional': ['animation']
        },

        'tabview': {
            'type': 'js',
            'path': 'tabview/tabview-min.js',
            'requires': ['element'],
            'optional': ['connection'],
            'skinnable': true
        },

        'treeview': {
            'type': 'js',
            'path': 'treeview/treeview-min.js',
            'requires': ['event'],
            'skinnable': true
        },

        'uploader': {
            'type': 'js',
            'path': 'uploader/uploader-experimental.js',
            'requires': ['yahoo']
        },

        'utilities': {
            'type': 'js',
            'path': 'utilities/utilities.js',
            'supersedes': ['yahoo', 'event', 'dragdrop', 'animation', 'dom', 'connection', 'element', 'yahoo-dom-event', 'get', 'yuiloader', 'yuiloader-dom-event'],
            'rollup': 8
        },

        'yahoo': {
            'type': 'js',
            'path': 'yahoo/yahoo-min.js'
        },

        'yahoo-dom-event': {
            'type': 'js',
            'path': 'yahoo-dom-event/yahoo-dom-event.js',
            'supersedes': ['yahoo', 'event', 'dom'],
            'rollup': 3
        },

        'yuiloader': {
            'type': 'js',
            'path': 'yuiloader/yuiloader-beta-min.js',
            'supersedes': ['yahoo', 'get']
        },

        'yuiloader-dom-event': {
            'type': 'js',
            'path': 'yuiloader-dom-event/yuiloader-dom-event.js',
            'supersedes': ['yahoo', 'dom', 'event', 'get', 'yuiloader', 'yahoo-dom-event'],
            'rollup': 5
        },

        'yuitest': {
            'type': 'js',
            'path': 'yuitest/yuitest-min.js',
            'requires': ['logger'],
            'skinnable': true
        }
}
_END_

    my %catalog_source;
    for my $item (keys %$catalog) {
        $catalog_source{$item} = [ @{ $catalog->{$item}->{requires} || [] } ];
    }

    sub catalog {
        return \%catalog;
    }

    sub catalog_source {
        return \%catalog_source;
    }
}

1;