package Net::Amazon::MechanicalTurk::Command::UpdateHITs;
use strict;
use warnings;
use Carp;
use IO::File;
use Net::Amazon::MechanicalTurk::BulkSupport;
use Net::Amazon::MechanicalTurk::DataStructure;
use Net::Amazon::MechanicalTurk::RowData;
use Net::Amazon::MechanicalTurk::Properties;
use Net::Amazon::MechanicalTurk::Template;
use Net::Amazon::MechanicalTurk::Template::ReplacementTemplate;
use Net::Amazon::MechanicalTurk::DelimitedWriter;

our $VERSION = '1.00';

=head1 NAME

Net::Amazon::MechanicalTurk::Command::UpdateHITs - Bulk HIT update support for Amazon Mechancial Turk.

This module adds the updateHITs method to the Net::Amazon::MechanicalTurk class.

=head1 SYNOPSIS

    my $properties = {
        Title       => 'UpdateHITs Perl sample 2',
        Description => 'This is a test of the bulk updating API.',
        Keywords    => 'UpdateHITs, bulkload, perl',
        Reward => {
            CurrencyCode => 'USD',
            Amount       => 0.02
        },
        RequesterAnnotation         => 'test',
        AssignmentDurationInSeconds => 60 * 60,
        AutoApprovalDelayInSeconds  => 60 * 60 * 10,
    };
    
    my $mturk = Net::Amazon::MechanicalTurk->new;
    
    $mturk->updateHITs(
        properties => $properties,
        input      => "updatehits-input.csv",
        progress   => \*STDOUT,
        success    => "updatehits-success.csv",
        fail       => "updatehits-failure.csv"
    );

=head1 C<updateHITs>

updateHITs

Bulk updates many hits to have a new hit type in Mechanical Turk.
The method takes a set of properties used to create a HITType and its
associated HITs.  HITIds are pulled from an input source.  For each row
in the input source, the HIT it identifies is modified to have the
properties specified.

updateHITs takes a hash reference or a hash with the following parameters:

  
    properties - (required) Either a hash reference or the name of a file,
                 containing the properties to use for generating a HITType
                 and the associated HITs.  When the properties are read from
                 a file, the method
                 Net::Amazon::MechanicalTurk::Properties->readNestedData is
                 used.
                 
    input      - (required) The input source for row data.
                 This parameter may be of the following types:
                    - Net::Amazon::MechanicalTurk::RowData
                    - An array of hashes.
                      (This is internally converted into an object of type:
                       Net::Amazon::MechanicalTurk::RowData::ArrayHashRowData)
                    - A reference to a subroutine.  When the updateHITs method
                      asks for row data, the subroutine will be called and
                      passed a subroutine reference, which should be called
                      for every row generated by the input.  The generated row
                      should be a hash reference.
                      (This is internally converted into an object of type
                       Net::Amazon::MechanicalTurk::RowData::SubroutineRowData)
                    - The name of a file.  The file should be either a CSV or
                      tab delimited file.  If the file name ends with '.csv',
                      it will read as a CSV, otherwise it is assumed to be
                      tab delimited. The first row in the file should contain
                      the column names.  Each subsequence row becomes a hash
                      reference based on the column names.
                      (This is internally converted into an object of type
                       Net::Amazon::MechanicalTurk::RowData::DelimitedRowData)
              
    progress   - (optional) Used to display progress messages.  This
                 parameter may be of the following types:
                    - A subroutine. The subroutine is called with 1 parameter,
                      a message to be displayed.
                    - An IO::Handle. The progress message is written to the
                      handle.
    
    success    - (optional) Used to handle a successfully created hit.  This
                 parameter may be of the following types:
                    - A filename. HITId's and HITTypeId's will be written to
                      this file.  The file will be in a delimited format,
                      with the first row containing column headers. If the
                      filename ends in ".csv" the file format will be CSV,
                      otherwise it will be tab delimited.
                    - A subroutine. The subroutine is called when a hit is
                      created and passed a hash with the following parameters:
                          - mturk      - A handle to the mturk client.
                          - row        - The input row the hit was created
                                         from.
                          - HITId      - The HITId of the updated HIT.
                          - HITTypeId  - The HITTypeId the updated HIT was
                                         assigned to.
                    
    fail       - (optional) Used to handle a hit which failed creation.  If
                 this value is not specified and a hit fails creation, an
                 error will be raised. This value may be of the following
                 types:
                    - A filename. The input row will be written back to the
                      file in a delimited format. If the file name ends with
                      ".csv", then the file will be in CSV format, otherwise
                      it will be in a tab delimited format.
                    - A subroutine.  The subroutine will be called back with
                      a hash containing the following values:
                          - mturk      - A handle to the mturk client.
                          - row        - The input row the hit was created
                                         from.
                          - HITId      - The HITId that was to be updated 
                          - HITTypeId  - The HITTypeId that the HIT was to
                                         be assigned to.
                          - error      - The error message associated with
                                         the failure.
                   
    maxHits    - (optional) If this value is greater than or equal to 0, than 
                 at most maxHits will be created.
    
=cut


sub updateHITs {
    my $mturk = shift;
    my %params = @_;
    
    foreach my $required (qw{ properties input }) {
        if (!exists $params{$required}) {
            Carp::croak("Missing required parameter $required.");            
        }
    }
    
    my $progress = Net::Amazon::MechanicalTurk::BulkSupport::progressBlock($params{progress});
    my $success  = Net::Amazon::MechanicalTurk::BulkSupport::successBlock($params{success});
    my $fail     = Net::Amazon::MechanicalTurk::BulkSupport::failBlock($params{fail});
    my $maxHits  = (exists $params{maxHits}) ? $params{maxHits} : -1;
    
    if ($progress) {
        $progress->("--[Initializing] " . scalar localtime() . " ---");
        $progress->("  URL:        " . $mturk->serviceUrl);
        $progress->("  Properties: $params{properties}");
        $progress->("  Input:      $params{input}");
    }
    
    my $properties = Net::Amazon::MechanicalTurk::Properties->toProperties($params{properties});
    my $input      = Net::Amazon::MechanicalTurk::RowData->toRowData($params{input});
    
    my $createHITTypeProperties = Net::Amazon::MechanicalTurk::BulkSupport::getCreateHITTypeProperties($properties);

    my $hitTypeId  = -1;
    my $exitedEach = 0;
    my $rowNumber  = 0;
    my $hitsLoaded = 0;
    my $failures   = 0;
    my $start      = time();
    my $hitIdKey;
    my $lastHITId;
    
    eval {
    
        # Register HITType    
        $progress->("--[Registering HITType] " . scalar localtime() . " ---") if ($progress);
        $hitTypeId = Net::Amazon::MechanicalTurk::BulkSupport::createHITType($mturk, $createHITTypeProperties, $properties, $progress);
        $progress->("  Created HITType (HITTypeId: $hitTypeId)") if ($progress);
        
        $progress->("--[Updating HITs] " . scalar localtime() . " ---") if ($progress);
        $input->each(sub {
            my ($_self, $row) = @_;
            $rowNumber++;
     
            if ($maxHits >= 0 and $rowNumber > $maxHits) {
                $exitedEach = 1;
                die "Exiting each loop";
            }
            
            # Need to find the column name of the HITId key
            # The loop is for case insensitive matching and ignoring white space.
            if (!defined $hitIdKey) {
                while (my ($key,$value) = each %$row) {
                    if ($key =~ /^\s*HITId\s*$/i) {
                        $hitIdKey = $key;
                        last;
                    }
                }
                if (!defined $hitIdKey) {
                    Carp::croak("Couldn't find HITId column.  Row has the following columns: " .
                        join(", ", sort keys (%$row)));
                }
            }
            
            if (!exists $row->{$hitIdKey}) {
                Carp::croak("Couldn't find HITId column on row $rowNumber.");
            }
            my $hitId = $row->{$hitIdKey};
            $hitId =~ s/^\s+//;
            $hitId =~ s/\s+$//;

                
            eval {
                my $changeProps = { HITId => $hitId, HITTypeId => $hitTypeId };
                $mturk->ChangeHITTypeOfHIT(HITId => $hitId, HITTypeId => $hitTypeId);
                $progress->("  Updated HITType of HIT $rowNumber (HITId: $lastHITId) to $hitTypeId.") if ($progress);
                $hitsLoaded++;
                $success->(
                    mturk      => $mturk,
                    row        => $row,
                    HITId      => $lastHITId,
                    HITTypeId  => $hitTypeId
                );
            };
            if ($@) {
                $failures++;
                $progress->("  $@") if $progress;
                $fail->(
                    mturk      => $mturk,
                    row        => $row,
                    HITId      => $hitId,
                    HITTypeId  => $hitTypeId,
                    error      => $@
                );
            }
        }); # End each
    };
    if ($@ and !$exitedEach) {
        my $message = "\nAn error occurred while updating a HIT.\n";
        $message .= "\n$@\n";
        if ($mturk->response) {
            if ($mturk->response->errorCode) {
                $message .= "\nError Code: " . $mturk->response->errorCode . "\n";
                $message .= "Error Message: " . $mturk->response->errorMessage . "\n";
            }
        }
        if ($rowNumber > 0) {
            $message .= "\nFailed on row $rowNumber in input $params{input}.\n";
        }
        if ($mturk->request) {
            $message .= "\nLast operation called " . $mturk->request->{Operation} . ".\n";
            $message .= "\nDump of call parameters:\n" .
                Net::Amazon::MechanicalTurk::BulkSupport::formatDataStructure($mturk->request, 4) . "\n";
        }
        if ($mturk->response) {
            $message .= "\nDump of response:\n" .
                Net::Amazon::MechanicalTurk::BulkSupport::formatDataStructure($mturk->response->fullResult, 4) . "\n";
        }
        Carp::croak($message);
    }
    
    if ($progress) {
        $progress->("  Failed to update $failures HITs.");
        $progress->("  Updated $hitsLoaded HITs.");
        $progress->("--[Done Updating HITs] " . scalar localtime() . " ---");
        $progress->("  Total update time: " . (time() - $start) . " seconds.");
        $progress->("  You may see your HITs here: " . $mturk->getHITTypeURL($hitTypeId));
    }
    
    return { updated => $hitsLoaded, failed => $failures, HITTypeId => $hitTypeId };
}

return 1;
