#------------------------------------------------------------
# Copyright (C) 2006 RoyaltyShare, Inc.   All Rights Reserved
#------------------------------------------------------------
package RPS::SearchCommand;

use URI::Escape;
use lib '/app/tools/common/lib';
use Common::Assert;
use Common::Preference;

use lib '/app/tools/appuser/lib';

use lib '/app/tools/rps/lib';
use RPS::Pagination;
use RPS::AlphabeticalPagination;

use RPS::Command;
use base 'RPS::Command';

use constant kDefaultPageSize => 30;

# jpk - I'm afraid I don't like this implementation very much.
# I don't like the way this code assumes that the way to get the
# collection of items is to take the db class name and tack ::GetAll onto it.
# This is fragile, and introduces a magic hidden dependency between this class
# and every DB::Item class.   In other words, it assumes an interface exists that
# belongs to a class outside this class' scope.   It's bound to cause problems.
#
# I've gone ahead and modified this class somewhat to allow these get and search methods to
# be passed in as arguments, but I still don't like the design very much...
#
# At some point, I'd like to refactor this to be more object-oriented.  Rather that
# use function references (symbolic or otherwise), it would be better to have the
# SearchCommand class provide a set of virtual methods that get called to provide
# these collections - Then classes that descend from SearchCommand could override
# these methods to provide this in anyway they see fit.  That would be a lot more flexible.
#
# !!! Actually, it looks like the DB::Item refactoring I did has accomplished most of this.
# !!! Search and GetAll are now class methods in DB:Item, and those have been refactored to use the '->' notation.
# !!! This allows me to invoke them given just the class name, and allows inheritance to happen
# !!! even in a class method. Slick.
# !!! I might even be able to make the class 'Search' method generic, and move that into
# !!! the base class just like GetAll. Maybe.
#

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

    # Give any inherited methods the first crack at this.
    # (This checks for login and basic access permissions).
    #
    my $response = $self->SUPER::execute();
    return $response if $response;

    my $dbClass  = $args{dbClass};
    my $xmlClass = $args{xmlClass};
    my $xmlTag   = $args{xmlTag};
    my $loadSubs = $args{loadSubs};
    my $pageSize = $self->getParam("PageSize") || $args{pageSize} || kDefaultPageSize;
    my $query    = $self->{query} || $self->getParam('Query');
    my $hParams  = $self->getParams() || {};

    #    $self->{xml}{Params} = $self->{params};

    assert($xmlTag);
    assert($xmlClass);
    eval "require $xmlClass";

    $self->{xml}{PageName} = $self->pageName() if ( $self->can('pageName') );

    # Provide an HTML-encoded version of the query string, so we can easily build links that
    # won't break if there are 'forbidden' characters.
    #
    if ($query) {
        $self->{xml}{EncodedQuery} = URI::Escape::uri_escape_utf8($query);
    }

    # let them pass in a collection of their own
    #
    my $collection;
    if ( $args{collection} ) {
        $collection = $args{collection};
    } else {
        assert( $dbClass, "Missing required dbClass arg" );
        eval "require $dbClass";

        if ($query) {

            # Strip leading and trailing white space
            $query =~ s/^\s+//g;
            $query =~ s/\s+$//g;

            # if(validSearchTerm($searchParam))
            my $searchMethod = 'Search';
            if ( $args{searchMethod} ) {
                $searchMethod = $args{searchMethod};
            }
            if (1) {
                $collection = $dbClass->$searchMethod($query);
            } else {

                # TODO: not sure how to handle this
            }
        } else {
            my $getAllMethod = 'GetAll';
            if ( $args{getAllMethod} ) {
                $getAllMethod = $args{getAllMethod};
            }

            # AppUser includes additional filter data, so we need to pass parameters to the invoked class.
            # Other classes do not expect URL parameters and will fail if they are provided.
            if ( $xmlTag eq 'AppUser' ) {
                $collection = $dbClass->$getAllMethod($hParams);
            } else {
                $collection = $dbClass->$getAllMethod();
            }
        }

    }

    if ( defined $collection ) {
        assert( $xmlClass, "Missing required xmlClass arg" );
        eval "require $xmlClass";

        my (%uniqUserAccessLevelStatus, @uniqUserAccessLevelStatusList);
        if ( $xmlTag eq 'AppUser' ) {
            # Clone the collection object before pagination with all access levels
            my $collectionClone = { %$collection };
            $collectionClone->{_query} =~ s/ua.access_level\s?=\s?\d+/1=1/;
            bless $collectionClone, ref $collection;

            while ( $collectionClone->hasNext() ) {
                my $hData = $collectionClone->next();
                next unless $hData->{access_level};
                $uniqUserAccessLevelStatus{ $hData->{access_level} } = $hData->{disabled};
            }
            @uniqUserAccessLevelStatusList = map {
                { AccessLevelID => $_, Disabled => $uniqUserAccessLevelStatus{$_} }
            } keys %uniqUserAccessLevelStatus;
        }

        # the new way to do this is to set the pagination parameters on the collection,
        # then we can create the pagination object by referencing the "totalSize()" of the collection.
        # By setting the pagination parameters in the collection before referencing any of its
        # internal properties we save it from doing a "select * from"
        #
        if ( $self->getParam("Page") ) {
            $collection->setPagination( pageIndex => $self->getParam("Page"), pageSize => $pageSize );
        } else {
            my $recordIndex = $self->getParam("Record") || 1;
            $collection->setPagination( recordIndex => $recordIndex, pageSize => $pageSize );
        }

        # the pagination occurs internally in the collection object
        #
        my @records;

        Common::Log::Debug($collection);
        while ( $collection->hasNext() ) {
            my $data = $collection->next();
            push @records, $xmlClass->new( _dbItem => $data, loadSubs => $loadSubs );
        }

        $self->{xml}{$xmlTag} = \@records if (@records);
        $self->{xml}{AccessLevelStatusList} = \@uniqUserAccessLevelStatusList;
        $self->{xml}{Pagination} = RPS::Pagination->new(
            total     => $collection->totalSize(),
            recordNum => $collection->{_record},
            pageSize  => $pageSize
        );
    }

    return undef;
}

sub totalCount {
    my $self = shift;

    if ( $self->{xml}->{Pagination} ) {
        return $self->{xml}->{Pagination}->Total;
    } else {
        return 0;
    }
}

sub validSearchTerm {
    my $term = shift;

    # I think it's safe to require at least 3
    # alphanumeric characters
    #
    my $alphaTerm;
    ( $alphaTerm = $term ) =~ s/[^a-zA-Z0-9]//g;
    return 0 if ( length($alphaTerm) < 3 );

    return 1;
}

sub setPagesize {
    my $self = shift;

    # Check if the page size was passed in
    my $pagesize = $self->getParam( $self->pageSizeParamName() );

    # If not see if a cookie exists
    $pagesize = Common::Preference::GetValue( $self->pageSizePrefName() )
      unless ($pagesize);

    # Finally set it to the default
    $pagesize = $self->defaultPageSize() unless ($pagesize);

    # Now set the new page size preference
    my $setMethod = $self->pageSizeMethod();
    &$setMethod( $self->pageSizePrefName(), $pagesize );

    return $pagesize;
}

#
# Overloadable constants for defining the search page preferences
#
sub defaultPageSize   { 25 }
sub pageSizeParamName { 'PageSize' }
sub pageSizePrefName  { 'pagesize' }
sub pageSizeMethod    { 'Common::Preference::SetPersistentCookie' }

###
1;    # Play nicely.
###
