### ----------------------------------------------------
### -- PDF
### -- Licenses: Artistic-2.0
### -- Authors: David Warring
### -- File: lib/PDF/IO/Crypt/AST.rakumod
### ----------------------------------------------------
unit role PDF::IO::Crypt::AST;

method crypt {...}
method EncryptMetadata {...}

my subset MetadataDictLike of Hash where (.<Type> ~~ Pair) && (.<Type><name> ~~ 'Metadata');

my subset EncryptDictLike of Hash where (.<Filter>:exists) && (.<U>:exists) && (.<O>:exists) && (.<P>:exists);

my subset SignatureDictLike of Hash where (.<Filter>:exists) && (.<Contents>:exists);

#| encrypt/decrypt all strings/streams in a PDF body
multi method crypt-ast('body', Array $body, Str :$mode = 'decrypt') {
    for $body.values {
        $.crypt-ast(.key, .value, :$mode)
            for .<objects>.values;
    }
}

#| descend and indirect object encrypting/decrypting any strings or streams
multi method crypt-ast('ind-obj', Array $ast, |c) {
    my UInt $obj-num = $ast[0];
    my UInt $gen-num = $ast[1];
    $.crypt-ast( $ast[2], :$obj-num, :$gen-num, |c );
}

multi method crypt-ast('array', Array $ast, |c) {
    $.crypt-ast($_, |c) for $ast.values
}

multi method crypt-ast('dict', EncryptDictLike $ast, |c) {
    # skip encryption of the Encrypt dictionary
}

multi method crypt-ast('dict', SignatureDictLike $ast, |c) {
    # skip encryption of the /Contents entry of a Signature
    # dictionary, if it's a hex string.
    for $ast.pairs.sort {
        $.crypt-ast(.value, |c)
            unless .key eq 'Contents' && .value ~~ :hex-string;
    }
}

multi method crypt-ast('dict', Hash $ast, |c) {
    $.crypt-ast(.value, |c)
        for $ast.pairs.sort;
}

multi method crypt-ast('stream', Hash $ast, |c) {
    my $dict = $ast<dict>;

    return
        if ! $.EncryptMetadata && $dict ~~ MetadataDictLike;

    $.crypt-ast($_, |c)
        for $ast.pairs;
    $dict<Length> = .codes
        with $ast<encoded>;
}

multi method crypt-ast(Str $key where 'hex-string'|'literal'|'encoded' , $ast is rw, :$obj-num, |c) {
    $ast = $.crypt( $ast, :$obj-num, |c )
        if $obj-num;
}
multi method crypt-ast(Str $key where 'hex-string'|'literal'|'encoded' , $ast, :$obj-num, :$gen-num, |c) {
    warn "read only '$key' {$ast.raku} in $obj-num $gen-num R";
}

multi method crypt-ast( Pair $p, |c) {
    $.crypt-ast( $p.key, $p.value, |c)
}

#| for JSON deserialization, e.g. { :int(42) } => :int(42)
use PDF::Grammar :AST-Types;
BEGIN my %ast-types = AST-Types.enums;
multi method crypt-ast( Hash $h! where { .keys == 1 && (%ast-types{.keys[0]}:exists)}, |c) {
    $.crypt-ast( |$h.kv, |c )
}

multi method crypt-ast(Str $key, $) { }
multi method crypt-ast(Numeric:D, |c) { }
multi method crypt-ast(Any:U, |c) { }



