package CloudStore::API;
use warnings;
use strict;
use autodie;

use lib 'lib';
use base qw(CloudStore::Gearman CloudStore::MD5sum);

use File::Path qw(make_path remove_tree);
use File::Find;
use Data::Dump qw(dump);
use Carp qw(confess cluck);

sub new {
	my ($class,$slice) = @_;

	cluck "DEPRICIATED $slice specified" if $slice;

	my $self = {
		passwd => '/var/lib/extrausers/passwd',
	};
	bless $self, $class;

	$self->{md5} = $self->user_info("md5") || die "can't find user md5";
=for removed
	$self->{md5}->{dir} = "$dir/md5";
	if ( ! -e $self->{md5}->{dir} ) {
		make_path $self->{md5}->{dir}, { uid => $self->{md5}->{uid}, gid => $self->{md5}->{gid} };
		warn "## CREATED md5pool $self->{md5}->{dir}\n";
	}
=cut

	return $self;
}

sub slice_dir_port {
	my ($self,$slice) = @_;
	my ( undef, $dir, $port, undef ) = getgrnam($slice);
	warn "# slice_dir_port $slice = $dir $port\n";
	return ( $dir, $port );
}

sub dir2gearman {
	my $self = shift;
	my $dir  = shift;
	$dir =~ s/\W+/_/g;
	$dir =~ s/^_+//;
	$dir =~ s{_\d+$}{};
	return join('_', $dir, @_);
}

sub user_info {
	my ($self,$login) = @_;

	confess "need login" unless $login;

	my @n = qw/ login passwd uid gid quota comment gecos dir shell expire /;
	my @p = $login =~ m/^\d+$/ ? getpwuid $login : getpwnam $login;
	die "user_info $login: $@" if $@;
	my $user;
	$user->{$_} = shift @p foreach @n;
	return $user;
}

sub create_user {
	my ( $self, $new_email, $new_passwd, $new_quota ) = @_;

	my $max_uid = 0;
	my $found = 0;

	open(my $fh, '<', $self->{passwd});
	while(<$fh>) {
		my ( $login, $passwd, $uid, $gid, $email, $dir, $shell ) = split(/:/,$_);
		$max_uid = $uid if $uid > $max_uid;
		$found = $uid if $email eq $new_email;
	}
	close($fh);

	my $slice = $ENV{SLICE} || 's1';
	my ( $dir, $port ) = $self->slice_dir_port( $slice );

	if ( ! $found ) {
		$max_uid++;
		$dir .= "/$max_uid";
		warn "# create_user $slice $new_email $new_quota = $max_uid $dir";
		open(my $fh, '>>', $self->{passwd});
		print $fh "u$max_uid:$new_passwd:$max_uid:$port:$new_email:$dir:/bin/true\n";
		close($fh);
		$found = $max_uid;

		mkdir $dir;
		chown $max_uid, $port, $dir;

		my $path = "$dir/.meta/secrets";
		$self->mkbasepath($path);
		open($fh, '>', $path);
		print $fh "u$max_uid:$new_passwd\n";
		close $fh;
	}

	# FIXME update quota only on create?
	$self->gearman_do( $self->dir2gearman( $dir, 'quota', 'set' ) => "$found $new_quota" );

	return $found;
}

sub mkbasepath {
	my ($self,$path,$opts) = @_;
	$path =~ s{/[^/]+$}{};
	make_path $path unless -d $path;
}

sub user_dir {
	my ( $self,$user, $dir ) = @_;
	$user = $self->user_info($user) unless ref $user eq 'HASH';
	my $path;
	if ( exists $user->{dir} ) {
		$path = $user->{dir} . '/.meta/' . $dir;
	} else {
		die "no dir in ", dump $user;
	}
	$path =~ s{//+}{/}g;

	if ( ! -e $path ) {
		$self->mkbasepath( $path, { uid => $user->{uid} } );
		open(my $fh, '>', $path);
		close $fh;
		chown $user->{uid}, $user->{gid}, $path;
		warn "# user_dir created $path\n";
	}

	#warn "### user_dir $path";
	return $path;
}

sub append {
	my $self = shift @_;
	$self->append_meta( 'usage', @_ );
}

sub append_meta {
	my $self = shift @_;
	my $log  = shift @_;
	my $user = shift @_;
	my $path = $self->user_dir( $user => $log );
	my $delimiter = '#';
	   $delimiter = '  ' if $log =~ m/md5sum$/;
	my $line = join($delimiter,@_);
	open(my $fh, '>>', $path);
	print $fh "$line\n";
	close $fh;
	warn "## $path $line\n";
}

sub usage {
	my ( $self, $user ) = @_;
	$user = $self->user_info($user) unless ref $user eq 'HASH';
	my $path = $self->user_dir( $user => 'usage');
	my $sum;
	open(my $fh, '<', $path);
	while(<$fh>) {
		chomp;
		my @v = split(/#/,$_);
		$sum->{ $v[0] } += $v[1];
		$sum->{_usage}  += $v[1];
	}
	my ( $usage, $quota ) = split(/ /,
		$self->gearman_do( $self->dir2gearman( $user->{dir}, 'quota', 'get' ) => $user->{uid} )
	);
	$sum->{_usage} += $usage;
	$sum->{_quota} = $quota;
	warn "## usage ",dump($user, $sum), $/;
	return $sum;
}

sub send_file {
	my ( $self, $f_uid,$f_path, $t_uid,$t_path ) = @_;

	my $f = $self->user_info($f_uid);
	die "FIXME not on current slice" if $f->{dir} !~ m/^$ENV{SLICE}/;
	my $t = $self->user_info($t_uid);

	my $f_full = "$f->{dir}/$f_path";
	my $t_full = "$t->{dir}/$t_path";

	$self->mkbasepath( $t_full, { uid => $t->{uid} } );

	my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat $f_full;
	if ( $uid == $f->{uid} ) {
		warn "# send_file - move $f_uid $f_path to pool\n";
		chown $self->{md5}->{uid}, $self->{md5}->{gid}, $f_full;
		chmod oct("0444"), $f_full;
		$self->append( $f, 'sent', -s $f_full, $t->{uid}, $f_path );
	} elsif ( $uid == $self->{md5}->{uid} ) {
		warn "# send_file - shared $f_full\n";
	}

	$self->delete( $t, $t_path ) if -e $t_full;

	my $size = -s $f_full;
	my $md5;

	my $ok;
	{
		no autodie qw(link);
		$ok = link $f_full, $t_full
	};
	if ( ! $ok || $! =~ m/cross-link/ ) {
		$ok = symlink $f_full, $t_full;
	} else {
		$size = -s $t_full;

		if ( $f->{uid} == $self->{md5}->{uid} ) {
			$md5 = $f_path; # we don't have local md5sum db for md5 user!
		} else {
			$md5 = $self->md5_get($f_full);
		}

	}

	if ( $ok ) {
		$self->append( $t, 'recv', $size, $f->{uid}, $t_path );
		$self->append_meta('md5sum', $t, $md5 => $t_path ) if $md5; # md5sum for received files! FIXME -- cross-slice md5
	}

	return $size;
}

sub rename_file {
	my ( $self, $user, $from, $to ) = @_;
	$user = $self->user_info($user) unless ref $user eq 'HASH';

	my $f_full = "$user->{dir}/$from";
	my $t_full = "$user->{dir}/$to";

	$self->mkbasepath( $t_full, { uid => $user->{uid}, gid => $user->{gid} } );
	my $ok = rename $f_full, $t_full;

	my $md5 = $self->md5_get($t_full);
	if ( ! $md5 ) {
		warn "ERROR: no md5sum for $from";
		return $ok; # XXX our internal error
	}

	$self->append_meta('md5sum', $user, 'rename' => $from );
	$self->append_meta('md5sum', $user, $md5 => $from );

	return $ok;
}


sub delete {
	my ( $self, $user, $path ) = @_;
	$user = $self->user_info($user) unless ref $user eq 'HASH';

	my $deleted_size = 0;
	my $full_path = "$user->{dir}/$path";

	if ( -d $full_path ) {

		find({ 
		no_chdir => 1,
		wanted => sub {
			return if -d $_;
			my ($uid,$size) = (stat($_))[4,7];
			warn "## find $uid $size $_\n";
			if ( $uid == $self->{md5}->{uid} ) {
				$deleted_size += $size;
			}
		}}, $full_path);

		remove_tree $full_path;
	} else {
		$deleted_size += -s $full_path;
		unlink $full_path;
	}

	warn "delete $deleted_size bytes shared\n";

	$self->append( $user, 'delete', -$deleted_size, $user->{uid}, $path );
	$self->append_meta('md5sum', $user, 'delete', $path );

	return $full_path;
}

sub file_size {
	my ( $self, $user, $path ) = @_;
	$user = $self->user_info($user) unless ref $user eq 'HASH';

	my $full_path = "$user->{dir}/$path";
	return -s $full_path;
}

1;
