package MongoDBx::Tiny::Validator;
use strict;
use warnings;

=head1 NAME

MongoDBx::Tiny::Validator - validation on insert and update.

=cut

use MongoDBx::Tiny::Util;
use Params::Validate qw(:all);
use Carp qw(confess);
use Data::Dumper;

=head1 SUBROUTINES/METHODS

=head2 new

  $validator = MongoDBx::Tiny::Validator->new(
  	$collection_name,
  	$document,
  	$tiny,
  );

=cut

sub new {
    my $class  = shift;
    my $c_name   = shift || confess q/no collection name/;
    my $document = shift || confess q/no document/;
    my $tiny     = shift || confess q/no tiny/;
    
    return bless {
	document         => $document,
	collection_name  => $c_name,
	tiny             => $tiny,
	errors           => [],
    }, $class;
}

=head2 document, collection_name, tiny

  # alias
  $document = $validator->document;
  $collection_name = $validator->collection_name;
  $tiny = $validator->tiny;

=cut

sub document         { shift->{document}        }

sub collection_name  { shift->{collection_name} }

sub tiny             { shift->{tiny} }

=head2 has_error

  $validator->has_error && die;

=cut

sub has_error        { @{shift->{errors}} }

=head2 set_error

  $validator->set_error(
      $name => [
  	'error-code','message',
      ]
  );

=cut

sub set_error {
    my $self  = shift;
    validate_pos(
	@_,
	1,
	{ type => ARRAYREF }
    );
    my $field   = shift;
    my $error = shift;
    my ($code,$message) = @{$error};

    my %error = (
	collection => $self->collection_name,
	field      => $field,
	code       => $code    || 'nocode',
	message    => $message || (sprintf "fail: %s",$code)
    );
    push @{$self->{errors}},\%error;
}

=head2 errors

  # erros: [{ field => 'field1', code => 'errorcode', message => 'message1' },,,]
  @erros         = $validator->erros; 
  
  @fields        = $validator->errors('field');
  @error_code    = $validator->errors('code');
  @error_message = $validator->errors('message');

=cut

sub errors {
    my $self = shift;
    my $field  = shift; # list field(field,code,message)
    if ($field) {
	return map { $_->{$field} } @{$self->{errors}};
    }
    return wantarray ? @{$self->{errors}} : $self->{errors};
}

=head2 check

  # no_validate: bool
  # state: [insert,update]
  $validator->check($opt); 

=cut

sub check {
    my $self       = shift;
    my $opt        = shift;

    return $self if $opt->{no_validate};

    my $c_class    = util_document_class($self->collection_name,  ref $self->tiny || $self->tiny );
    my $document   = $self->document;
    my $field      = $c_class->field;

    my $all_fields  = { map { $_ => 1 } $field->list };
    my @fail_fields = grep { ! $all_fields->{$_} } keys $document;
    for (@fail_fields) {
        $self->set_error(
            $_ => ['not_field', (sprintf "%s is not field",$_)]
        );
    }

    if ($opt->{state} eq 'insert') {

	for my $name ($field->list('REQUIRED')) {
	    unless (exists $document->{$name}) {
		$self->set_error(
		    $name => [
			'required',(sprintf "%s is required",$name)
		    ]
		);
	    }
	}

	for my $name ($field->list) {
	    unless (exists $document->{$name}) {
		$document->{$name} = undef;
	    }
	}
    }

    for my $name (keys %$document) {
	for my $attr ( @{ $field->get($name) || [] } ) {
	    my $func = $attr->{callback};
	    my ($status,$ret) = $func->($document->{$name}, $self->tiny, $opt);
	    $ret ||= {};
	    validate_with(
		params => $ret,
		spec => {
		    message => 0,
		    target  => 0,
		}
	    );

	    if (!$status) {
		$self->set_error(
		    $name => [$attr->{name},$ret->{message}]
		)
	    } else {
		$document->{$name} = $ret->{target} if defined $ret->{target};
	    }
	}
    }
    $self->{document} = $document;
    return $self;
}

1;
__END__

=head1 AUTHOR

Naoto ISHIKAWA, C<< <toona at seesaa.co.jp> >>

=head1 LICENSE AND COPYRIGHT

Copyright 2013 Naoto ISHIKAWA.

This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.


=cut
