package Test::ApplyPatch;

use strict;
use warnings;
use Exporter qw'import';

our @EXPORT      = qw//;
our @EXPORT_OK   = qw/&apply_patch/;
our %EXPORT_TAGS = ( 
    DEFAULT => [qw/&apply_patch/],
    all    =>  [qw/&apply_patch/]
);

use JSON qw();
use JSON::Pointer;
use JSON::Pointer::Exception qw();
use TryCatch;
use Data::Dumper;

sub apply_patch {
    my ($entity, $patch, $optional_field_code_ref) = @_;
    for my $op (@{ $patch }) {
        my $coderef = JSON::Pointer->can($op->{op});
        die "invalid op '".$op->{op}."' despite schema validation" unless $coderef;
        for my $op_name ($op->{op}) {
            if ('add' eq $op_name or 'replace' eq $op_name) {
                try {
                    $entity = $coderef->('JSON::Pointer', $entity, $op->{path}, $op->{value});
                } catch($pe) {
                    if (defined $optional_field_code_ref && ref $optional_field_code_ref eq 'CODE') {
                        if (blessed($pe) && $pe->isa('JSON::Pointer::Exception') && $pe->code == JSON::Pointer::Exception->ERROR_POINTER_REFERENCES_NON_EXISTENT_VALUE) {
                            &$optional_field_code_ref(substr($op->{path},1),$entity,$op);
                            $entity = $coderef->('JSON::Pointer', $entity, $op->{path}, $op->{value});
                        }
                    } else {
                        die($pe); #->rethrow;
                    }
                }
            } elsif ('remove' eq $op_name) {
                try {
                    $entity = $coderef->('JSON::Pointer', $entity, $op->{path});
                } catch($pe) {
                    if (defined $optional_field_code_ref && ref $optional_field_code_ref eq 'CODE') {
                        if (blessed $pe && $pe->isa('JSON::Pointer::Exception') && $pe->code == JSON::Pointer::Exception->ERROR_POINTER_REFERENCES_NON_EXISTENT_VALUE) {
                            &$optional_field_code_ref(substr($op->{path},1),$entity);
                            $entity = $coderef->('JSON::Pointer', $entity, $op->{path});
                        }
                    } else {
                        die($pe); #->rethrow;
                    }
                }
            } elsif ('move' eq $op_name or 'copy' eq $op_name) {
                try {
                    $entity = $coderef->('JSON::Pointer', $entity, $op->{from}, $op->{path});
                } catch($pe) {
                    if (defined $optional_field_code_ref && ref $optional_field_code_ref eq 'CODE') {
                        if (blessed $pe && $pe->isa('JSON::Pointer::Exception') && $pe->code == JSON::Pointer::Exception->ERROR_POINTER_REFERENCES_NON_EXISTENT_VALUE) {
                            &$optional_field_code_ref(substr($op->{path},1),$entity);
                            $entity = $coderef->('JSON::Pointer', $entity, $op->{from}, $op->{path});
                        }
                    } else {
                        die($pe); #->rethrow;
                    }
                }
            } elsif ('test' eq $op_name) {
                try {
                    die "test failed - path: $op->{path} value: $op->{value}\n"
                        unless $coderef->('JSON::Pointer', $entity, $op->{path}, $op->{value});
                } catch($pe) {
                    if (defined $optional_field_code_ref && ref $optional_field_code_ref eq 'CODE') {
                        if (blessed $pe && $pe->isa('JSON::Pointer::Exception') && $pe->code == JSON::Pointer::Exception->ERROR_POINTER_REFERENCES_NON_EXISTENT_VALUE) {
                            &$optional_field_code_ref(substr($op->{path},1),$entity);
                            die "test failed - path: $op->{path} value: $op->{value}\n"
                                unless $coderef->('JSON::Pointer', $entity, $op->{path}, $op->{value});
                        }
                    } else {
                        die($pe); #->rethrow;
                    }
                }
            }
        }
    }
    return $entity;
}
1;
