package Common::DB::ItemCollection;

use strict;
use warnings;
use Data::Dumper;
use Carp;
use lib '/app/tools/common/lib';
use Common::Log;
use Common::RSDB;
use Common::RSApp;
use Common::Assert;
use Common::DB::Item;

use constant kClientDB => 1;
use constant kCommonDB => 2;

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

    my $self = bless {}, $class;

    # For debugging purposes, I want to know the name of the function who called this constructor.
    #
    $args{callingFunc} = ( caller(1) )[3];

    return $self->_init(%args);
}

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

    # JPK - I may re-do this interface for now.
    # I am trying to think of a super-clever way to use
    # the config hash, but as this point I am not coming up
    # with anything.
    #
    # So, for now, I'll simply have the DBItem classes
    # pass the _actual sql_ statement and db id to this class.
    # I can probably put some mechanisms for generating
    # the queries into the DBItem base class...
    #
    assert( exists $args{class} );
    assert( exists $args{query} );
    assert( exists $args{dbID} );

    $self->{_containedClass} = $args{class};
    $self->{_query}          = $args{query};
    $self->{_placeHolders}   = $args{placeHolders};    # NEW! -jff-
    $self->{_dbID}           = $args{dbID};
    $self->{_caller}         = $args{callingFunc};

    $self->{_loaded} = 0;

    return $self;
}

# takes two args: page and pageSize.
# note that page _is_ 0-based (page 0 is the first page)
#
sub setPagination {
    my ( $self, %args ) = @_;

    # jpk - for now, this is an error.
    # we may want in the future to allow a collection to be 'reset'
    #
    assert( !$self->_isPaginated() );
    assert( defined $args{pageIndex} || defined $args{recordIndex} );
    assert( $args{pageSize} );

    $args{recordIndex} = 1 if ( $args{recordIndex} && $args{recordIndex} < 1 );
    $args{pageIndex}   = 1 if ( $args{pageIndex}   && $args{pageIndex} < 1 );

    if ( $args{recordIndex} ) {
        $self->{_record} = $args{recordIndex};
        $self->{_page}   = int( $args{recordIndex} / $args{pageSize} ) + 1;

    } elsif ( $args{pageIndex} ) {
        $self->{_record} = ( ( $args{pageIndex} - 1 ) * $args{pageSize} ) + 1;
        $self->{_page} = $args{pageIndex};
    } else {
        $self->{_record} = 1;
        $self->{_page}   = 1;
    }

    $self->{_pageSize} = $args{pageSize};
}

sub page {
    my ($self) = @_;
    return $self->{_page};
}

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

    return 1 unless $self->_isPaginated();

    my $numPages = int( $self->totalSize() / $self->pageSize() );

    if ( $self->totalSize() % $self->pageSize() > 0 ) {
        $numPages++;
    }
    return $numPages;
}

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

    return $self->totalSize() unless $self->_isPaginated();
    return $self->{_pageSize};
}

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

    return $self->{_pageSize};
}

sub _load {
    my ($self) = @_;
    return if $self->{_loaded};

    # Pull in the contained class and get its config hash.
    #
    my $class = $self->{_containedClass};
    eval "require $class";
    die $@ if $@;

    # Get the proper database object.
    #
    my $dbID = $self->{_dbID};

    my $dbo;
    if ( kCommonDB == $dbID ) {
        $dbo = Common::RSApp->GetCommonDB();
    } elsif ( kClientDB == $dbID ) {
        $dbo = Common::RSApp->GetClientDB();
    } else {
        die "Error: dbID $dbID unknown";
    }

    my $sql = $self->{_query};

    if ( $self->_isPaginated() ) {

        # Going to need to determine how many rows the original query
        # would have returned.
        my $countSql = $sql;

        $countSql = "SELECT COUNT(*) FROM ( $countSql ) as subq";

        # Execute this statement, get the count.
        #
        my $countSth;
        if ( ref( $self->{_placeHolders} ) eq 'ARRAY' ) {
            $countSth = $dbo->DoCmdWithPlaceholders( $countSql, $self->{_placeHolders} );
        } else {
            $countSth = $dbo->DoCmd($countSql);
        }
        my $countHref = $countSth->fetchrow_hashref();
        $self->{_totalSize} = $countHref->{'COUNT(*)'};

        my $offset = $self->{_record} - 1;
        if ( $offset < 0 || $offset >= $self->{_totalSize} ) {
            $offset = 0;
            $self->{_page} = 1;
        }

        # Now modify the original sql, adding the limit statement.
        #
        $sql .= " LIMIT " . $self->{_pageSize} . " OFFSET " . $offset;
    }

    # Perform the query!
    #
    my $sth;
    eval {
        if ( ref( $self->{_placeHolders} ) eq 'ARRAY' ) {
            $sth = $dbo->DoCmdWithPlaceholders( $sql, $self->{_placeHolders} );
        } else {
            $sth = $dbo->DoCmd($sql);
        }
    };
    if ($@) {
        confess $@;
    }
    $self->{_sth} = $sth;

    $self->{_size} = $self->{_sth}->rows;

    # Fetch the first response, which we'll hold onto.
    #
    $self->{_href} = $self->{_sth}->fetchrow_hashref();

    $self->{_loaded} = 1;
}

sub hasNext {
    my ($self) = @_;
    $self->_load();
    return ( defined $self->{_href} );
}

sub next {
    my ($self) = @_;
    $self->_load();
    return undef unless defined $self->{_href};

    my $class = $self->{_containedClass};
    my $obj;
    if ( $class eq ref $self ) {
        $obj = Common::DB::Item->Lookup( hash => $self->{_href} );
    } else {
        $obj = $class->Lookup( hash => $self->{_href} );
    }

    $self->{_href} = $self->{_sth}->fetchrow_hashref();

    return $obj;
}

# this will let us iterate through the results
# without creating a DB object (useful for paginated results)
#
sub skip {
    my ($self) = @_;
    $self->_load();

    $self->{_href} = $self->{_sth}->fetchrow_hashref();
}

sub size {
    my ($self) = @_;
    $self->_load();
    return $self->{_size};
}

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

    if ( !$self->_isPaginated() ) {
        return $self->size();
    }
    $self->_load();
    return $self->{_totalSize};
}

1;
