############################################################################
##      Copyright (C) 2006 Subredu Manuel  <diablo@iasi.roedu.net>.        #
##                                                                         #
## This program is free software; you can redistribute it and/or modify    #
## it under the terms of the GNU General Public License as published by    #
## the Free Software Foundation; either version 2 of the License, or       #
## (at your option) any later version.                                     #
##                                                                         #
## This program is distributed in the hope that it will be useful,         #
## but WITHOUT ANY WARRANTY; without even the implied warranty of          #
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           #
## GNU General Public License for more details.                            #
##                                                                         #
## You should have received a copy of the GNU General Public License       #
## along with this program; if not, write to the Free Software             #
## Foundation, Inc., 59 Temple Place - Suite 330, Boston,                  #
## MA 02111-1307,USA.                                                      #
############################################################################

package RoPkg::Rsync::Node;

use strict;
use warnings;

use vars qw($VERSION);

$VERSION = '0.1';

use Scalar::Util qw(blessed);
use English      qw( -no_match_vars );

use RoPkg::Exceptions;
use RoPkg::Rsync::Atom;

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

  $self = bless { %opt }, $class;

  if ( !$self->{'node_name'} ) {
    Param::Missing->throw('node_name not found in parameters list');
  }

  $self->{_plist}        = ();
  $self->{_longest}      = 0;
  
  $self->{_latest_comment_index} = 0;
  $self->{_latest_blank_index}   = 0;

  return $self;
}

#########################
## Add methods - BEGIN ##
#########################

sub _getIndex {
  my ($self, $type) = @_;

  if ( $type eq 'comment' ) {
    return ++$self->{_latest_comment_index};
  }
  elsif ( $type eq 'blank' ) {
    return ++$self->{_latest_blank_index};
  }
  else {
    return 0;
  }
}

sub _check_special_cases {
  my ($self, $atom) = @_;

  if ( $atom->{type} eq 'comment' ) {
    if ( ! $atom->{name} ) {
      $atom->{name} = 'c_' . $self->_getIndex('comment');
    }
  }
  elsif ( $atom->{type} eq 'blank' ) {
    if ( ! $atom->{name} ) {
      $atom->{name} = 'b_' . $self->_getIndex('blank');
    }
  }

  return 1;
}

#Add a new atom to the module
sub Add {
  my ($self, %adet) = @_;

  if ( !blessed($self) ) {
    OutsideClass->throw('Called outside class instance');
  }

  if ( !exists($adet{type}) ) {
    Param::Missing->throw('No type found for this atom');
  }

  $self->_check_special_cases(\%adet);

  if ( !  exists($adet{name}) ||
       ! defined($adet{name}) ||
       !  exists($adet{value}) ||
       ! defined($adet{value}) ) {
    Param::Wrong->throw('name or value is not defined');
  }

  my $at = new RoPkg::Rsync::Atom(%adet);
  if ( $self->Has($at) ) {
    return 0;
  }
  
  if ( $adet{type} eq 'param' ) {
    if ( $self->{_longest} < length($adet{name}) ) {
      $self->{_longest} = length($adet{name});
    }
  }

  push @{ $self->{_plist} }, $at;

  if ( $adet{type} eq 'comment' || $adet{type} eq 'blank' ) {
    return $at->Name();
  }
  else {
    return (scalar(@{ $self->{_plist} }));
  }
}

sub AddComment {
  my ($self, $pval) = @_;

  if ( !blessed($self) ) {
    OutsideClass->throw('Called outside class instance');
  }

  return $self->Add(
                  type  => 'comment',
                  value => $pval,
                );
}

sub AddParam {
  my ($self, $pname, $pval) = @_;

  if ( !blessed($self) ) {
    OutsideClass->throw('Called outside class instance');
  }

  return $self->Add(
                  type  => 'param',
                  name  => $pname,
                  value => $pval,
                );
}

sub AddBlank {
  my ($self, $bval) = @_;

  if ( !blessed($self) ) {
    OutsideClass->throw('Called outside class instance');
  }

  return $self->Add(
                  type  => 'blank',
                  value => $bval,
                );
}

#########################
##  Add methods -  END ##
#########################

#########################
## Get methods - BEGIN ##
#########################

sub GetAtom {
  my ($self, %adet) = @_;

  if ( !blessed($self) ) {
    OutsideClass->throw('Called outside class instance');
  }

  if ( !exists($adet{name}) ) {
    Param::Missing->throw('Name of the atom was not specified');
  }

  if ( !exists($adet{type}) ) {
    Param::Missing->throw('Type of the atom was not specified');
  }

  foreach(@{ $self->{_plist} }) {
    return $_ if ( $_->Name() eq $adet{name} && $_->Type eq $adet{type} );
  }

  Param::Unknown->throw('No atom with name <' . $adet{name} .'> was found');
  return 0;
}

sub GetParam {
  my ($self, $name) = @_;

  if ( !blessed($self) ) {
    OutsideClass->throw('Called outside class instance');
  }

  return $self->Get(type => 'param', name => $name);
}

sub GetComment {
  my ($self, $name) = @_;

  if ( !blessed($self) ) {
    OutsideClass->throw('Called outside class instance');
  }

  return $self->GetAtom(type => 'comment', name => $name);
}

sub GetBlank {
  my ($self, $name) = @_;

  if ( !blessed($self) ) { 
    OutsideClass->throw('Called outside class instance');
  }

  return $self->GetAtom(type => 'blank', name => $name);
}

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

  return ( wantarray ? @{ $self->{_plist}} : (scalar(@{ $self->{_plist}})) );
}

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

  if (!blessed($self) ) {
    OutsideClass->throw('Called outside class instance');
  }

  return scalar(@{ $self->{_plist} });
}

#########################
##  Get methods -  END ##
#########################

# test if a atom has already been added to the node.
# Returns 1 if atom is already added, 0 otherwise.
sub Has {
  my ($self, $atom) = @_;

  if ( !blessed($self) ) {
    OutsideClass->throw('Called outside class instance');
  }

  if ( ref($atom) ne 'RoPkg::Rsync::Atom' ) {
    Param::Wrong->throw('$atom is not a RoPkg::Rsync::Atom object');
  }

  foreach(@{ $self->{_plist} }) {
    return 1 if ($_ eq $atom);
  }

  return 0;
}

sub Name {
  my ($self, $pval) = @_;

  if ( ! blessed($self) ) {
    OutsideClass->throw('Called outside class instance');
  }

  if ( !defined($pval) ) {
    return $self->{node_name};
  }
  else {
    return ($self->{node_name} = $pval);
  }
}

sub Delete {
  my ($self, $aname) = @_;
  my ($index, $elements);

  if ( !blessed($self) ) {
    OutsideClass->throw('Called outside class instance');
  }

  $elements = @{ $self->{_plist}};
  $index = 0;

  foreach(@{ $self->{_plist} }) {
    if ( $_->Name eq $aname ) {
      delete $self->{_plist}->[$index];
      
      for(my $j=$index; $j<$elements; $j++) {
        $self->{_plist}->[$j] = $self->{_plist}->[$j+1];
      }
      delete $self->{_plist}->[$elements-1];
      return 1;
    }
    $index++;
  }

  return 0;
}

sub ToString {
  my ($self, $ind, $include_node_name) = @_;
  my $result = q{};
  my $indent;

  if ( !blessed($self) ) {
    OutsideClass->throw('Called outside class instance');
  }

  if ( !defined($include_node_name) ) {
    $include_node_name = 1;
  }

  if ( !$ind ) {
    $indent = "\t";
  }
  else {
    if ( $ind ) {
      $indent = "\t";
    }
    else {
      $indent = q{};
    }
  }

  if ( $include_node_name ) {
    $result = sprintf("[%s]$RS", $self->{node_name});
  }
  else {
    $result = q{};
  }

  foreach(@{ $self->{_plist} }) {
    my $ob = $_;

    if ( $ob->Type eq 'comment' ) {
      $result .= $ob->ToString(0) . $RS;
    }
    else {
      $result .= $ob->ToString($ind, $self->{_longest}) . $RS;
    }
  }

  return $result;
}

1;

__END__

=head1 NAME

RoPkg::Rsync::Node

=head1 DESCRIPTION

A node is a rsync module. A node is formed of zero or more atoms. 
 
 
B<Example>:
 
 [debian]
   path    = /var/ftp/pub/mirrors/debian.org/
#This comment will show on rsync -v
   comment = Debian Mirror
   list    = yes

[debian] is the node. The node name is B<debian>. The node has 4 atoms:

=over 4

=item *) param atom (path)

=item *) comment atom (the comment)

=item *) param atom (comment)

=item *) param atom (list)

=back

=head1 SYNOPSIS

 #!/usr/bin/perl
 
 use strict;
 use warnings;

 sub main {
   my $node = new RoPkg::Rsync::Node(node_name => 'debian');

   $node->AddParam('path', '/var/ftp/pub/mirrors/debian.org');
   $node->AddComment('#This comment will show on rsync -v');
   $node->AddParam('comment', 'Debian Mirror');
   $node->AddParam('list', 'yes');

   print $node->ToString();
 }

 main();

The result is:

 [debian]
        path    = /var/ftp/pub/mirrors/debian.org
 #This comment will show on rsync -v
        comment = Debian Mirror
        list    = yes


=head1 METHODS

All methods, throw the OutsideClass exception, if you use
them as class methods. 
Besides I<OutsideClass> the methods are throwing other exceptions
as well. Refer to each method documentation for more information.

=head2 new(%hash)

The constructor of the class. Expects a hash as parameter. At this
time, the only valid option is B<node_name> . Any other option
will be discarded. The B<node_name> is a required parameter. If is not
defined, a I<Param::Missing> exception will be raised.
 
B<Example>:
 
 my $node = new RoPkg::Rsync::Node(node_name => 'debian');

=head2 Add(%atom_details)

Add a new atom to the node. The atom details (type, name and value) are
keys from the hash. All three parameters must be defined. There are 2 special
cases: when the atom is a comment or a blank. In both cases,
RoPkg::Rsync::Node generates a name for them. If the atom is a comment
or a blank the returned value is the name of the atom. If the atom is
a parameter, the total number of atoms for this node is returned.
 
B<Example>:

 $node->Add(
    type  => 'param',
    name  => 'path',
    value => '/var/ftp/pub/mirrors/debian.org',
  );

The returned value is the total number of atoms for this node.

B<Example>:
 
 $node->Add(
    type  => 'comment',
    value => '#This comment will show on rsync -v',
  );

The returned value is the name of the atom (the name is autogenerated).
 
B<Exceptions>:
 
=over 2

=item *) Param::Missing - $atom_details{type} is not defined

=item *) Param::Wrong = $atom_details{name} or $atom_details{value} are not defined

=back

=head2 AddParam($pname, $pvalue)

A wrapper around Add provided for comodity. See B<Add> documentation for 
more details about the behaviour.

=head2 AddComment($comment_value)

A wrapper around Add provided for comodity. See B<Add> documentation for 
more details about the behaviour.

=head2 AddBlank($blank_value)

A wrapper around Add provided for comodity. See B<Add> documentation for 
more details about the behaviour.

=head2 GetAtom(%atom_details)

Returns a RoPkg::Rsync::Atom object based on parameters specified
in %atom_details. The %atom_details fields can be: I<type>, I<name> and 
I<value>. B<type> and B<value> B<must> be specified.
 
B<Exceptions>:
 
=over 2

=item *) Param::Missing - type/name were not specified

=item *) Param::Unknown - no objects were found

=back

Take note that only the first object who match the criterias
is returned.

=head2 GetParam($name)

A wrapper around GetAtom provided for comodity. See B<GetAtom>
documentation for more details about the behaviour.

=head2 GetComment($name)

A wrapper around GetAtom provided for comodity. See B<GetAtom>
documentation for more details about the behaviour.

=head2 GetBlank($name)

A wrapper around GetAtom provided for comodity. See B<GetAtom>
documentation for more details about the behaviour.

=head2 GetAll()

Returns a array with all the atoms of the node. In scalar context
returns the number of atoms.

=head2 GetAtomsNo()

Returns the number of atoms for this node.

=head2 Has($atom)

Returns 1 if the $atom object is already a atom for this node,
0 otherwise.

=head2 Delete($atom_name)

Removes the atom those name is I<$atom_name>. Returns 1 on success
(the atom was found and removed), 0 otherwise.

=head2 Name($node_name)

get/set method for node name. Using this method you can change the
node name or find it.

=head2 ToString($indent, $include_node_name)

Returns the string representation of the node. If I<$indent> is true,
the atoms are indented. If $include_node_name is true, the node name
will be included in the string.
 
B<Example>:

 $node = new RoPkg::Rsync::Node(node_name => 'debian');
 $node->AddParam('gid', 'users');

 print $node->ToString(0, 0),$/,
       $node->ToString(0, 1),$/,
       $node->ToString(1, 1),$/,
       $node->ToString(),$/;

The result is:
 gid = users
 [debian]
 gid = users;
 [debian]
        gid = users
 [debian]
        gid = users

=head1 PREREQUISITES

perl 5.008 (or later) is required. Besides perl, you must have the following:

=over

=item *) RoPkg::Exceptions

=item *) Scalar::Util

=item *) English

=back

=head1 SEE ALSO

L<RoPkg::Rsync::Atom> L<RoPkg::Rsync::ConfFile> L<RoPkg::Exceptions>

=head1 AUTHOR

Subredu Manuel <diablo@iasi.roedu.net>

=head1 LICENSE

Copyright (C) 2005 Subredu Manuel.  All Rights Reserved.
This module is free software; you can redistribute it 
and/or modify it under the same terms as Perl itself.
The LICENSE file contains the full text of the license.

=cut
