package Modules::File::TreeEntry;

my ($lastError,%errorCodes,$class,$lastErrorCode,$lastFullPath);



BEGIN {

	use Exporter;
  our @ISA = ('Exporter');
  our @EXPORT = qw( &lastError &lastErrorCode &lastFullPath);

	%errorCodes = ( 'delete' => 'Unable to delete ',
									'create' => 'Unable to create ',
									'chmod' => 'Unable to change mode ',
									'chown' =>  'Unable to change owner/group ',
									'undef' => 'full path is not defined',
									'notexists' => 'dir/file is not exists ',
									'stat' => 'Unable to get stat ');

	$class="Modules::File::TreeEntry";

}

use strict;


sub new {
	my $className = shift;
	my $this;
	my %args = ('name'=>undef,
							'mode'=>0555,
							'type'=>'d',
							'owner'=>undef,
							'group'=>undef,
							'uid'=>undef,
							'gid'=>undef,
							'base'=>undef,
							@_
						 );

	if ($args{'uid'} && $args{'uid'}!~/^\d+$/){
		$args{'uid'}=undef;
	}
	if ($args{'gid'} && $args{'gid'}!~/^\d+$/){
		$args{'gid'}=undef;
	}

	$this={'name'=>$args{'name'},
				 'mode'=>($args{'mode'}=~/^\d+$/)?$args{'mode'}:0555,
				 'type'=>$args{'type'},
				 'owner'=>$args{'owner'},
				 'group'=>$args{'group'},
				 'uid'=>$args{'uid'},
				 'gid'=>$args{'gid'},
				 'base'=>$args{'base'},
				 'content'=>{},
				};

	if ($args{'owner'} && $args{'owner'}=~/^\d+$/){
		$this->{'uid'}=$args{'owner'};
	}
	if ($args{'group'} && $args{'group'}=~/^\d+$/){
		$this->{'gid'}=$args{'group'};
	}
	
	$this->{'parent'}=undef;
	$this->{'content'}={};
	$this->{'restore_users_permissions'} = exists($args{'restore_users_permissions'})?
		$args{'restore_users_permissions'}:0;

	bless ($this,$className);
  return $this;
}

sub Mode {
	my $this = shift;
	if(@_){
		my $mode = shift;
		$this->{'mode'}= $mode if $mode =~ /^\d+$/;
	}
	return $this->{'mode'};
}

sub Base {
	my $this = shift;
	if(@_){
		$this->{'base'}=shift;
		if(defined($this->{'base'})){
			my $newBase = $this->{'base'}.'/'.$this->{'name'};
			foreach  my  $ptrEntry (values %{$this->{'content'}}){
				$ptrEntry->Base($newBase);
			}
		}
	}
	return $this->{'base'};
}

sub Name {
	my $this = shift;
	if(@_){
		my $name = shift;
		$this->{'name'} = $name;
		if ( defined $this->{'base'} ) {
			my($newBase,$ptrEntry);
			$newBase = $this->{'base'}.'/'.$this->{'name'};
			foreach $ptrEntry ( values %{$this->{'content'}} ) {
				if ( ref($ptrEntry) =~ /TreeEntry/ ) {
					$ptrEntry->Base($newBase) if $newBase;
				}
			}
		}
	}
	return $this->{'name'};
}

sub Owner {
	my $this = shift;
	if(@_){
		$this->{'owner'}=shift;
		if($this->{'owner'}=~/^\d+$/){
			$this->{'uid'}=$this->{'owner'};
		}
	}
	return $this->{'owner'};
}

sub Group {
	my $this = shift;
	if(@_){
		$this->{'group'}=shift;
		if($this->{'group'}=~/^\d+$/){
			$this->{'gid'}=$this->{'group'};
		}
	}
	return $this->{'group'};
}

sub Uid {
	my $this = shift;
	if(@_){
		my $uid = shift;
		if($uid=~/^\d+$/){
			$this->{'uid'}=$uid;
		}
	}
	return $this->{'uid'};
}

sub Gid {
	my $this = shift;
	if(@_){
		my $gid = shift;
		if($gid=~/^\d+$/){
			$this->{'gid'}=$gid;
		}
	}
	return $this->{'gid'};
}

sub Type {
	my $this = shift;
	if(@_){
		my $type = shift;
		if($type=~/f/i){
			$this->{'type'}='f';
		}elsif($type=~/d/i){
			$this->{'type'}='d';
		}
	}
	return $this->{'type'};
}

sub Parent {
	my $this = shift;
	if(@_){
		my $parent = shift;
		if(ref($parent)=~/TreeEntry/){
			$this->{'parent'}=$parent;
		}
	}
	return $this->{'parent'};
}

sub Content {
	my $this = shift;
	if(@_){
		my $ptrArray = shift;
		unless(ref($ptrArray)=~/ARRAY/){
			unshift @_,$ptrArray;
			$ptrArray=\@_;
		}
		$this->{'content'}={};
		my ($name,$ptrEntry);
		if (defined $this->{'base'}){
			my $newBase = $this->{'base'}.'/'.$this->{'name'};
			foreach $ptrEntry (@{$ptrArray}){
				if (ref($ptrEntry)=~/TreeEntry/){
					$name = $ptrEntry->Name();
					$this->{'content'}->{$name} = $ptrEntry;
					$ptrEntry->Base($newBase) if $newBase;
				}
			}
		}
	}
	return $this->{'content'};
}

sub hasContent {
	my $this = shift;
	return scalar(keys(%{$this->{'content'}}));
}

sub addToContent {
	my $this = shift;
	my $newEntry = Modules::File::TreeEntry->new(@_);
	my $name = $newEntry->Name();
	if ($name){
		$this->{'content'}->{$name} = $newEntry;
		$newEntry->Parent($this);
		return $newEntry;
	}
	return undef;
}

sub mapOwner {
	my $this = shift;
	my ($uidKey,$uidValue,$gidKey,$gidValue) = @_;

	if ( defined($uidValue) && ( $uidValue =~ /^\d+$/ ) ) {
		if ( $this->{'owner'} eq $uidKey ) {
			$this->{'uid'} = $uidValue;
		}
	}
	if ( defined($gidValue) && ( $gidValue=~/^\d+$/ ) ) {
		if ($gidKey && $this->{'group'} && ($this->{'group'} eq $gidKey)){
			$this->{'gid'} = $gidValue;
		}
	}
	foreach my $ptrEntry ( values %{$this->{'content'}} ) {
		$ptrEntry->mapOwner( $uidKey, $uidValue, $gidKey, $gidValue );
	}
}

sub mapGroup {
	my $this = shift;
	my ($gidKey,$gidValue) = @_;

	if(defined($gidValue) && $gidValue=~/^\d+$/){
		if ($this->{'group'} eq $gidKey){
			$this->{'gid'}=$gidValue;
		}
	}
	foreach my $ptrEntry (values %{$this->{'content'}}){
		$ptrEntry->mapOwner($gidKey,$gidValue);
	}
}

sub FullPath {
	my $this = shift;
	if (defined($this->{'base'})){
		return $this->{'base'}.'/'.$this->{'name'};
	}else{
		return undef;
	}
}

sub create {
	my $this = shift;
	my $notStrong = shift;

	my $changePermissions = 1;

	$lastErrorCode = 'undef';

	unless(defined $this->{'base'}){
		return undef;
	}
	unless (-d $this->{'base'}){
		return undef;
	}
	unless ($this->{'mode'}=~/^\d+$/){
		return undef;
	}
	my $fullPath = $this->{'base'}.'/'.$this->{'name'};
	$lastFullPath = $fullPath;
#
# delete if type is wrong
#
	$lastErrorCode = 'delete';

	if(-l $fullPath){
		unless(unlink($fullPath)) {
			$lastError = $!;
			return undef;
		}
	}

	if ( -e $fullPath ) {
		if($this->{'type'} eq 'd'){
			unless (-d $fullPath){
				unless(unlink($fullPath)){
					$lastError = $!;
					return undef;
				}
			}
		}else{
			if(-d $fullPath){
				unless(system("rm -rf $fullPath")==0) {
					$lastError = $!;
					return undef;
				}
			}elsif(! -f $fullPath){
				unless(unlink($fullPath)){
					$lastError = $!;
					return undef;
				}
			}
		}
	}
#
# end delete if type is wrong
#

#
# create
#
	$lastErrorCode = 'create';
	my $exists = 1;
	if ( -e $fullPath ) {
		unless( $this->{'restore_users_permissions'} ) {
			if ($this->{'owner'} =~ /u/i) {
				$changePermissions = 0; 
			}
		}
	} else {
	
		$exists = 0;
	
		if ( $this->{'type'} eq 'd' ) {
			unless(mkdir($fullPath,$this->{'mode'})){
				$lastError = $!;
				return undef;
			}
			
		} elsif ( $this->{'type'} eq 'f' ) {

			unless ( $notStrong || ( $this->{'mode'} & 0111 ) ||
							 ( $this->{'name'} =~ /\.(tgz|tar\.gz)$/ )
						 ){  ## not executable & not an archive & strong
			
				if(open (TOUCH,">$fullPath")){
					close (TOUCH);
				}else{
					$lastError = $!;
					return undef;
				}
			}
		}
	}

#
# end create
#
	if ( $changePermissions && ( -e $fullPath ) ) {
		my ($mode,$uid,$gid);

		if ( $notStrong && $exists && $this->{'uid'} ) {
			unless ( ($mode,$uid,$gid) = ( stat($fullPath) )[2,4,5] ) {
				$lastError = $!;
				$lastErrorCode = 'stat';
				return undef;
			}
			$mode &= 07777;
			$uid = $this->{'uid'};

		} else {
			$mode = $this->{'mode'};
			$uid = $this->{'uid'};
			$gid = $this->{'gid'};
		}
#
# chmod
#
		unless ( chmod ( $mode,$fullPath ) ) {
			$lastError = $!;
			$lastErrorCode = 'chmod';
			return undef;
		}

#
# chown
#
		unless ( chown ( $uid,$gid,$fullPath ) ) {
			$lastError = $!;
			$lastErrorCode = 'chown';
			return undef;
		}
	}
	
	my($ptrEntry,$ret);
	foreach $ptrEntry (values %{$this->{'content'}}){
		$ret = $ptrEntry->create($notStrong);
		unless(defined($ret)){
			return undef;
		}
	}
	
	$lastError = undef;
	$lastErrorCode = undef;

	return $fullPath;
}

sub check {
	my $this = shift;
	my $ret=1;
	$lastErrorCode = 'undef';

	unless(defined $this->{'base'}){
		return undef;
	}
	unless (-d $this->{'base'}){
		return undef;
	}
	unless ($this->{'mode'}=~/^\d+$/){
		return undef;
	}
	my $fullPath = $this->{'base'}.'/'.$this->{'name'};
	$lastFullPath = $fullPath;

	if(-e $fullPath){
		if ($this->{'uid'}=~/^\d+$/ && $this->{'gid'}=~/^\d+$/){
			my($mode,$uid,$gid);
			unless(($mode,$uid,$gid)=(stat($fullPath))[2,4,5]){
				$lastError = $!;
				$lastErrorCode = 'stat';
				return undef;
			}
			$mode &= 07777;
			if(($mode == $this->{'mode'}) && 
				 ($uid == $this->{'uid'}) && 
				 ($gid == $this->{'uid'})){
				$ret = 1;

				foreach my $ptrEntry (values %{$this->{'content'}}){
					$ret &&= $ptrEntry->check();
					last unless($ret);
				}
				
			}else{
				$ret = 0;
			}
		}else{
			$ret = undef;
		}
	}else{
		$lastErrorCode = 'notexists';
		$ret = 0;
	}
	return $ret;
}

sub Ownership {
	my $this = shift;
	my $searchPath = shift;

	return undef unless $this->{'base'};

	my($uid,$gid,$mode,$suffix);

	my $fullPath = $this->{'base'}.'/'.$this->{'name'};
	if ( $searchPath =~ /^\Q$fullPath\E(.*)/ ) {

		$suffix = $1;

		if ( $suffix ) { ## search mode

			$suffix =~ s/^\///; ## remove first '/' if exists

			my($prefix) = split(/\//,$suffix);  ## get first name
			my $ptrEntry = $this->{'content'}->{$prefix}; ## search in content given name
			if ( $ptrEntry ) {  ## found

				($uid,$gid,$mode) = $ptrEntry->Ownership( $searchPath, @_ );  ## recurent call

				if ( wantarray() ) {
					return ( $uid,$gid,$mode );
				}else{
					return $uid;
				}

			} else {
				return undef; ## not found
			}

		} else { ## ownership mode
			if ( @_ ) {
				($uid,$gid) = @_;
				if( defined($uid) && $uid =~ /^\d+$/ ) {
					$this->{'uid'} = $uid;
				}
				if ( defined($gid) && $gid =~ /^\d+$/ ) {
					$this->{'gid'} = $gid;
				}
			}
			if ( wantarray() ) {
				return ($this->{'uid'},$this->{'gid'},$this->{'mode'});
			}else{
				return $this->{'uid'};
			}
		}
	}
	return undef;
}

sub lastError {
	return $lastError; 
}

sub lastErrorCode {
	if(wantarray()){
		return ($lastErrorCode,$errorCodes{$lastErrorCode});
	}else{
		return $lastErrorCode;
	}
}

sub lastFullPath {
	return $lastFullPath;
}

sub toString {
	my $this = shift;
	my $prefix = shift || '';

  my $ret = $prefix."name:\t".$this->{'name'}."\n";
	$ret .= $prefix."mode: ".sprintf('%04o',$this->{'mode'});
	$ret .= ",\towner: ".$this->{'owner'}."($this->{'uid'}),\tgroup: ".
		$this->{'group'}."($this->{'gid'})\ttype: $this->{'type'}\n";
	$ret .= $prefix."base: ".$this->{'base'}."\n\n";

	foreach my $ptrEntry (values %{$this->{'content'}}){
		$ret .= $ptrEntry->toString($prefix.'  ');
	}

	return $ret;
}

sub RestoreUsersPermissions {
	my $this = shift;
	if ( @_ ) {
	  my ($ptrEntry);
		$this->{'restore_users_permissions'} = shift;
		foreach $ptrEntry (values %{$this->{'content'}} ) {
		  $ptrEntry->RestoreUsersPermissions($this->{'restore_users_permissions'});
		}
	}
		
	return $this->{'restore_users_permissions'};
}


1;
