package ddGUI::Window;

use 5.010001;
use strict;
use warnings;

BEGIN {
	$ddGUI::Window::AUTHORITY = 'cpan:TOBYINK';
	$ddGUI::Window::VERSION   = '0.004';
}

use Moo;

use B 'perlstring';
use Devel::Size qw( );
use Prima qw( Application FrameSet DetailedOutline PodView );
use Scalar::Util qw( blessed reftype refaddr );
use Types::Standard -types;

has title   => (is => "ro",   isa => Str,              required => 1);
has items   => (is => "ro",   isa => ArrayRef,         required => 1);
has $_      => (is => "lazy", isa => Object,           builder => 1) for qw( window frameset outline textview );
has size    => (is => "ro",   isa => Tuple[Int, Int],  default => sub { [640, 480] });
has headers => (is => "ro",   isa => Tuple[Str, Str],  default => sub { ['Path', 'Value'] });
has vars    => (is => "lazy", isa => ArrayRef[Str],    builder => 1);
has _seen   => (is => "rw",   isa => HashRef[Str],     default => sub { +{} });

sub _build_window {
	my $self = shift;
	
	return 'Prima::MainWindow'->new(
		text      => $self->title,
		size      => $self->size,
		autoClose => 1,
	);
}

sub _build_frameset {
	my $self = shift;
	
	$self->window->insert(
		'Prima::FrameSet',
		pack         => { expand => 1, fill => 'both'},
		frameSizes   => [qw(33% *)],
		opaqueResize => 1,
	);
}

sub _build_outline {
	my $self = shift;
	
	return $self->frameset->insert_to_frame(
		0,
		'Prima::DetailedOutline',
		columns   => 2,
		pack      => { expand => 1, fill => 'both'},
		headers   => $self->headers,
		items     => $self->_prepared_items,
		onSelectItem => sub {
			my $outline = $_[0];
			my ($item)  = $outline->get_item($_[1][0][0]);
			my $data    = $item->[0][2];
			my $path    = $item->[0][3];
			$self->_display_node( $path, $data );
		},
	);
}

sub _build_textview {
	my $self = shift;
	
	$self->frameset->insert_to_frame(
		1,
		'Prima::PodView',
		pack => { expand => 1, fill => 'both'},
	);
}

sub execute {
	my $self = shift;
	
	# ensure widgets are built
	$self->$_ for qw( window outline textview );
	
	$self->window->execute;
}

sub _display_node {
	my $self = shift;
	my ($path, $item) = @_;
	
	my $tv = $self->textview;
	$tv->open_read;
	$tv->{readState}{createIndex} = [];
	$tv->read("=head1 VARIABLE\n\n$path\n\n");
	if (!defined($item)) {
		$tv->read("=head1 TYPE\n\nUndefined.\n\n");
	}
	elsif (!ref($item)) {
		$tv->read("=head1 TYPE\n\nNon-reference scalar.\n\n");
		$tv->read("=head1 VALUE\n\nC<< ${\ perlstring($item)} >>\n\n");
	}
	else {
		$tv->read("=head1 TYPE\n\n");
		$tv->read("Reference address: ${\ sprintf '0x%08X', refaddr($item) }.\n\n");
		$tv->read("Reference type: ${\ reftype($item) }.\n\n");
		
		if (reftype($item) eq 'ARRAY') {
			$tv->read("Array length: ${\ scalar(@$item) }.\n\n");
		}
		
		if (reftype($item) eq 'HASH') {
			$tv->read("Hash key count: ${\ scalar(keys %$item) }.\n\n");
			$tv->read("Hash buckets: ${\ scalar(%$item) } (used/total).\n\n");
		}
		
		if (blessed($item)) {
			$tv->read("Blessed: ${\ ref($item)}.\n\n");
			if ('Class::MOP'->can('class_of') and my $meta = Class::MOP::class_of($item)) {
				$self->_display_moose($path, $item, $meta);
			}
		}
	}
	$tv->read("=head1 MEMORY USAGE\n\n");
	$tv->read("Size: ${\ Devel::Size::size($item)} bytes.\n\n");
	$tv->read("Total size: ${\ Devel::Size::total_size($item)} bytes.\n\n");
	$tv->close_read;
}

sub _display_moose {
	my $self = shift;
	my ($path, $item, $meta) = @_;
	my $tv = $self->textview;
	
	my %d = (
		SUPERCLASSES => [ $meta->linearized_isa ],
		SUBCLASSES   => [ sort $meta->subclasses ],
		ROLES        => [ sort map $_->name, $meta->calculate_all_roles_with_inheritance ],
		METHODS      => [ sort map $_->name, $meta->get_all_methods ],
	);
	shift @{$d{SUPERCLASSES}}; # itself
	for my $h (qw/ SUPERCLASSES SUBCLASSES ROLES METHODS /) {
		$tv->read("=head1 $h\n\n");
		if (@{$d{$h}}) {
			$tv->read(join("; ", @{$d{$h}}).".\n\n");
		}
		else {
			$tv->read("(none).\n\n");
		}
	}
	
	$tv->read("=head1 ATTRIBUTES\n\n");
	
	if (!$meta->get_all_attributes) {
		$tv->read("(none)");
		return;
	}
	
	$tv->read("=over\n\n");
	for my $attr ($meta->get_all_attributes) {
		$tv->read(
			"=item *\n\nB<< ${\ $attr->name } >>"
			. "${\( $attr->has_documentation ? (' - '.$attr->documentation) : '' )}\n\n"
		);
		if ($attr->has_value($item)) {
			$tv->read("${\ Type::Tiny::_dd($attr->get_raw_value($item)) }\n\n");
		}
		else {
			$tv->read("Unset\n\n");
		}
	}
	$tv->read("=back\n\n");
}

sub _build_vars {
	my $self = shift;
	my $pfx  = $Data::Dumper::Varname;
	return [
		map "\$$pfx$_", 1 .. scalar(@{$self->items})
	];
}

sub _prepared_items {
	my $self = shift;
	$self->_seen({});
	my $i = -1;
	return [
		map { ++$i; $self->_item_to_arrayref($self->vars->[$i], $_, $self->vars->[$i]) } @{ $self->items }
	];
}

sub __maybequote {
	map {
		(/^_*[A-Z][A-Z0-9_]*$/i || /^-_*[A-Z][A-Z0-9_]+$/i)
			? $_
			: perlstring($_)
	} @_
}

sub _item_to_arrayref {
	my $self = shift;
	my ($label, $item, $path) = @_;
	
	if (!defined($item)) {
		return [[ $label, "(undef)", $item, $path ]];
	}
	
	if (!ref($item)) {
		return [[ $label, perlstring($item), $item, $path ]];
	}
	
	if (exists $self->_seen->{ refaddr($item) }) {
		my $is = $self->_seen->{ refaddr($item) };
		return [[ $label, "= $is", $item, $path ]];
	}
	
	$self->_seen->{ refaddr($item) } = $path;
	
	my @internals;
	
	if (reftype($item) eq 'ARRAY') {
		my $i = $[-1;
		@internals = map { ++$i; $self->_item_to_arrayref("[$i]", $_, "$path\->[$i]") } @$item;
	}
	if (reftype($item) eq 'HASH') {
		@internals = map { $self->_item_to_arrayref("{${\__maybequote($_)}}", $item->{$_}, "$path\->{${\__maybequote($_)}}") } sort keys %$item;
	}
	if (reftype($item) eq 'SCALAR') {
		@internals = $self->_item_to_arrayref("\${...}", $$item, "\${$path}");
	}
	
	return [[ $label, '['.ref($item).']', $item, $path ], \@internals, !blessed($item), undef ];
}

1;

__END__

=pod

=encoding utf-8

=head1 NAME

ddGUI::Window - all the Prima stuff for Data::Dumper::GUI

=head1 DESCRIPTION

No user-serviceable parts within.

=head1 BUGS

Please report any bugs to
L<http://rt.cpan.org/Dist/Display.html?Queue=Data-Dumper-GUI>.

=head1 SEE ALSO

L<Data::Dumper::GUI>.

=head1 AUTHOR

Toby Inkster E<lt>tobyink@cpan.orgE<gt>.

=head1 COPYRIGHT AND LICENCE

This software is copyright (c) 2013 by Toby Inkster.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=head1 DISCLAIMER OF WARRANTIES

THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.

