package Bitcoin::Crypto::Transaction::UTXO;
$Bitcoin::Crypto::Transaction::UTXO::VERSION = '3.002';
use v5.10;
use strict;
use warnings;

use Moo;
use Mooish::AttributeBuilder -standard;
use Types::Common -sigs, -types;

use Bitcoin::Crypto::Transaction;
use Bitcoin::Crypto::Transaction::Output;
use Bitcoin::Crypto::Types -types;
use Bitcoin::Crypto::Util qw(to_format);
use Bitcoin::Crypto::Exception;

use namespace::clean;

my %utxos;
my $loader;

has param 'txid' => (
	coerce => ByteStr->create_child_type(
		constraint => q{ length $_ == 32 },
		coercion => 1
	),
);

# NOTE: ideally, utxo should point to a transaction, and transaction should
# point to a block
has option 'block' => (
	isa => InstanceOf ['Bitcoin::Crypto::Block'],
);

has param 'output_index' => (
	isa => IntMaxBits [32],
);

has param 'output' => (
	coerce => (InstanceOf ['Bitcoin::Crypto::Transaction::Output'])
		->plus_coercions(HashRef q{ Bitcoin::Crypto::Transaction::Output->new($_) }),
);

signature_for register => (
	method => Object,
	positional => [],
);

sub register
{
	my ($self) = @_;

	# Do not store NULLDATA UTXOs
	return $self
		if $self->output->is_standard && $self->output->locking_script->type eq 'NULLDATA';

	$utxos{$self->txid}[$self->output_index] = $self;
	return $self;
}

signature_for unregister => (
	method => Object,
	positional => [],
);

sub unregister
{
	my ($self) = @_;

	delete $utxos{$self->txid}[$self->output_index];
	return $self;
}

signature_for get => (
	method => Str,
	positional => [ByteStr, PositiveOrZeroInt],
);

sub get
{
	my ($class, $txid, $outid) = @_;

	my $utxo = $utxos{$txid}[$outid];

	# NOTE: loader should unregister the utxo in its own store
	if (!$utxo && defined $loader) {
		$utxo = $loader->($txid, $outid);
		$utxo->register if $utxo;
	}

	Bitcoin::Crypto::Exception::UTXO->raise(
		"no UTXO registered for transaction id @{[to_format [hex => $txid]]} and output index $outid"
	) unless $utxo;

	return $utxo;
}

signature_for set_loader => (
	method => Str,
	positional => [Maybe [CodeRef]],
);

sub set_loader
{
	my ($class, $new_loader) = @_;

	$loader = $new_loader;
	return;
}

signature_for extract => (
	method => Str,
	positional => [ByteStr],
);

sub extract
{
	my ($class, $serialized_tx) = @_;

	my $tx = Bitcoin::Crypto::Transaction->from_serialized($serialized_tx);
	$tx->update_utxos;

	return;
}

1;

__END__
=head1 NAME

Bitcoin::Crypto::Transaction::UTXO - Unspent transaction output instance

=head1 SYNOPSIS

	use Bitcoin::Crypto qw(btc_utxo);

	# register the utxos automatically from the serialized transaction
	btc_utxo->extract($serialized_tx);

	# create the utxo manually
	my $utxo = btc_utxo->new(
		txid => [hex => '94e519b9c0f43228e3dc841d838fc7372de95345206ef936ac6020889abe0457'],
		output_index => 1,
		output => {
			locking_script => [P2PKH => '1HrfeGdVP4d1uAdbSknzeaFpDFQVJyVpLu'],
			value => 1_02119131,
		}
	);

	# register
	$utxo->register;

	# find the utxo
	btc_utxo->get([hex => '94e519b9c0f43228e3dc841d838fc7372de95345206ef936ac6020889abe0457'], 1);

	# unregister
	$utxo->unregister;

=head1 DESCRIPTION

UTXO is a transaction output which hasn't been spent yet. All transaction
inputs must be UTXOs. You need to register UTXOs before you can fully utilize a
transaction. If a transaction has its UTXOs unregistered, its methods may raise
an exception if they require full UTXO data.

This module keeps an internal register of all valid UTXOs. You can add or
remove UTXOs from this register, and they are accessed using two
characteristics: transaction ID and output number (counted from 0).

=head1 INTERFACE

=head2 Attributes

=head3 txid

A bytestring - id of the source transaction.

I<Available in the constructor>.

=head3 output_index

A positive or zero integer which is the index of the output in the source
transaction.

I<Available in the constructor>.

=head3 block

Optional instance of L<Bitcoin::Crypto::Block>.

I<Available in the constructor>.

=head3 output

Instance of L<Bitcoin::Crypto::Transaction::Output>. A hash reference will be
coerced into an object by passing it to the constructor.

I<Available in the constructor>.

=head2 Methods

=head3 new

	$tx = $class->new(%args)

This is a standard Moo constructor, which can be used to create the object. It
takes arguments specified in L</Attributes>.

Returns class instance.

=head3 register

	$object = $object->register()

Registers the given UTXO. It will be held in memory and will be available to
fetch using L</get>.

=head3 unregister

	$object = $object->unregister()

Does the opposite of L</register>.

=head3 get

	$utxo = $object->get($txid, $output_index);

Returns the UTXO registered with given txid and output index. Throws an
exception if it cannot be found or loaded.

=head3 set_loader

	$class->set_loader(sub { ... })
	$class->set_loader(undef)

Replaces an UTXO loader.

The subroutine should accept the same parameters as L</get> and return a
constructed UTXO object. If possible, the loader should not return the same
UTXO twice in a single runtime of the script.

Returns nothing. Passing undef disables the custom loader.

=head3 extract

	$class->extract($serialized_tx)

Extracts all outputs from the C<$serialized_tx> (a bytestring). Same can be
achieved by calling C<update_utxos> on a transaction object.

Returns nothing. All C<$serialized_tx> outputs will be added to the register as
new UTXO instances.

=head1 EXCEPTIONS

This module throws an instance of L<Bitcoin::Crypto::Exception> if it
encounters an error. It can produce the following error types from the
L<Bitcoin::Crypto::Exception> namespace:

=over

=item * UTXO - UTXO was not found

=back

=head1 SEE ALSO

=over

=item L<Bitcoin::Crypto::Transaction>

=back

=cut

