use utf8;

package Pinto::Schema::Result::Revision;

# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE


use strict;
use warnings;

use Moose;
use MooseX::NonMoose;
use MooseX::MarkAsMethods autoclean => 1;
extends 'DBIx::Class::Core';


__PACKAGE__->table("revision");


__PACKAGE__->add_columns(
    "id", { data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
    "uuid",         { data_type => "text",    is_nullable => 0 },
    "message",      { data_type => "text",    is_nullable => 0 },
    "username",     { data_type => "text",    is_nullable => 0 },
    "utc_time",     { data_type => "integer", is_nullable => 0 },
    "time_offset",  { data_type => "integer", is_nullable => 0 },
    "is_committed", { data_type => "boolean", is_nullable => 0 },
    "has_changes",  { data_type => "boolean", is_nullable => 0 },
);


__PACKAGE__->set_primary_key("id");


__PACKAGE__->add_unique_constraint( "uuid_unique", ["uuid"] );


__PACKAGE__->has_many(
    "ancestry_children", "Pinto::Schema::Result::Ancestry",
    { "foreign.child" => "self.id" }, { cascade_copy => 0, cascade_delete => 0 },
);


__PACKAGE__->has_many(
    "ancestry_parents", "Pinto::Schema::Result::Ancestry",
    { "foreign.parent" => "self.id" }, { cascade_copy => 0, cascade_delete => 0 },
);


__PACKAGE__->has_many(
    "registrations", "Pinto::Schema::Result::Registration",
    { "foreign.revision" => "self.id" }, { cascade_copy => 0, cascade_delete => 0 },
);


__PACKAGE__->has_many(
    "stacks",
    "Pinto::Schema::Result::Stack",
    { "foreign.head" => "self.id" },
    { cascade_copy   => 0, cascade_delete => 0 },
);


with 'Pinto::Role::Schema::Result';

# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-03-07 12:56:52
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:u3EeZBioyg8H9+azCHQYNA

#------------------------------------------------------------------------------

# ABSTRACT: Represents a set of changes to a stack

#------------------------------------------------------------------------------

our $VERSION = '0.09998'; # VERSION

#------------------------------------------------------------------------------

use MooseX::Types::Moose qw(Str Bool);

use DateTime;
use DateTime::TimeZone;
use DateTime::TimeZone::OffsetOnly;
use String::Format;
use Digest::SHA;

use Pinto::Util qw(:all);

use overload (
    '""'  => 'to_string',
    '<=>' => 'numeric_compare',
    'cmp' => 'numeric_compare',
    'eq'  => 'equals'
);

#------------------------------------------------------------------------------

has uuid_prefix => (
    is       => 'ro',
    isa      => Str,
    default  => sub { substr( $_[0]->uuid, 0, 8 ) },
    init_arg => undef,
    lazy     => 1,
);

has message_title => (
    is       => 'ro',
    isa      => Str,
    default  => sub { trim_text( title_text( $_[0]->message ) ) },
    init_arg => undef,
    lazy     => 1,
);

has message_body => (
    is       => 'ro',
    isa      => Str,
    default  => sub { trim_text( body_text( $_[0]->message ) ) },
    init_arg => undef,
    lazy     => 1,
);

has is_root => (
    is       => 'ro',
    isa      => Bool,
    default  => sub { $_[0]->id == 1 },
    init_arg => undef,
    lazy     => 1,
);

has datetime => (
    is       => 'ro',
    isa      => 'DateTime',
    default  => sub { DateTime->from_epoch( epoch => $_[0]->utc_time ) },
    init_arg => undef,
    lazy     => 1,
);

has datetime_local => (
    is       => 'ro',
    isa      => 'DateTime',
    default  =>  sub {
        my $tz = DateTime::TimeZone->offset_as_string( $_[0]->repo->config->time_offset );
        return DateTime->from_epoch( epoch => $_[0]->utc_time, time_zone => $tz );
    },
    init_arg => undef,
    lazy     => 1,
);

has datetime_user => (
    is       => 'ro',
    isa      => 'DateTime',
    default  => sub { DateTime->from_epoch( epoch => $_[0]->utc_time, time_zone => $_[0]->time_zone ) },
    init_arg => undef,
    lazy     => 1,
);

has time_zone => (
    is      => 'ro',
    isa     => 'DateTime::TimeZone',
    default => sub {
        my $offset = DateTime::TimeZone->offset_as_string( $_[0]->time_offset );
        return DateTime::TimeZone::OffsetOnly->new( offset => $offset );
    },
    init_arg => undef,
    lazy     => 1,
);

#------------------------------------------------------------------------------

sub FOREIGNBUILDARGS {
    my ( $class, $args ) = @_;

    $args                 ||= {};
    $args->{uuid}         ||= uuid();
    $args->{username}     ||= '';
    $args->{utc_time}     ||= current_utc_time();
    $args->{time_offset}  ||= 0;
    $args->{is_committed} ||= 0;
    $args->{has_changes}  ||= 0;
    $args->{message}      ||= '';

    return $args;
}

#------------------------------------------------------------------------------

sub add_parent {
    my ( $self, $parent ) = @_;

    # TODO: Figure out how to do merges
    $self->create_related( ancestry_children => { parent => $parent->id } );

    return;
}

#------------------------------------------------------------------------------

sub add_child {
    my ( $self, $child ) = @_;

    # TODO: Figure out how to do merges
    $self->create_related( ancestry_parents => { child => $child->id } );

    return;
}

#------------------------------------------------------------------------------

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

    my $where = { child => $self->id };
    my $attrs = { join => 'ancestry_parents', order_by => 'me.utc_time' };

    return $self->result_source->resultset->search( $where, $attrs )->all;
}

#------------------------------------------------------------------------------

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

    my $where = { parent => $self->id };
    my $attrs = { join => 'ancestry_children', order_by => 'me.utc_time' };

    return $self->result_source->resultset->search( $where, $attrs )->all;
}

#------------------------------------------------------------------------------

sub is_ancestor_of {
    my ($self, $rev) = @_;

    my @ancestors = $rev->parents;
    while (my $ancestor = pop @ancestors) {
        return 1 if $ancestor->id == $self->id;
        push @ancestors, $ancestor->parents;
    }

    return 0;
}

#------------------------------------------------------------------------------

sub is_descendant_of {
    my ($self, $rev) = @_;

    my @descendants = $rev->children;
    while (my $descendant = pop @descendants) {
        return 1 if $descendant->id == $self->id;
        push @descendants, $descendant->children;
    }

    return 0;
}

#------------------------------------------------------------------------------

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

    my $rev_id   = $self->id;
    my $subquery = "SELECT DISTINCT distribution FROM registration WHERE revision = $rev_id";
    my $where    = { 'me.id' => { in => \$subquery } };
    my $attrs    = { order_by => 'archive' };

    return $self->result_source->schema->search_distribution( $where, $attrs );
}

#------------------------------------------------------------------------------

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

    my $rev_id   = $self->id;
    my $subquery = "SELECT package FROM registration WHERE revision = $rev_id";
    my $where    = { 'me.id' => { in => \$subquery } };
    my $attrs    = { order_by => 'name' };

    return $self->result_source->schema->search_package( $where, $attrs );
}

#------------------------------------------------------------------------------

sub commit {
    my ( $self, %args ) = @_;

    throw "Must specify a message to commit" if not $args{message};

    $args{is_committed} = 1;
    $args{has_changes}  = 0; # XXX: Why reset this?
    $args{username}    ||= $self->repo->config->username;
    $args{time_offset} ||= $self->repo->config->time_offset;
    $args{utc_time}    ||= current_utc_time;

    $self->update( \%args );

    return $self;
}

#------------------------------------------------------------------------------

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

    # TODO: mark column dirty rather than refresh whole object.
    throw "PANIC: Revision $self is already committed"
        if $self->refresh->get_column('is_committed');

    return $self;
}

#-------------------------------------------------------------------------------

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

    # TODO: mark column dirty rather than refresh whole object.
    throw "PANIC: Revision $self is still open"
        if not $self->refresh->get_column('is_committed');

    return $self;
}

#-------------------------------------------------------------------------------

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

    # TODO: mark column dirty rather than refresh whole object.
    throw "PANIC: Revision $self has not changed"
        if not $self->refresh->get_column('has_changes');

    return $self;
}

#------------------------------------------------------------------------------

sub diff {
    my ( $self, $other ) = @_;

    my $left = $other || ( $self->parents )[0];
    my $right = $self;

    require Pinto::Difference;
    return Pinto::Difference->new( left => $left, right => $right );
}

#------------------------------------------------------------------------------

sub numeric_compare {
    my ( $revision_a, $revision_b ) = @_;

    my $pkg = __PACKAGE__;
    throw "Can only compare $pkg objects"
        if not( itis( $revision_a, $pkg ) && itis( $revision_b, $pkg ) );

    return 0 if $revision_a->id == $revision_b->id;

    my $r = ( $revision_a->utc_time <=> $revision_b->utc_time );

    return $r;
}

#------------------------------------------------------------------------------

sub equals {
    my ( $revision_a, $revision_b ) = @_;

    my $pkg = __PACKAGE__;
    throw "Can only compare $pkg objects"
        if not( itis( $revision_a, $pkg ) && itis( $revision_b, $pkg ) );

    return $revision_a->id == $revision_b->id;
}

#------------------------------------------------------------------------------

sub to_string {
    my ( $self, $format ) = @_;

    my %fspec = (
        i => sub { $self->uuid_prefix },
        I => sub { $self->uuid },
        j => sub { $self->username },
        u => sub { $self->datetime_local->strftime( $_[0] || '%c' ) },
        g => sub { $self->message_body },
        G => sub { indent_text( trim_text( $self->message ), $_[0] ) },
        t => sub { $self->message_title },
        T => sub { truncate_text( $self->message_title, $_[0] ) },
    );

    $format ||= $self->default_format;
    return String::Format::stringf( $format, %fspec );
}

#-------------------------------------------------------------------------------

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

    return '%i';
}

#------------------------------------------------------------------------------

__PACKAGE__->meta->make_immutable;

#------------------------------------------------------------------------------
1;

__END__

=pod

=encoding UTF-8

=for :stopwords Jeffrey Ryan Thalhammer

=head1 NAME

Pinto::Schema::Result::Revision - Represents a set of changes to a stack

=head1 VERSION

version 0.09998

=head1 NAME

Pinto::Schema::Result::Revision

=head1 TABLE: C<revision>

=head1 ACCESSORS

=head2 id

  data_type: 'integer'
  is_auto_increment: 1
  is_nullable: 0

=head2 uuid

  data_type: 'text'
  is_nullable: 0

=head2 message

  data_type: 'text'
  is_nullable: 0

=head2 username

  data_type: 'text'
  is_nullable: 0

=head2 utc_time

  data_type: 'integer'
  is_nullable: 0

=head2 time_offset

  data_type: 'integer'
  is_nullable: 0

=head2 is_committed

  data_type: 'boolean'
  is_nullable: 0

=head2 has_changes

  data_type: 'boolean'
  is_nullable: 0

=head1 PRIMARY KEY

=over 4

=item * L</id>

=back

=head1 UNIQUE CONSTRAINTS

=head2 C<uuid_unique>

=over 4

=item * L</uuid>

=back

=head1 RELATIONS

=head2 ancestry_children

Type: has_many

Related object: L<Pinto::Schema::Result::Ancestry>

=head2 ancestry_parents

Type: has_many

Related object: L<Pinto::Schema::Result::Ancestry>

=head2 registrations

Type: has_many

Related object: L<Pinto::Schema::Result::Registration>

=head2 stacks

Type: has_many

Related object: L<Pinto::Schema::Result::Stack>

=head1 L<Moose> ROLES APPLIED

=over 4

=item * L<Pinto::Role::Schema::Result>

=back

=head1 AUTHOR

Jeffrey Ryan Thalhammer <jeff@stratopan.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2014 by Jeffrey Ryan Thalhammer.

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

=cut
