#!/usr/bin/perl

use strict;
use warnings;
use v5.10;

use Scalar::Util qw(blessed looks_like_number);

use File::Information;

# Check which types we, only request data for installed modules.
my @as_types = qw(bool mediatype raw);
unshift(@as_types, 'DateTime')          if eval { require DateTime;         1 };
unshift(@as_types, 'Data::Identifier');

my %config = (
    digest_as => undef,
    key_length => undef,
);

my $instance;
{
    my %settings;

    while (scalar(@ARGV) && $ARGV[0] =~ /^--./) {
        my $opt = shift(@ARGV);

        if ($opt eq '--database' && scalar(@ARGV)) {
            my $dsn = shift(@ARGV);
            require Data::TagDB;
            $settings{db} = Data::TagDB->new($dsn);
            unshift(@as_types, 'Data::TagDB::Tag');
        } elsif ($opt eq '--extractor') {
            require Data::URIID;
            $settings{extractor} = Data::URIID->new;
        } elsif ($opt eq '--digest-as' && scalar(@ARGV)) {
            $config{digest_as} = shift(@ARGV);
        } elsif ($opt eq '--key-length' && scalar(@ARGV)) {
            $config{key_length} = int(shift(@ARGV));
        } elsif ($opt =~ /^--skip-(?:inode|deep|filesystem|tagpool)$/) {
            $config{$opt =~ s/^--//r =~ s/-/_/gr} = 1;
        } elsif ($opt eq '--tagpool-rc' && scalar(@ARGV)) {
            push(@{$settings{tagpool_rc} //= []}, shift(@ARGV));
        } elsif ($opt eq '--tagpool-path' && scalar(@ARGV)) {
            push(@{$settings{tagpool_path} //= []}, shift(@ARGV));
        } elsif ($opt eq '--digest-sizelimit' && scalar(@ARGV)) {
            $settings{digest_sizelimit} = int(shift(@ARGV));
        } elsif ($opt eq '--store-path' && scalar(@ARGV)) {
            push(@{$settings{store} //= []}, shift(@ARGV));
        } elsif ($opt eq '--help') {
            say 'Usage: ', $0, ' [OPTIONS] file...';
            say '';
            say 'OPTIONS:';
            say '   --database DSN              - Opens the given database via Data::TagDB';
            say '   --extractor                 - Creates an extractor (experimental!)';
            say '   --digest-as TYPE            - Selects the digest rendering';
            say '                                 Common values are: hex (default), b64, and utag';
            say '   --key-length NUM            - The assumed length for all keys';
            say '                                 Set to 0 for most diff-able output.';
            say ' * --skip-{inode|deep|filesystem|tagpool}';
            say '                               - Skips display of the named subobject';
            say ' * --tagpool-rc FILE           - Selects the tagpool config to use';
            say ' * --tagpool-path PATH         - Selects the tagpool paths to use';
            say '   --digest-sizelimit SIZE     - Sets the size limit for digest calculation';
            say '                                 The value is in bytes.';
            say ' * --store-path PATH           - Sets the path to a file store';
            say '                                 Will be opened via File::FStore';
            say '   --help                      - Show this help';
            say '';
            say 'Options marked with * can be given multiple times.';
            exit;
        } else {
            die 'Invalid option: '.$opt;
        }
    }

    $instance = File::Information->new(%settings);
}

shift(@ARGV) if scalar(@ARGV) && $ARGV[0] eq '--';

foreach my $filename (@ARGV) {
    my $link = $instance->for_link(path => $filename, symlinks => 'follow');

    say $filename;
    say '-' x length($filename);

    eval {$link->deep};

    dump_base($link);
    dump_base(scalar eval {$link->inode})       unless $config{skip_inode};
    dump_base(scalar eval {$link->deep})        unless $config{skip_deep};
    dump_base(scalar eval {$link->filesystem})  unless $config{skip_filesystem};
    unless ($config{skip_tagpool}) {
        dump_base($_) foreach eval {$link->tagpool};
    }
}

sub dump_base {
    my ($base) = @_;
    my File::Information $instance;
    my @hashes;
    my @properties;
    my $max_len = $config{key_length};
    my %verify_a_b;

    return unless defined $base;
    $instance   = $base->instance;
    @hashes     = sort map {$_->{name}} $base->digest_info;
    @properties = sort map {$_->{name}} $base->property_info;

    {
        my $title = ref($base) =~ s/^.*:://r;
        say ' ', $title;
        say ' ', '-' x length($title);
    }

    unless (defined $max_len) {
        # Figure out the witdh of the key column.

        $max_len = 0;

        outer:
        foreach my $key (@properties) {
            foreach my $lifecycle ($instance->lifecycles) {
                my @values = $base->get($key, lifecycle => $lifecycle, list => 1, default => [], as => 'raw');
                if (scalar @values) {
                    my $l = length($key);
                    $max_len = $l if $l > $max_len;
                    next outer;
                }
            }
        }

        outer:
        foreach my $algo (@hashes) {
            my $key = sprintf('digest <%s> unsafe', $algo);
            foreach my $lifecycle ($instance->lifecycles) {
                my $value = $base->digest($algo, lifecycle => $lifecycle, default => undef);
                if (defined $value) {
                    my $l = length($key);
                    $max_len = $l if $l > $max_len;
                    next outer;
                }
            }
        }
    }

    # Print properties:
    foreach my $key (@properties) {
        foreach my $lifecycle ($instance->lifecycles) {
            my $first = 1;
            my @values;
            my $type;

            foreach my $as (@as_types) {
                @values = $base->get($key, lifecycle => $lifecycle, list => 1, default => [], as => $as);
                $type = $as;
                last if scalar @values;
            }
            next unless scalar @values;

            foreach my $value (@values) {
                if (blessed($value)) {
                    if ($value->isa('Data::Identifier')) {
                        my $id = $value;
                        $value = sprintf('%s / %s', $id->type->displayname, $id->id);
                        if (defined(my $displayname = $id->displayname(no_defaults => 1, default => undef))) {
                            $value .= sprintf(' (%s)', $displayname);
                        }
                    } elsif ($value->isa('Data::TagDB::Tag')) {
                        my $tag = $value;
                        $value = sprintf('Tag <%s>', $tag->ise);
                        if (defined(my $displayname = $tag->displayname(no_defaults => 1, default => undef))) {
                            $value .= sprintf(' (%s)', $displayname);
                        }
                    } elsif ($value->isa('Data::URIID::Barcode')) {
                        $value = sprintf('Barcode <%s>', $value->data);
                    } elsif ($value->isa('Data::URIID::Colour')) {
                        $value = sprintf('Colour <%s>', $value->rgb);
                    } elsif ($value->isa('File::FStore::File')) {
                        $value = sprintf('Store File <%s>', $value->dbname);
                    }
                } elsif ($type eq 'bool') {
                    $value = $value ? 'true' : 'false';
                } elsif (looks_like_number($value)) {
                    # no-op
                } else {
                    $value = sprintf('\'%s\'', $value);
                }
                if ($first) {
                    printf("  %7s :: %-*s = %s\n", $lifecycle, $max_len, $key, $value);
                    $first = undef;
                } else {
                    printf("%s & %s\n", ' ' x ($max_len + 7 + 6), $value);
                }
            }
        }
    }

    # Print digests:
    foreach my $algo (@hashes) {
        my $key = sprintf('digest <%s>', $algo);

        $key .= ' unsafe' if $instance->digest_info($algo)->{unsafe};

        foreach my $lifecycle ($instance->lifecycles) {
            my $value = $base->digest($algo, lifecycle => $lifecycle, default => undef, as => $config{digest_as});
            next unless defined $value;
            printf("  %7s :: %-*s = %s\n", $lifecycle, $max_len, $key, $value);
        }
    }

    foreach my $lifecycle_from ($instance->lifecycles) {
        foreach my $lifecycle_to ($instance->lifecycles) {
            my $verify_key = join('::', sort ($lifecycle_from, $lifecycle_to));

            next if $verify_a_b{$verify_key};
            $verify_a_b{$verify_key} = 1;

            if ($lifecycle_from ne $lifecycle_to) {
                my $result = $base->verify(lifecycle_from => $lifecycle_from, lifecycle_to => $lifecycle_to);
                unless ($result->has_no_data) {
                    my $displaykey = sprintf('%7s :: verify', $lifecycle_to);
                    printf("  %7s :: %-*s = %s\n", $lifecycle_from, $max_len, $displaykey, $result->status);
                }
            }
        }
    }
}

#ll
