/usr/local/CPAN/DBD-Amazon/SQL/Amazon/Request/ItemSearch.pm


#
#   Copyright (c) 2005, Presicient Corp., USA
#
# Permission is granted to use this software according to the terms of the
# Artistic License, as specified in the Perl README file,
# with the exception that commercial redistribution, either 
# electronic or via physical media, as either a standalone package, 
# or incorporated into a third party product, requires prior 
# written approval of the author.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Presicient Corp. reserves the right to provide support for this software
# to individual sites under a separate (possibly fee-based)
# agreement.
#
#	History:
#
#		2005-Jan-27		D. Arnold
#			Coded.
#

package SQL::Amazon::Request::ItemSearch;

use SQL::Amazon::Request::ItemLookup;
use SQL::Amazon::Parser qw(:pred_node_codes);
use base qw(SQL::Amazon::Request::ItemLookup);

use strict;
my %is_power_col = (
'TITLE', [ 'title', 1 ],
'SUBJECT', [ 'subject', undef ],
'AUTHORS', [ 'author', 1 ],
'PUBLISHER', [ 'publisher', 1 ],
'LANGUAGE', [ 'language', undef ],
);
my %is_search_col = (
'TITLE', [ 'Title', '*', 1 ],
'AUTHORS', [ 'Author', 'Books', 1 ],
'PUBLISHER', [ 'Publisher', 'Books', 1 ],
'ARTIST', [ 'Artist', '*', 1 ],
'ACTOR', [ 'Actor', '*', 1 ],
'DIRECTOR', [ 'Director', '*', 1 ],
'MANUFACTURER', [ 'Manufacturer', '*', 1 ],
'MUSICLABEL', [ 'MusicLabel', '*', 1 ],
'COMPOSER', [ 'Composer', '*', 1 ],
'BRAND', [ 'Brand', '*', 1 ],
'CONDUCTOR', [ 'Conductor', '*', 1 ],
'ORCHESTRA', [ 'Orchestra', '*', 1 ],
'CITY', [ 'City', 'Restruants', 1 ],
'CUISINE', [ 'Cuisine', 'Restruants', 1 ],
'NEIGHBORHOOD', [ 'Neighborhood', 'Restruants', 1 ],
'MERCHANTID', [ 'MerchantID', 'Offers', undef ],
'CONDITION', [ 'Condition', 'Offers', undef ],
'DELIVERYMETHOD', [ 'DeliveryMethod', 'Offers', undef ],
);
my %can_power_search = qw(
BOOKS 1
);
my %is_amzn_function = qw(
AMZN_POWER_SEARCH 1
AMZN_IN_ANY 1
AMZN_MATCH_ANY 1
AMZN_MATCH_ALL 1
AMZN_MATCH_TEXT 1
);

our $errstr;

sub errstr {
	return $errstr
		if $errstr && $errstr ne ''; 

	return shift->{_errstr};
}

sub new {
	my $class = shift;
	my $req_attrs = {
		Power => {
			Explicit => [],
			Keywords => [],
			title	=> undef,
			subject	=> [],
			author	=> [],
			publisher	=> undef,
			language	=> undef
		},
		Keywords => [],
	};
	$errstr = undef;
	my $predicate = create_search_request($req_attrs, @_);

	return (undef, undef)
		if $errstr;
	return ($predicate, undef)
		unless scalar keys %$req_attrs;

	my $obj = $class->SUPER::new($req_attrs);

	$obj->{url_params}{Operation} = 'ItemSearch';
	$obj->{req_attrs} = $req_attrs;
	return ($predicate, $obj);
}

sub create_search_request {
	my ($request, $expr, $table, $parser) = @_;
	$request->{SearchIndex} = $table;
	my $conjoins = $expr->[0];
	my @finalcjs = ();
	foreach (@$conjoins) {
		my ($op, $left, $right, $neg) =  @$_;
		$table = uc $table;
		my ($name, $value);
		if ($op eq 'USER_DEFINED') {

			$name = (ref $left ne 'HASH') ?
				$left->name() : $left->{name};

			push (@finalcjs, $_),
			next
				unless $is_amzn_function{uc $name};

			if ($name eq 'AMZN_IN_ANY') {
				$op = 'IN';
				$value = (ref $left ne 'HASH') ?
					$left->args()->{value}[0] :
					$left->{value}{value}[0];
			}
		}

		push (@finalcjs, $_),
		next
			if ($neg && ($op eq 'IN'));
		push (@finalcjs, $_),
		next
			if ((ref $right eq 'HASH') && ($right->{type} eq 'column'));

		$name = ((ref $left eq 'HASH') && ($left->{type} eq 'column')) ?
			$left->{value} :
			($op eq 'IN') ? $value : $name;
		$name = uc $name;
		$table = uc $1
			if ($name=~s/^([A-Z]\w*)\.(\w+)/$2/);
		my $aliases = $parser->{struct}{column_aliases};
		$name = $aliases->{$name}
			if $aliases->{$name};
	
		push (@finalcjs, $_),
		next
			unless ($name=~/^[A-Z]\w*$/i);
		$name = 'ASIN' if ($name eq 'ISBN');

		if ($name eq 'ASIN') {
			push (@finalcjs, $_),
			next
				unless ($table eq 'BOOKS');

			push (@finalcjs, $_),
			next
				if ($op eq '<>');
			$request->{Power}{ASIN} = $right,
			next
				if ($op eq '=');

			push (@finalcjs, $_);
		}
		elsif ($can_power_search{$table} && $is_power_col{$name}) {
			push (@finalcjs, $_),
			next
				if ($op eq '<>');
		
			$errstr = "Invalid range predicate for $name.",
			return undef
				if ($op =~/^[<>]/);

			if (($op eq 'LIKE') || ($op eq 'CLIKE')) {
				push (@finalcjs, $_),
				next
					if $neg;

				unless (defined($request->{Power}{$is_power_col{$name}[0]}) &&
					ref $request->{Power}{$is_power_col{$name}[0]}) {
					$request->{Power}{$is_power_col{$name}[0]} = $right;
				}
				else {
					push @{$request->{Power}{$is_power_col{$name}[0]}}, $right;
				}
				push (@finalcjs, $_)
					if $is_power_col{$name}[1];
			}
			elsif ($is_search_col{$name}) {
				$request->{$is_search_col{$name}[0]} = $right;
				push (@finalcjs, $_)
					if $is_search_col{$name}[2];
			}
			else {
				push (@finalcjs, $_);
			}
		}
		elsif ($name=~/^AMZN_MATCH_(ANY|ALL|TEXT)$/) {
			if ($name eq 'AMZN_MATCH_ANY') {
				$errstr = "Invalid MATCHES predicate for $table table.",
				return undef
					unless ($table eq 'BOOKS');
				push @{$request->{Power}{Keywords}}, $left->args();
			}
			elsif ($name eq 'AMZN_MATCH_ALL') {
				push @{$request->{Keywords}}, $left->args();
			}
			elsif ($name eq 'AMZN_MATCH_TEXT') {
				$request->{TextStream} = $left->args();
			}
		}
		elsif ($name=~/^AMZN_POWER_SEARCH$/) {
			$errstr = "Invalid POWER_SEARCH predicate for $table table.",
			return undef
				unless ($table eq 'BOOKS');
			push @{$request->{Power}{Explicit}}, $left->args();
		}
		elsif ($name eq 'AUDIENCERATING') {
			$request->{AudienceRating}{Values} = $right;
			$request->{AudienceRating}{Operator} = 
				(($op eq 'IN') && $neg) ? 'NOT IN' : $op;
			push (@finalcjs, $_);
		}
		elsif ($name eq 'LISTPRICEAMT') {
			$errstr = 'Invalid predicate: ListPriceAmt not valid with IN/LIKE/CLIKE.',
			return undef
				if (($op eq 'IN') || ($op eq 'LIKE') || ($op eq 'CLIKE'));

			push (@finalcjs, $_),
			next
				if (($op eq '=') || ($op eq '<>'));

			$request->{MaximumPrice} = $right
				if (($op eq '<') || ($op eq '<='));

			$request->{MinimumPrice} = $right
				if (($op eq '>') || ($op eq '>='));
		}
		elsif ($is_search_col{$name}) {
			$errstr = "Unknown column $name for table $table.",
			return undef
				unless (($is_search_col{$name}[1] eq '*') ||
					(uc $is_search_col{$name}[1] eq $table));

			push (@finalcjs, $_),
			next
				if (($op eq '<>') ||
					($op eq 'LIKE') || ($op eq 'CLIKE') ||
					($op eq 'IN'));

			$errstr = "Invalid range expression for $name.",
			return undef
				unless ($op eq '=');

			$request->{$is_search_col{$name}[0]} = $right;
			push (@finalcjs, $_)
				if $is_search_col{$name}[2];
		}
		else {
			push (@finalcjs, $_);
		}
	}
	$expr->[0] = \@finalcjs;
	return $expr;
}
sub create_power_search {
	my ($obj, $stmt) = @_;
	
	$obj->{_errstr} = undef;
	my $power = $obj->{req_attrs}->{Power};
	my $pwrstr = '';
	my $val;
	my @pwrparms = ();
	foreach my $expl (@{$power->{Explicit}}) {
		if (ref $expl eq 'ARRAY') {
			foreach (@$expl) {
				$val = $stmt->get_row_value($_, undef, {});
				push @pwrparms, $val
					if defined($val);
			}
		}
		else {
			$val = $stmt->get_row_value($expl, undef, {});
			push @pwrparms, $val
				if defined($val);
		}
	}
	$pwrstr = '(' . join(') and (', @pwrparms) . ')'
		if scalar @pwrparms;
	@pwrparms = ();
	foreach my $keys (@{$power->{Keywords}}) {
		if (ref $keys eq 'ARRAY') {
			foreach (@$keys) {
				$val = $stmt->get_row_value($_, undef, {});
				push @pwrparms, $val
					if defined($val);
			}
		}
		else {
			$val = $stmt->get_row_value($keys, undef, {});
			push @pwrparms, $val
				if defined($val);
		}
	}
	$pwrstr .= (($pwrstr ne '') ? ' and (keywords: ("' : '(keywords: ("') .
		join('" or "', @pwrparms) . '"))'
		if scalar @pwrparms;
	@pwrparms = ();
	foreach (keys %$power) {
		next if (($_ eq 'Explicit') || ($_ eq 'Keywords'));

		if (ref $power->{$_} eq 'HASH') {
			$val = $stmt->get_row_value($power->{$_}, undef, {});
			next unless defined($val);
			$val=~s/%%/\0/g;
			next if (substr($val,0, 1) eq '%');
			$val=~s/^(.+?)%/$1\*/;
			$val=~s/\0/%/g;
			push @pwrparms, (lc $_) . ': "' . $val . '"'; 
		}
		elsif (ref $power->{$_} eq 'ARRAY') {
			my @vals = ();
			foreach my $pwr (@{$power->{$_}}) {
				my $val = $stmt->get_row_value($pwr, undef, {});
				next unless defined($val);
				$val=~s/%%/\0/g;
				next if (substr($val,0, 1) eq '%');
				$val=~s/^(.+?)%/$1\*/;
				$val=~s/\0/%/g;
				push @vals, $val;
			}
			push @pwrparms, (lc $_) . ': ("'. 
				join('" and "', @vals) . '")'
				if scalar @vals;
		}
	}

	$pwrstr .= (($pwrstr ne '') ? ' and (' : '(') .
		join(') and (', @pwrparms) . ')'
		if scalar @pwrparms;

	$obj->{url_params}{Power} = ($pwrstr eq '') ? undef : $pwrstr;
	
	return $obj;
}

sub populate_request {
	my $obj = shift;
	my ($subid, $locale, $stmt, $max_pages, $resp_group) = @_;

	my $val;
	my $req_attrs = $obj->{req_attrs};
	my $url_params = $obj->{url_params};
	
	$url_params->{ResponseGroup} = $resp_group;
	$obj->{_max_pages} = $max_pages;
	return undef
		if ($req_attrs->{Power} && (! $obj->create_power_search($stmt)));
	foreach (keys %$req_attrs) {
		next if ($_ eq 'Power');

		if ($_ ne 'Keywords') {
			$url_params->{$_} = 
				$stmt->get_row_value(
				(ref $req_attrs->{$_} eq 'ARRAY' ? 
					$req_attrs->{$_}[0] :
					$req_attrs->{$_}), undef, {}),
			next;
		}
		my $keywords = '';
		foreach my $key (@{$req_attrs->{$_}}) {
			$keywords .= $stmt->get_row_value($key, undef, {}),
			next
				unless (ref $key eq 'ARRAY');

			foreach my $keyword (@$key) {
				my $val = $stmt->get_row_value($keyword, undef, {});
				$keywords .= "$val " if defined($val);
			}
		}
		$url_params->{$_} = ($keywords eq '') ? undef : $keywords;
	}
	foreach ('MaximumPrice', 'MinimumPrice') {
		$url_params->{$_} = int($url_params->{$_} * 100)
			if defined($url_params->{$_});
	}
	return $obj->SUPER::populate_request(@_);
}

1;