# $Id: Data.pm,v 1.83 2004/02/07 17:43:52 leonardr Exp $
package Clark04::Eblocks::DBI;

use 5.008;
use strict;
use warnings;

use Class::DBI;
use base qw(Class::DBI) ;

our $VERSION = '0.01';
use Clark04::Eblocks::DBConnect;
require Exporter;

my $connstring = "DBI:mysql:database=$DB;host=$DBHOST;port=$DBPORT";
sub getDBH() {
    my $dbh = DBI->connect($connstring, $DBUSER, $DBPASS);
    return $dbh;
}

Clark04::Eblocks::DBI->set_db('Main',$connstring,$DBUSER,$DBPASS);

package Clark04::Eblocks::Data::votercontacts;
use base 'Clark04::Eblocks::DBI';

Clark04::Eblocks::Data::votercontacts->table("votercontacts");
Clark04::Eblocks::Data::votercontacts->columns(Primary => qw/id/);
Clark04::Eblocks::Data::votercontacts->columns(Essential => qw/contactinfo/);

package Clark04::Eblocks::Data::config;
use base 'Clark04::Eblocks::DBI';

Clark04::Eblocks::Data::config->table("vars");
Clark04::Eblocks::Data::config->columns(Primary => qw/name/);
Clark04::Eblocks::Data::config->columns(Essential => qw/value/);
Clark04::Eblocks::Data::config->columns(Other => qw/description type category/);

package Clark04::Eblocks::Data::voters;
use base 'Clark04::Eblocks::DBI';

Clark04::Eblocks::Data::voters->table("voters");
Clark04::Eblocks::Data::voters->columns(All => qw/voterkey statevoterkey honor firstnam midname
		lastname jr madd mcity mstate mzipcode mzip4 phone party gender
		address_assigned address_mailed mailed_at address_notes address_score
                phone_assigned phone_resolution phone_notes phoned_at phone_score hhid/);
Clark04::Eblocks::Data::voters->columns(Primary => qw/voterkey/);

sub prefix {
    my $self = shift;
    my $n = $self->honor();
    if (length($n) > 1) {
	return  ucfirst(lc($n));
    } else {
	return "";
    }
}

sub firstName {
    my $self = shift;
    my $n = $self->firstnam();
    return ucfirst(lc($n));
}

sub middleName {
    my $self = shift;
    my $n = $self->midname();
    return $n ? "$n." : $n;
}


sub lastName {
    my $self = shift;
    my $n = $self->lastname();
    return ucfirst(lc($n));
}

sub phoneNumber {
    my $self = shift;
    my $phone = $self->phone();
    $phone =~ s/(\d{3})(\d{3})(\d{4})/($1) $2-$3/;
    return $phone;
}

sub genderName {
    my $self = shift;
    my $test = uc($self->gender());
    my $gender = 'Unknown';
    if ($test eq 'M') {
	$gender = 'Male';
    } elsif ($test eq 'F') {
	$gender = 'Female';
    }
    return $gender;
}


sub suffix {
    my $self = shift;
    my $n = $self->jr();
    if ($n =~ /^I/) {
	return $n;
    } else {
	return ucfirst(lc($n));
    }
}

sub city {
    my $self = shift;
    return $self->capitalizeParts($self->mcity());
}

sub address {
    my $self = shift;
    return $self->capitalizeParts($self->madd());
}

sub zip {
    my $self = shift;
    my $zip = $self->mzipcode();
    if ($zip) {
	my $zip4 = $self->mzip4();
	if ($zip4) {
	    $zip = "$zip-$zip4";
	}
    }
    return $zip;
}

sub capitalizeParts {
    my $self = shift;
    my $o = lc(shift);
    my @parts = split /[\-' ]/, $o;
    my $n = "";
    foreach my $part (@parts) {
	$n .= ucfirst($part) . ' ';
    }
    chop($n);
    return $n;
}

sub partyName {
    my $self = shift;
    my $parties = {'D' => 'Democratic',
		   'G' => 'Green',
		   'I' => 'Independent',
		   'L' => 'Libertarian',
		   'R' => 'Republican',};
    my $party = $self->party();
    return $parties->{$party} || "Unknown";
}

sub userExceededTimeout {
    my $self = shift;
    my $user = shift;
    my $mode = shift;
    return 0 if $user->hasPermission('eblocks_unlimited');
    my $assignmentField = $mode eq "call" ? "phone_assigned" : "address_assigned";
    my $completionTimeField = $mode eq "call" ? "phoned_at" : "mailed_at";
    my $data = new Clark04::Eblocks::Data();
    my $timeout = $data->getConfVar("eblocks_${mode}_timeout") || 60;
    my $limit = $data->getConfVar("eblocks_${mode}_timeout_limit") || 10;
    my $uid = $user->getUserID();
    
    my $query = "select count(voterkey) count from voters where $assignmentField='$uid' and $completionTimeField > DATE_SUB(NOW(), INTERVAL $timeout MINUTE)";
    my $sth = $self->getDBH()->prepare($query);
    $sth->execute();
    my $row = $sth->fetchrow_hashref();
    return $row->{'count'} >= $limit;
}

sub done {
    my $self = shift;
    my $data = shift;
    my $user = shift;
    my $mode = shift;
    my $notes = shift;
    my $resolution = shift;
    my $err;
    if ($mode eq 'mail') {
	$err = $self->mailed($user, $notes, $data);
    } else {
	$err = $self->called($user, $notes, $resolution, $data);
    }
    return $err;
}

sub mailed {
    my $self = shift;
    my $user = shift;
    my $comments = shift;
    my $data = shift || new Clark04::Eblocks::Data();
    if ($self->userExceededTimeout($user, 'mail')) {
	my $timeout = $data->getConfVar('eblocks_mail_timeout') || 60;
	my $limit = $data->getConfVar('eblocks_mail_timeout_limit') || 10;
	return qq{Sorry, but your account is limited to completing $limit mailings every $timeout minutes.<p>If you need to be able to mark voters complete more quickly, please send mail to <a href="mailto:accounts\@forclark.com">accounts\@forclark.com</a>.};
    }
    if ($self->address_assigned() ne $user->getUserID()) {
	return "You wrote a voter who wasn't in your eBlock?";
    }
    if ($self->address_mailed()) {
	return "You already sent that voter a letter.";
    }
    $self->address_notes($comments);
    $self->address_mailed('Y');
    $self->mailed_at($self->now());
    $self->update();
    $self->deactivateOtherHouseholdMembers($data, 'mail');
    if ($data) {
	$data->incMailTotals($user->getUserID());
    }
    return 0;
}

sub called {
    #TODO: Make sure the user isn't exceeding their quota
    
    my $self = shift;
    my $user = shift;
    my $comments = shift;
    my $resolution = shift;
    my $data = shift || new Clark04::Eblocks::Data();
    if ($self->phone_assigned() ne $user->getUserID()) {
	return "You called a voter who wasn't in your eBlock?";
    }
    if ($self->phone_resolution()) {
	return "You already called that voter.";
    }
    if ($self->userExceededTimeout($user, 'call')) {
	my $timeout = $data->getConfVar('eblocks_call_timeout') || 60;
	my $limit = $data->getConfVar('eblocks_call_timeout_limit') || 10;
	return qq{Sorry, but your account is limited to completing $limit phone calls every $timeout minutes.<p>If you need to be able to mark voters complete more quickly, please send mail to <a href="mailto:accounts\@forclark.com">accounts\@forclark.com</a>.};
    }
    $self->phone_notes($comments);
    $self->phone_resolution($resolution);
    $self->phoned_at($self->now());
    $self->update();
    $self->deactivateOtherHouseholdMembers($data, 'call');
    if ($data) {
	$data->incCallTotals($user->getUserID());
    }
    return 0;
}

sub deactivateOtherHouseholdMembers {
    my $self = shift;
    my $data = shift;
    my $mode = shift || 'mail';
    my $hhid = $self->hhid();
    if ($hhid) {
	my $state = $self->mstate();
	my $voterkey = $self->voterkey();
	my $assignmentField = $data->getAssignmentColumn($mode);
	my $query = qq{update voters set $assignmentField=-2 where mstate="$state" and hhid=$hhid and voterkey != $voterkey};
	Clark04::Eblocks::DBI->getDBH()->do($query);
    }
}

sub getPollingLocation() {
    my $self = shift;
    $self->_retrieveSupplementalVoterTableValue('poll_site');
}

sub getPollingStreet() {
    my $self = shift;
    $self->_retrieveSupplementalVoterTableValue('poll_street');
}

sub getPreviousCallID() {
    my $self = shift;
    $self->_retrieveSupplementalVoterTableValue('candidate_1');
}

sub _retrieveSupplementalVoterTableValue() {
    my $self = shift;
    my $field = shift;
    my $mstate = $self->mstate();
    my $statevoterkey = $self->statevoterkey();
    my $query = "select $field from voters_supplemental where mstate='$mstate' and statevoterkey=$statevoterkey";
    my $sth = $self->getDBH()->prepare($query);
    $sth->execute();
    my $row = $sth->fetchrow_hashref();
    return $row->{$field};
}

sub now {
    my $self = shift;
    my $query = "select now() as date";
    my $sth = $self->getDBH()->prepare($query);
    $sth->execute();
    my $row = $sth->fetchrow_hashref();
    return $row->{'date'};
}

package Clark04::Eblocks::Data::totals;
use base 'Clark04::Eblocks::DBI';

Clark04::Eblocks::Data::totals->table("userprefs");
Clark04::Eblocks::Data::totals->columns(All => qw/uid prefname prefvalue/);
Clark04::Eblocks::Data::totals->columns(Primary => qw/uid prefname/);

sub getTotalsCommon {
	my $class = shift;
	my $pref = shift || die "No prefname specified";
	my $uid = shift;
#print STDOUT "pref: $pref uid: $uid\n";
	return if (! defined($uid));
	my $ret = Clark04::Eblocks::Data::totals->retrieve((uid => $uid, prefname => $pref));
}

package Clark04::Eblocks::Data;

our $call_totals = 'eblocks_calls';
our $mail_totals = 'eblocks_letters';

sub new {
	my $type = shift;
	my $self = bless {}, $type;
	return $self;
}

our $stateData = {
 'MI' => { 'name' => 'Michigan', 'primaryDate' => 'February 7th',
	   'primaryTime' => 1076108400,
           'bush2000' => 0, 'offset' => -5, caucus => 1},
 'WA' => { 'name' => 'Washington', 'primaryDate' => 'February 7th',
	   'primaryTime' => 1076108400,
           'bush2000' => 0, 'offset' => -7, 'caucus' => 1},
 'ME' => { 'name' => 'Maine', 'primaryDate' => 'February 8th',
	   'primaryTime' => 1076194800,
           'bush2000' => 0, 'offset' => -5, 'caucus' => 0},
 'TN' => { 'name' => 'Tennessee', 'primaryDate' => 'February 10th',
	   'primaryTime' => 1076367600,  
           'bush2000' => 1, 'easternmostOffset' => -5, 'westernmostOffset' => -6},
 'VA' => { 'name' => 'Virginia', 'primaryDate' => 'February 10th',
	   'primaryTime' => 1076367600,
           'bush2000' => 1, 'offset' => -5},
 'WI' => { 'name' => 'Wisconsin', 'primaryDate' => 'February 17th',
	   'primaryTime' => 1076972400,
           'bush2000' => 0, 'offset' => -6},
 'HI' => { 'name' => 'Hawaii', 'primaryDate' => 'February 24th',
	   'primaryTime' => 1077577200,
           'bush2000' => 0, 'offset' => -10, 'caucus' => 1},
};

our $areaCodeTimezoneData = {
    423 => -5,
    865 => -5,
    'default' => -6
};

sub getStateData {
    my $self = shift;
    my $state = uc(shift);
    my $data;
    if ($stateData->{$state}) {
	$data = $stateData->{$state};
	$data->{abbr} = $state;
    } else {
	$data = { 'abbr' => '', 'name' => '[state name]', 'primaryDate' => '[date of primary]', 'bush2000' => undef};
    }
    return $data;
}

sub getTotal {
    my $self = shift;
    my $mode = shift;
    my $state = uc(shift) || undef;
    my $varname = "eblocks_total_$mode";
    if ($state) {
	$varname .= "_$state";
    }
    return $self->getConfVar($varname);
}

sub hoursUntilPrimaryDate {
    my $hour = 60 * 60;
    my $self = shift;
    my $state = shift;
    my $stateData = $self->getStateData($state);
    my $systemOffset = -5 * $hour; #FIXME: This is a variable, obviously
    my $stateOffset = $stateData->{offset} * $hour;
    my $localOffset += ($systemOffset - $stateOffset);
    my $now = time() + $systemOffset;
    my $startTime = $stateData->{primaryTime} || $now;
    return ($startTime - $now) / $hour;
}

sub getOffsetPhoneHour {
    my $self = shift;
    my $state = shift;
    my $number = shift || undef;
    my $when = shift || 'start';
    my $optimal = shift || '';
    if ($optimal) {
	$optimal = 'optimal_'
    }

    my $systemOffset = -5; #FIXME: Oh no, not again.
    #print "<!--System offset $systemOffset-->\n";
    my $default = $when eq 'start' ? 9 : 22;
    my $startTime = $self->getConfVar("eblocks_phone_$optimal$when") || $default;
    #print "<!--$when time obtained from eblocks_phone_$optimal$when with backup $default: $startTime-->\n";

    my $stateOffset = undef;
    if ($number) {
	$stateOffset = $areaCodeTimezoneData->{substr($number, 0, 3)} || $areaCodeTimezoneData->{default};
	#print "<!--Using area code offset: $stateOffset-->\n";
    }
    if ($state && $stateOffset == undef) {	
	my $key = $when eq 'start' ? 'easternmostOffset' : 'westernmostOffset';
	$stateOffset = $stateData->{$state}->{$key} || $stateData->{$state}->{offset};
	#print "<!--Using state offset obtained from $key: $stateOffset-->\n";
    }

    my $offsetDifference = $systemOffset - $stateOffset;
    #print "<!--Offset difference: $offsetDifference-->\n";
    #print "<!--system-local $when time: " . ($startTime + $offsetDifference) . "-->\n";
    return $startTime + $offsetDifference;
}

sub inStateCallTime {
    my $self = shift;
    my $state = shift;
    my $number = shift || undef;
    my $optimal = shift || 0;
    my @time = localtime();
    ## see http://www.perldoc.com/perl5.8.0/pod/func/localtime.html
    ## for time output
    my $earliestHour = $self->getOffsetPhoneHour($state, $number, 'start', $optimal);
    my $latestHour = $self->getOffsetPhoneHour($state, $number, 'end', $optimal);
    #print "<!--Checking $earliestHour <= $time[2] < $latestHour-->\n";
    return ($time[2] >= $earliestHour && $time[2] < $latestHour);

}

sub chunkSizesForUser {
    my $self = shift;
    my $user = shift;
    my @sizes = (10);
    if ($user->hasPermission("eblocks_unlimited")) {
	push(@sizes, 30);
	push(@sizes, 50);
	push(@sizes, 100);
    }
    if ($user->hasPermission("eblocks_admin") || $user->hasPermission("eblocks_trusted")) {
	push(@sizes, 200);
	push(@sizes, 250);
	push(@sizes, 1000);
    }
    return \@sizes;
}

sub maxChunkSizeForUser {
    my $self = shift;
    my $user = shift;
    my @arr = @{$self->chunkSizesForUser($user)};
    return $arr[$#arr];
}

sub inStateOptimalCallTime {
    my $self = shift;
    my $state = shift;
    return $self->inStateCallTime($state, 1);
}

sub inWeekend {
    my $self = shift;
    my @time = localtime();
    return ( $time[6] == 0 || $time[6] == 6);
}

sub queryString {
    my $self = shift;
    my $mode = shift;
    my $state = shift;
    my $volunteerState = shift || '';
    my $format = shift || '';
    my $formatStr = '';
    if ($format) {
	$formatStr = "&format=$format";
    }
    my $stateStr = '';
    if ($volunteerState) {
	$stateStr = "&volunteerState=$volunteerState";
    } elsif ($state) {
	$stateStr = "&state=$state";
    } 
    return "mode=$mode$stateStr$formatStr";
}

sub getConfVar {
	my $self = shift;
	my $vname = shift || die "No varname specified";
	my $var = Clark04::Eblocks::Data::config->retrieve($vname);
	return if (! defined($var));
	return $var->value();
}

#Get the designated voter contact for this user.
sub getVoterContact {
    my $self = shift;
    my $uid = shift || die 'No user ID specified';
    my $contactInfo = '';
    my $contactID = $self->getPrefValue($uid, "eblocks_voter_contact");
    if ($contactID) {
	$contactInfo = Clark04::Eblocks::Data::votercontacts->retrieve($contactID);
    }
    if (!$contactInfo) {
	my $query = 'select max(id) as maxid from votercontacts';
	my $sth = Clark04::Eblocks::DBI->getDBH()->prepare($query);
	$sth->execute();
	my $row = $sth->fetchrow_hashref();
	my $maxID = $row->{'maxid'};
 	$contactID = int(rand($maxID+1));
	$contactInfo = Clark04::Eblocks::Data::votercontacts->retrieve($contactID);
	$self->setPrefValue($uid, "eblocks_voter_contact", $contactID);
    }
    return $contactInfo->{contactinfo};
}

# managing dealing with the backend totals objects in a parsimonious way
sub getCallTotals {
	my $self = shift;
	my $uid = shift || die "No uid specified";
	my $pref = Clark04::Eblocks::Data::totals->getTotalsCommon($call_totals, $uid);
	return defined($pref) ? $pref->prefvalue() : 0;
}
sub getMailTotals {
	my $self = shift;
	my $uid = shift || die "No uid specified";
	my $pref = Clark04::Eblocks::Data::totals->getTotalsCommon($mail_totals, $uid);
	return defined($pref) ? $pref->prefvalue() : 0;
}

sub getPrefValue {
    my $value = '';
    my $self = shift;
    my $uid = shift || die "No uid specified";
    my $prefname = shift || die "No prefname specified";
    my $pref = Clark04::Eblocks::Data::totals->retrieve((uid => $uid, prefname => $prefname));
    if ($pref) {
	$value = $pref->prefvalue();
    }
    return $value;
}

sub setPrefValue {
    my $self = shift;
    my $uid = shift || die "No uid specified";
    my $prefname = shift || die "No prefname specified";
    my $prefvalue = shift || '';
    my $pref = Clark04::Eblocks::Data::totals->retrieve((uid => $uid, prefname => $prefname));
    my $qry1;
    if (!$pref) {
	$qry1 = "insert into userprefs values ($uid, '$prefname', '$prefvalue')";
    } else {
	$qry1 = "update userprefs set prefvalue='$prefvalue' where uid=$uid and prefname='$prefname'";
    }
    Clark04::Eblocks::DBI->getDBH()->do($qry1);
    return $prefvalue;
}

sub incTotalsCommon {
    my $self = shift;    
    my $uid = shift || die "No uid specified";
    my $prefname = shift || die "No prefname specified";
    my $tot = Clark04::Eblocks::Data::totals->getTotalsCommon($prefname, $uid);
    if (!$tot) {
	$tot = Clark04::Eblocks::Data::totals->create({uid => $uid,
						       prefname => $prefname,
						       prefvalue => 1});
    } else {
	$tot->prefvalue($tot->prefvalue()+1);
    }   
    $tot->update();
    return $tot->prefvalue();
}

sub incCallTotals {
    my $self = shift;
    my $uid = shift || die "No uid specified";
    $self->incTotalsCommon($uid, $call_totals);
}

sub incMailTotals {
    my $self = shift;
    my $uid = shift || die "No uid specified";
    $self->incTotalsCommon($uid, $mail_totals);
}

sub userCanCompleteInBulk {
    my $self = shift;
    my $user = shift;
    my $mode = shift;
    return $user->hasPermission('eblocks_unlimited');
}

sub userIsTrusted {
    my $self = shift;
    my $user = shift;
    return $user->hasPermission('eblocks_trusted') || $user->hasPermission('eblocks_admin');
}

# return an individual voter, specified by key
sub getVoterByKey {
	my $self = shift;
	my $voter_key = shift;
	return Clark04::Eblocks::Data::voters->retrieve(voterkey => $voter_key);
}

sub expiryTime {
    my $self = shift;
    my $user = shift;
    my $mode = shift || 'mail';
    my $uid = $user->getUserID();
    my $assigned_at_field = $self->getAssignmentDateColumn($mode);
    my $assigned_field = $self->getAssignmentColumn($mode);
    my $completion_date_field = $self->getResolutionDateColumn($mode);
    my $query = "select date_add(min($assigned_at_field), interval 72 hour) as expiry from voters where $assigned_field=$uid and $completion_date_field is null";
    my $sth = Clark04::Eblocks::DBI->getDBH()->prepare($query);
    $sth->execute();
    my $row = $sth->fetchrow_hashref();
    return $row->{'expiry'};
}

sub getAssignmentColumn {
    my $self = shift;
    my $purpose = shift || 'mail';
    return $purpose eq 'mail' ? 'address_assigned' : 'phone_assigned';
}

sub getAssignmentDateColumn {
    my $self = shift;
    my $purpose = shift || 'mail';
    return $purpose eq 'mail' ? 'address_assigned_at' : 'phone_assigned_at';
}

sub getResolutionColumn {
    my $self = shift;
    my $purpose = shift || 'mail';
    return $purpose eq 'mail' ? 'address_mailed' : 'phone_resolution';
}

sub getResolutionDateColumn {
    my $self = shift;
    my $purpose = shift || 'mail';
    return $purpose eq 'mail' ? 'mailed_at' : 'phoned_at';
}

sub getScoreColumn {
    my $self = shift;
    my $purpose = shift || 'mail';
    return $purpose eq 'mail' ? 'address_score' : 'phone_score';
}

# Clears the given user's currently assigned chunk of voters.
sub clearVoters {
    my $self = shift;
    my $user = shift || die "No user specified";
    my $mode = shift || '';
    my $state = shift || '';
    my $gender = shift || '';
    my $uid = $user->getUserID();
    foreach my $purpose ('call', 'mail') {
	if (!$mode || $mode eq $purpose) {
	    my $assignment = $self->getAssignmentColumn($purpose);
	    my $assignmentDate = $self->getAssignmentDateColumn($purpose);
	    my $resolution = $self->getResolutionColumn($purpose);
	    my $stateWhere = '';
	    if ($state) {
		$stateWhere = " and mstate='$state'";
	    }
	    my $qry1 = "UPDATE voters SET $assignment = -1, $assignmentDate = null WHERE $assignment = $uid $stateWhere and $resolution = ''";
	    #print $qry1;
	    Clark04::Eblocks::DBI->getDBH()->do($qry1);
	}
    }
}

sub getFirstVoter {
    my $self = shift;
    my $purpose = shift || die "No purpose specified (call or mail)";
    my $user = shift || die "No user specified";
    my $state = shift || die "No state specified";
    return $self->getChunkOfVoters($purpose, 10, $user, $state)->[0];
}

# get chunks of voters - if the caller already has some assigned, returne them
# else return a newly assigned chunk, pending quotes
sub getChunkOfVoters {
    my $self = shift;
    my $purpose = shift || die "No purpose specified (call or mail)";
    my $qty = shift || die "No quantity supplied";
    my $user = shift || die "No user specified";
    my $state = shift || die "No state specified";
    my $city = shift || '';
    my $gender = shift || '';
    my $community = shift || '';
    my $priorityMin = shift || '';
    my $priorityMax = shift || '';

    #Priority selection is only available to admins.
    if (!$user->hasPermission('edit_user')) {
	$priorityMin = undef;
	$priorityMax = undef;
    }

    my $assignment = $self->getAssignmentColumn($purpose);
    my $assignmentDate = $self->getAssignmentDateColumn($purpose);
    
    my $uid = $user->getUserID();
    my $dbh = Clark04::Eblocks::DBI->getDBH();
    if ($city) {
	$city = $dbh->quote($city);
    }
    if ($gender) {
	$gender = $dbh->quote($gender);
    }
    if ($community) {
	$community = $dbh->quote('%,' . $community . '%');
    }

    my @voters = @{$self->_getAssignedVoters($uid, $purpose, $state)};
    my $assignedSoFar = scalar(@voters);
    if (!$assignedSoFar) { 
        #They have no block at all. Find them one, from approximately
        #the set we want to give them, without any ORDER BY (which
        #takes an enormous amount of time).
	my $maxPriority = $user->hasPermission('eblocks_admin') ? 1000 : ($user->hasPermission('eblocks_unlimited') ? 5 : 1);
	my $priority = $maxPriority;
	my $onlyContactableThroughThisMedium = 0;
	# in order for a record to be conidered having valid data,
	# these columns must match this criteria       
	my $valid_cols;
	if ($purpose eq 'call') {
	    $valid_cols = nonEmptyCol('PHONE');		
	} else {
	    $valid_cols = nonEmptyCol('MADD', 'MCITY', 'MSTATE', 'MZIPCODE');
	}	
	my $restrictAlphabetically = 1;
	while ($priority >= 0 && $assignedSoFar < $qty) {
	    #We haven't filled the block and we're not below zero priority.
	    my $where = '';
	    
	    #Assign them some voters;
	    my $scoreField = $self->getScoreColumn($purpose);
	    if ($priority == 0) {
		$where .= "$scoreField = 0";
	    } else {
		$where .= "$scoreField >= $priority";
		if (!$user->hasPermission('eblocks_admin')) {
		    $where .= " and $scoreField < 1000";
		}
	    }
	    if ($onlyContactableThroughThisMedium) {
	    	if ($purpose eq 'mail') {
	    	    $where .= ' AND phone = ""';
	    	} else {
	    	    $where .= ' and madd = ""';
	    	}
	    } 
	    if ($state) {
		$where .= " AND mstate = '$state'";
	    }
	    if ($gender) {
		$where .= " AND gender = $gender";
	    }
	    if ($community) {
		$where .= " AND community like $community";
	    }
	    if ($city) {
		$where .= " AND mcity = $city";
	    }
	    if ($restrictAlphabetically) {
		my $alphabet = 'abcdefghijklmnopqrstuvwxy';
		my $pos = int(rand(length($alphabet)));
		my $letter = substr($alphabet, $pos, 1);	    
		$where .= " AND lastname > '$letter'";
	    }

	    if ($priorityMin) {
		$where .= " AND $scoreField >= $priorityMin";
	    }
	    if ($priorityMax) {
		$where .= " AND $scoreField <= $priorityMax";
	    }
	    # this query actually assigns the people to this particular user
	    my $uid = $user->getUserID();
	    my $limit = $qty-$assignedSoFar;
	    my $findQuery = "select voterkey from voters where $assignment = -1 AND $valid_cols AND $where LIMIT $limit";
	    print "<!--$findQuery-->\n";
	    my @found = ();
	    my $sth = $dbh->prepare($findQuery);
	    $sth->execute();
	    while (my @row = $sth->fetchrow()) {
		push(@found, $row[0]);
	    }
	    if (scalar(@found)) {
		my $qry1 = "update voters set $assignment=$uid, $assignmentDate=now() where $assignment=-1 AND voterkey in (" . join(',', @found) . ");";
		$dbh->do($qry1);
		@voters = @{$self->_getAssignedVoters($uid, $purpose, $state)};
		$assignedSoFar = scalar(@voters);
	    }

	    if ($assignedSoFar < $qty) {		
		my $decrement = 1;
		if ($restrictAlphabetically 
		    && $onlyContactableThroughThisMedium == 0 
		    && $priority == 0) {
		    #We've gone through every possible combination and
                    #we still don't have enough voters. Stop applying
                    #the alphabet restriction.
		    $restrictAlphabetically = 0;
		    $onlyContactableThroughThisMedium = 1;
		    $priority = $maxPriority;
		    $decrement = 0;
		} elsif ($onlyContactableThroughThisMedium) {
		    #Try getting voters at this same priority but
		    #don't limit to people who can only be contacted
                    #through this medium.
		    $onlyContactableThroughThisMedium = 0;		    
		    $decrement = 0;
		} else {
		    $onlyContactableThroughThisMedium = 1;
		}
		if ($decrement) {		    
		    if ($priority == 1000) {
			$priority = 5;
		    } else {
			$priority--;
		    }
		}
	    }
	}    
    }
    return \@voters;
}

sub _getAssignedVoters {
    my $self = shift;
    my $uid = shift;
    my $purpose = shift;
    my $state = shift;
    my $assignment = $self->getAssignmentColumn($purpose);
    my $done_col = $self->getResolutionColumn($purpose);
    my @voters = Clark04::Eblocks::Data::voters->search($assignment => $uid,
							$done_col => '',
							mstate => $state);
    return \@voters;
}

# just a quickie to refactor some string formatting code for queries
sub nonEmptyCol {
	my @str = ();
	foreach my $col (@_) {
		push @str, "$col IS NOT NULL AND $col <> ''";
	}
	return join(' AND ', @str);
}

# Autoload methods go after =cut, and are processed by the autosplit program.

1;
__END__
# Below is stub documentation for your module. You'd better edit it!

=head1 NAME

Clark04::Eblocks::Data - Data associated with the Eblocks project

=head1 SYNOPSIS

  use Clark04::Eblocks::Data;
  my $eb_data = new Clark04::Eblocks::Data();

  my $val = $eb_data->getVar("var_name");

=head1 ABSTRACT

  This should be the abstract for Clark04::Eblocks.
  The abstract is used when making PPD (Perl Package Description) files.
  If you don't want an ABSTRACT you should also edit Makefile.PL to
  remove the ABSTRACT_FROM option.

=head1 DESCRIPTION

Clark04::Eblocks::Data is the repository of several types of data for
the Eblocks application.  It allows you to read global configuration
variables as set in the database.

=head2 EXPORT

None by default.



=head1 SEE ALSO

Mention other useful documentation such as the documentation of
related modules or operating system documentation (such as man pages
in UNIX), or any relevant external documentation such as RFCs or
standards.

If you have a mailing list set up for your module, mention it here.

If you have a web site set up for your module, mention it here.

=head1 AUTHOR

Dave Steinberg <lt>dave@redterror.net<gt>

=head1 COPYRIGHT AND LICENSE

Copyright 2003 by Dave Steinberg

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

=cut
