package CatalystX::NavigationMenuItem;

use strict;
use warnings;

use Moose;
use namespace::autoclean;

=head1 NAME

CatalystX::NavigationMenuItem

=head1 SYNOPSIS

  my $mi = CatalystX::NavigationMenuItem->new(
  	...
  );

  my $entry = $mi->nav_entry($c);
  my $link = $mi->get_link($c);

=head1 DESCRIPTION

CatalystX::NavigationMenuItem represents a menu item in a L(CatalystX::NavigationMenu).
This object does all the work of determining if the menu item can be displayed, what
link to use and if there are sub menus.

=cut

has label => (
	is => 'rw',
	isa => 'Str',
);

has title => (
	is => 'rw',
	isa => 'Str',
);

has action => (
	is => 'ro',
	isa => 'Catalyst::Action',
	predicate => 'has_action',
);

has path => (
	is => 'ro',
	isa => 'Str',
);

has parent => (
	is => 'ro',
	writer => '_set_parent',
	isa => 'Str',
);

has action_args => (
	traits => ['Array'],
	is => 'rw',
	isa => 'ArrayRef[Str]',
	default => sub{ [] },
	handles => {
		count_args => 'count',
		all_args => 'elements',
	},
);

has conditions => (
	traits => ['Array'],
	is => 'rw',
	isa => 'ArrayRef[Str]',
	default => sub{ [] },
	handles => {
		condition_count => 'count',
		all_conditions => 'elements',
	},
);

has order => (
	is => 'ro',
	isa => 'Int',
	default => 0
);

has required_roles => (
	traits => ['Array'],
	is => 'ro',
	isa => 'ArrayRef[Str]',
	default => sub{ [] },
	handles => {
		count_roles => 'count',
		all_roles => 'elements',
	},
);

has children => (
	is => 'rw',
	isa => 'CatalystX::NavigationMenu',
	predicate => 'has_children'
);

=head1 METHODS

=head2 contains_path($path)

Returns true if this menu item or any of its children contain the given
path.

=cut

sub contains_path {
	my ($self, $path) = @_;

	# If this is the path, we obviously contain the path.
	return 1 if ($self->path eq $path);

	# Now check all the children:
	if ($self->has_children) {
		# For each child element see if it contains the path.
		foreach my $i ($self->children->all_items) {
			return 1 if ($i->contains_path($path));
		}
	}

	return 0;
}

=head2 nav_entry($c, $with_subs)

Returns a hash reference that contains the navigation entry for this item. If
there is no link associated with item it will return an undef value. If $with_subs 
is true then the sub navigation to this item is also include in the hash with a key 
of: subnav. The other keys are:

=over 4

=item label

The label to use for the actual link. The text to show between a tags.

=item title

An optional title of the link. To be used as the title attribute of the a tag.

=item link

The actual link. This contains the actual URI string, not an object.

=item active

True if the given entry is currently active (part of the path to the currently
viewed page).

=back

=cut

sub nav_entry {
	my ($self, $c, $with_subs) = @_;

	# Check to see if we can display this item or not.
	if ($self->count_roles > 0 && $c->can('check_user_roles')) {
		# Check each roles.
		foreach my $role ($self->all_roles) {
			if ($role =~ /\|/) {
				my @roles = split(/\|/, $role);
				return undef if (!$c->check_any_user_role(@roles));
			}
			else {
				return undef if (!$c->check_user_roles($role));
			}
		}
	}

	# We can show this entry.
	my $link = $self->get_link($c);

	# Don't show this item if there is no link possible.
	return undef if (!$link);

	my $entry = {
		label => $self->label,
		title => $self->title,
		link => $link, 
		active => $self->contains_path($c->action->namespace . '/' . $c->action->name),
	};

	if ($with_subs && $self->has_children) {
		my $sub_nav = $self->children->get_navigation($c);
		if (scalar(@$sub_nav) > 0) {
			$entry->{subnav} = $sub_nav;
		}
	}

	return $entry;
}

=head2 add_item($item)

Add the given item to the sub tree for this item. If no subtree exists create one.

=cut

sub add_item {
	my ($self, $item) = @_;

	if (!$self->has_children) {
		$self->children(CatalystX::NavigationMenu->new());
	}
	if ($item->parent eq $self->path) {
		$self->children->insert_item($item);
	}
	else {
		$self->children->add_item($item);
	}
}

=head2 get_link($c)

Using the given Catalyst object determine if this link should be displayed or 
not. It will use the Catalyst object to fill in any missing values it needs 
to complete an action link, it will also use the object to determine any 
conditions that require it.

=cut

sub get_link {
	my ($self, $c) = @_;

	# Check the conditions if we have any.
	if ($self->condition_count > 0) {
		# Test each conditions
		foreach my $cond ($self->all_conditions) {
			return undef if (!eval($cond));
		}
	}

	my $link;
	if ($self->has_action) {
		my @capture_args;
		my @url_args;
		foreach my $arg ($self->all_args) {
			# Select which array we are to be added to.
			my $array = \@capture_args;
			if ($arg =~ /^\@/) {
				$arg =~ s/^\@//;
				$array = \@url_args;
			}

			# Now process the arg.
			if ($arg =~ /^\$/) {
				$arg =~ s/^\$//;
				return undef if (!exists($c->stash->{$arg}));
				push(@$array, $c->stash->{$arg});
			}
			else {
				push(@$array, $arg);
			}	
		}
		my @args = ($self->action);
		push(@args, \@capture_args) if (scalar(@capture_args) > 0);
		push(@args, @url_args);
		$link = $c->uri_for(@args);
		if ($link) {
			$link = $link->as_string;
		}
	}
	elsif ($self->has_children) {
		# We don't have a link but maybe one of our children does.
		foreach my $child ($self->children->sorted_items) {
			my $entry = $child->nav_entry($c, 0);
			if ($entry) {
				$link = $entry->{link};
				last if ($link);
			}
		}
	}

	return $link;
}

__PACKAGE__->meta->make_immutable;

1;

__END__

=head1 DEPENDENCIES

L<Catalyst>

=head1 SEE ALSO 

L<CatalystX::NavigationMenu>

=head1 AUTHORS

Derek Wueppelmann <derek@roaringpenguin.com>

=head1 LICENSE AND COPYRIGHT

Copyright (c) 2011 Roaring Penguin Software, Inc.

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

=cut