#!perl
use strict;
use warnings 'all' => 'FATAL';
use Getopt::Long ();
use Pod::Usage ();

Getopt::Long::GetOptions(
    help => \ &Pod::Usage::pod2usage,
)
    or Pod::Usage::pod2usage();
if ( @ARGV ) {
    open OUT, '>', $ARGV[0]
	or die "Can't open $ARGV[0] for writing: $!";
    select OUT;
}

{
    local @INC = ( @INC, 'lib' );
    require Devel::Spy::_constants;
}

my $SELF           = Devel::Spy::SELF();
my $OTHER          = Devel::Spy::OTHER();
my $INVERTED       = Devel::Spy::INVERTED();

my $TIED_PAYLOAD   = Devel::Spy::TIED_PAYLOAD();
my $UNTIED_PAYLOAD = Devel::Spy::UNTIED_PAYLOAD();
my $CODE           = Devel::Spy::CODE();

require overload;

print <<"HEADER";
#
# Devel/Spy/_overload.pm
#
# Copyright (C) ... by Joshua ben Jore
#
# You may distribute under the terms of either the GNU General Public
# License or the Artistic License, as specified in the README file.
#
# !!!!!!!   DO NOT EDIT THIS FILE   !!!!!!!
#  This file is built by @{[__FILE__]} from its data.  Any changes made here
#  will be lost!
#

package Devel::Spy::_obj;
use strict;
use warnings;

require Sub::Name;
require overload;

HEADER

# Shadow both isa and can methods. I want to make sure other things
# like overload.pm can still make requests about the Devel::Spy::_obj
# class with ->isa and ->can but any request about an object get
# forwarded to the inner, wrapped object.
#
# TODO: should other wrapped methods like DESTROY, ref, import, AUTOLOAD also go here?
print
    map {
        my $method = $_;
        <<"ISA";
sub $method {
    my \$self = shift \@_;

    if ( defined Scalar::Util::blessed( \$self ) ) {
        my \$followup = \$self->[$CODE]->( '->$method' );
        # Object method call passed onto our stored thing.
        return Devel::Spy->new( \$self->[$UNTIED_PAYLOAD]->$method( \@_ ),
        \$followup );
    }
    else {
        # Class method call on Devel::Spy::_obj. Just forward
        # to UNIVERSAL or whatever else is there.
        return \$self->SUPER::$method( \@_ );
    }
}
ISA
    }
    qw(
        isa
        DOES
        can
    );

print <<"OVERLOADS";
my \@overloading = (
    'fallback' => 0,
    '=' => Sub::Name::subname(
        '=',
        sub {
            my \$class = CORE::ref \$_[$SELF];
            my \$followup = \$_[$SELF][$CODE]->(
                '->(= '
                . ( defined \$_[$SELF][$UNTIED_PAYLOAD]
                    ? \$_[$SELF][$UNTIED_PAYLOAD]
                    : 'undef' )
                . ') -> ');
            return Devel::Spy->new( \$_[$SELF][$UNTIED_PAYLOAD], \$followup );
        }
    ),
    'atan2' => Sub::Name::subname(
        'atan2',
        sub {
            my ( \$result, \$followup );
            if ( \$_[$INVERTED] ) {
                \$result = atan2 \$_[$OTHER], \$_[$SELF][$TIED_PAYLOAD];
                \$followup = \$_[$SELF][$CODE]->(
                    ' ->(atan2 '
                    . ( defined \$_[$OTHER]
                        ? \$_[$OTHER]
                        : 'undef')
                    . ', '
                    . ( defined \$_[$SELF][$UNTIED_PAYLOAD]
                        ? \$_[$SELF][$UNTIED_PAYLOAD]
                        : 'undef')
                    . ') ->'
                    . overload::StrVal(\$result) );
            }
            else {
                \$result = atan2 \$_[$SELF][$TIED_PAYLOAD], \$_[$OTHER];
                \$followup = \$_[$SELF][$CODE]->(
                    ' ->(atan2 '
                    . ( defined \$_[$SELF][$UNTIED_PAYLOAD]
                        ? \$_[$SELF][$UNTIED_PAYLOAD]
                        : 'undef')
                    . ', '
                    . ( defined \$_[$OTHER]
                        ? \$_[$OTHER]
                        : 'undef')
                    . ') ->'
                    . overload::StrVal(\$result) );
            }
            return Devel::Spy->new( \$result, \$followup );
        }
    ),
    '-X' => Sub::Name::subname(
        '-X',
        sub {
            my \$result =
                \$_[2] eq 'r' ? ( -r \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'w' ? ( -w \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'x' ? ( -x \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'o' ? ( -o \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'R' ? ( -R \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'W' ? ( -W \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'X' ? ( -X \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'O' ? ( -O \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'e' ? ( -e \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'z' ? ( -z \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 's' ? ( -s \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'f' ? ( -f \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'd' ? ( -d \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'l' ? ( -l \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'p' ? ( -p \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'S' ? ( -S \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'b' ? ( -b \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'c' ? ( -c \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 't' ? ( -t \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'u' ? ( -u \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'g' ? ( -g \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'k' ? ( -k \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'T' ? ( -T \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'B' ? ( -B \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'M' ? ( -M \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'A' ? ( -A \$_[$SELF][$TIED_PAYLOAD] ) :
                \$_[2] eq 'C' ? ( -C \$_[$SELF][$TIED_PAYLOAD] ) :
                eval( "-\$_[2] \$_[$SELF][$TIED_PAYLOAD]" );
            my \$followup = \$_[$SELF][$CODE]->(
                " ->(-\$_[2] "
                . ( defined \$_[$SELF][$UNTIED_PAYLOAD]
                    ? \$_[$SELF][$UNTIED_PAYLOAD]
                    : 'undef' )
                . ') ->'
                . overload::StrVal(\$result) );
            return Devel::Spy->new( \$result, \$followup );
        }
    ),
    '<>' => Sub::Name::subname(
        '<>',
        sub {
            my \$result = readline \$_[$SELF][$TIED_PAYLOAD];
            my \$followup = \$_[$SELF][$CODE]->(
                ' ->(readline '
                . ( defined \$_[$SELF][$UNTIED_PAYLOAD]
                    ? \$_[$SELF][$UNTIED_PAYLOAD]
                    : 'undef' )
                . ') ->'
                . overload::StrVal(\$result) );
            return Devel::Spy->new( \$result, \$followup );
        }
    ),
OVERLOADS

print
    map {
        my $deref = $_;
        <<"DEREFERENCING";
    '${deref}' => Sub::Name::subname(
        '${deref}',
        sub {

            # Allow ourselves to access our own guts and let
            # everyone else have the payload.
            if ( caller() eq 'Devel::Spy::_obj' ) {
                return \$_[$SELF];
            }
            else {
                # This idea is really dodgy but I found myself in
                # an infinite loop of some kind when I returned a
                # plain Devel::Spy object wrapping the
                # result. Bummer.
                my \$followup = \$_[$SELF][$CODE]->( ' ->$deref' );
                my \$tied = \$_[$SELF][$TIED_PAYLOAD];
                my \$reftype = CORE::ref( \$tied );
                my \$obj =
                    'HASH'   eq \$reftype ? ( tied %\$tied  ) :
                    'ARRAY'  eq \$reftype ? ( tied \@\$tied ) :
                    \$reftype =~ /
                        ^
                        (?:
                            SCALAR
                          | REF
                          | LVALUE
                          | REGEXP
                          | VSTRING
                          | BIND
                        )
                        \\z
                    /x
                        ? ( tied \$\$tied ) :
                    'CODE'   eq \$reftype ? ( tied &\$tied  ) :
                    \$reftype =~ /
                        ^
                        (?:
                            GLOB
                          | FORMAT
                          | IO
                        )
                        \\z
                    /x
                        ? ( tied *\$tied ) :
                    die "Unknown reftype \$reftype for object \$tied";
                \$obj->[1] = \$followup;
                return \$tied;
            }
        }
    ),
DEREFERENCING
    }
    split ' ',
    $overload::ops{dereferencing};

print
    map {
        my $converter = $_;
        <<"CONVERSION";
    '${converter}' => Sub::Name::subname(
        '${converter}',
        sub {
            \$_[$SELF][$CODE]->(' ->$converter');
            return \$_[$SELF][$TIED_PAYLOAD];
        }
    ),
CONVERSION
    }
    split ' ',
    $overload::ops{conversion};

# Do a common things for all these common binary operators except |=,
# &=, and ^= which are assignment operators and will handled
# elsewhere.
print
    map {
        my $op = $_;
        <<"BINARY";
    '${op}' => Sub::Name::subname(
        '${op}',
        sub {
            my ( \$result, \$followup );
            if ( \$_[$INVERTED] ) {
                \$result = \$_[$OTHER] $op \$_[$SELF][$TIED_PAYLOAD];
                \$followup = \$_[$SELF][$CODE]->(
                    ' ->('
                    . ( defined \$_[$OTHER]
                        ? \$_[$OTHER]
                        : 'undef')
                    . ' $op '
                    . ( defined \$_[$SELF][$UNTIED_PAYLOAD]
                        ? \$_[$SELF][$UNTIED_PAYLOAD]
                        : 'undef')
                    . ') ->'
                    . overload::StrVal(\$result) );
            }
            else {
                \$result = \$_[$SELF][$TIED_PAYLOAD] $op \$_[$OTHER];
                \$followup = \$_[$SELF][$CODE]->(
                    ' ->('
                    . ( defined \$_[$SELF][$UNTIED_PAYLOAD]
                        ? \$_[$SELF][$UNTIED_PAYLOAD]
                        : 'undef')
                    . ' $op '
                    . ( defined \$_[$OTHER]
                        ? \$_[$OTHER]
                        : 'undef')
                    . ') ->'
                    . overload::StrVal(\$result) );
            }
            return Devel::Spy->new( \$result, \$followup );
        }
    ),
BINARY
    }
    grep { ! /^[|&^]=\z/ }
    map split(' '),
    @overload::ops{qw(
        with_assign
        num_comparison
        3way_comparison
        str_comparison
        binary
        matching
    )};

# Handle ++ and --. Overload's copy constructor will take care of
# post-inc/decrement by first making a copy of the value to return and
# invoking ++/-- on the original.
print
    map {
        my $op = $_;
        <<"MUTATOR";
    '${op}' => Sub::Name::subname(
        '${op}',
        sub {
            my \$result = $op \$_[$SELF][$TIED_PAYLOAD];
            my \$followup = \$_[$SELF][$CODE]->(
                ' ->($op '
                . ( defined \$_[$SELF][$UNTIED_PAYLOAD]
                    ? \$_[$SELF][$UNTIED_PAYLOAD]
                    : 'undef' )
                . ') ->'
                . overload::StrVal(\$result) );
            return Devel::Spy->new( \$result, \$followup );
        }
    ),
MUTATOR
    }
    split ' ',
    $overload::ops{mutators};

# Handle assignment operators plus &=, |=, and ^= from 'binary'.
print
    map {
        my $op = $_;
        <<"ASSIGNMENT";
    '${op}' => Sub::Name::subname(
        '${op}',
        sub {
            \$_[$SELF][$TIED_PAYLOAD] $op \$_[$OTHER];
            my \$followup = \$_[$SELF][$CODE]->(
                '->($op '
                . ( defined \$_[$OTHER]
                    ? \$_[$OTHER]
                    : 'undef' )
                . ') ->'
                . overload::StrVal(\$_[$SELF][$UNTIED_PAYLOAD]) );
            \$_[0] = Devel::Spy->new( \$_[$SELF][$UNTIED_PAYLOAD], \$followup );
        }
    ),
ASSIGNMENT
    }
    grep { /=$/ }
    map split(' '),
    @overload::ops{qw(
        assign
        binary
    )};

# Handle unary and math functions except atan2
print
    map {
        my $op = $_;
        my $actual_op = $op eq 'neg' ? '!' : $op;
        <<"UNARY";
    '${op}' => Sub::Name::subname(
        '${op}',
        sub {
            my \$result = $actual_op \$_[$SELF][$TIED_PAYLOAD];
            my \$followup = \$_[$SELF][$CODE]->(
                " ->($op"
                . ( defined \$_[$SELF][$UNTIED_PAYLOAD]
                    ? \$_[$SELF][$UNTIED_PAYLOAD]
                    : 'undef')
                . ') ->'
                . overload::StrVal(\$result) );
            return Devel::Spy->new( \$result, \$followup );
        }
    ),
UNARY
    }
    grep { $_ ne 'atan2' }
    map split(' '),
    @overload::ops{qw(
        unary
        func
    )};

print <<"FOOTER";
);

overload->import( \@overloading );

# TEST: Verify that all overloadable operations have been overloaded
for my \$category ( sort keys %overload::ops ) {
    my \@ops = split ' ', \$overload::ops{\$category};
    for my \$op ( \@ops ) {
            next if \$category eq 'special' && \$op eq 'nomethod';
            next if \$category eq 'special' && \$op eq 'fallback';

	no strict 'refs';

	next if defined &{"Devel::Spy::_obj::(\$op"};

	warn "Missing op [\$op] from category [\$category]";
    }
}

# Clean up the few things I've generated in this namespace
delete \@Devel::Spy::_obj::{qw(
    BEGIN
    __ANON__
)};

1;
FOOTER
