#!/usr/bin/perl

use strict;
use warnings;
$| = 1;

package API::Applicator;

use Carp;
use Try::Tiny;

use lib qw(libs);
use API::Config ();
use API::Utils::WorkQueue ();
use base 'API::Utils::WorkQueue';

use lib ($API::Config::Config->{'Website_libs'});
use ECS::DB;
use ECS::dbConfig;
use ECS::Category;
use ECS::Vehicle;
use ECS::Category;
use ECS::Vehicle;
use ECS::Cache::ProductCache;
use ECS::Search::Product;
use ECS::Site;

use TMS::Vehicle;
use TMS::Search::Product;

use Clone ();
use Date::Format ();
use Params::Check ();
use Scalar::Util ();
use Thread::Semaphore ();
use Time::HiRes ();

# it might be possible to use multiple threads later if we can find
# a good way to determine which products can safely be modified
# at the same time as one another
$API::Utils::WorkQueue::NUM_THREADS = 4;
$API::Utils::WorkQueue::PERSISTENCE_FILENAME = "/tmp/.applicator_queue";
$API::Utils::WorkQueue::WORK = \&save;
$API::Utils::WorkQueue::WORK_ITEM_MATCHER = \&workItemMatcher;

my @ALLOWED_WEBSITES = (
    'ECS',
    'TMS',
);

return 1;

###############################################################################

sub workItemMatcher {
    my ( $item, $matchData ) = @_;

    my $item_type = $matchData->{'item_type'}
        or croak "item_type required";
    my $item_id = $matchData->{'item_id'}
        or croak "item_id required";
    croak "item_id must be a number"
        unless Scalar::Util::looks_like_number( $matchData->{'item_id'} );

    # skip over work items that don't look like our kind of work
    return if (
        not $item->{'item_type'}
        or not $item->{'item_id'}
    );

    # actual matching here
    return 1 if (
        $item->{'item_type'} eq $matchData->{'item_type'}
        and $item->{'item_id'} == $matchData->{'item_id'}
    );

    return; # no match
}

###############################################################################

sub save {
    my $data = shift;
    my $t0 = Time::HiRes::time;
    printf("[%s] - - - Beginning to process %s %u\n",
        scalar localtime,
        $data->{'item_type'},
        $data->{'item_id'},
        );

    my $dbh = ECS::DB->connect({'config' => $ECS::dbConfig::db{'mysql'}});
    if(not defined $dbh) {
        return 0;
    }

    my $valid = validate_parameters($data);
    if(not $valid) {
        warn "Invalid parameters\n";
        #$dbh->disconnect();
        ECS::DB->dbDisconnectAllHandles();
        return -1;
    }
    
    # die('parameters are valid, but stop here');
    
    if($data->{'item_type'} eq "product") {
        set_worldpac_modified({
            'dbh'       => $dbh,
            'productID' => $data->{'item_id'},
        });
    }

    my $new_configs = add_vehicle_configs({
        'dbh'       => $dbh,
        'configs'   => $data->{'requires_insert'},
    });
    push(@{$data->{'applications'}->{$_->{'website'}} }, $_) for @{ $new_configs };
    
    my $productIndexDoc; # existing product index doc
    my %productIndexApps; # new product index doc (application data only for partial doc update)
    
    if ($data->{'item_type'} eq 'product') {
        my $response;
        try {
            $response = ECS::Search::Product::get({
                'id' => $data->{'item_id'},
            });
        } catch {
            warn 'failed to retrieve elasticsearch document for productID '
                .$data->{'item_id'};
        };
        $productIndexDoc = $response->{'_source'}
            if $response;
    }

    for my $website (@ALLOWED_WEBSITES) {

        # TMS Database supports Product Applications
        #  and News Applications
        if ( $website eq 'TMS' ) {
            if ( $data->{'item_type'} eq "product" ) {
                # accept
            }
            elsif ( $data->{'item_type'} eq "newsArticle" ) {
                # accept
            }
            else {
                next; # deny
            }
        }

        my $p = {
            'app_dbh'      => $dbh,
            'dbh'          => $dbh,
            'website'      => $website,
            'info'         => $data,
            'applications' => $data->{'applications'}->{$website},
            'productIndexDoc' => $productIndexDoc,
            'productIndexApps' => \%productIndexApps,
        };

        $p->{'siteTMS'} = ECS::Site->fromName('TMS');
		$p->{'dbhTMS'} = $p->{'siteTMS'}->site_dbh;
        if ($website eq 'TMS') {
            $p->{'app_dbh'} = $p->{'dbhTMS'};
        }

        set_applications($p);

    }
    
    if (
        $productIndexDoc
        and %productIndexApps
    ) {
        ECS::Search::Product::update($data->{'item_id'}, {
            'doc' => \%productIndexApps,
        });
    }

    if ($data->{'item_type'} eq 'product') {
        # Clear the product cache following the
        # update because there may be cached values
        # which depend upon the current applications
        ECS::Cache::ProductCache::deleteProductInfo({
            'DBH'                  => $dbh,
            'productIDs'           => [ $data->{'item_id'} ],
            'update_date_modified' => 1,
        });
    }

    my $site = ECS::Site->fromID( $data->{'site_id' } );
    my $siteDBH = $site->site_dbh;

    set_filters({
        'dbh'   => $siteDBH,
        'info'  => $data,
        });

    set_matching_rows({
        'dbh'   => $siteDBH,
        'info'  => $data,
        });

    printf("[%s] - - - Took %.03fs to process %s %u for site %s\n",
        scalar localtime,
        Time::HiRes::time - $t0,
        $data->{'item_type'},
        $data->{'item_id'},
        $site->name,
        );

    #$dbh->disconnect();
    ECS::DB->dbDisconnectAllHandles();
    return 1;
}

###############################################################################

sub set_applications(%) {
    my $p = shift;

    my $existing_apps = get_apps({
        'dbh'   => $p->{'app_dbh'},
        'table' => $p->{'info'}->{'application_table'},
        'item'  => $p->{'info'}->{'item_type'},
        'id'    => $p->{'info'}->{'item_id'},
        });

    my $new_apps = {map({
            $_->{'categoryConfigID'} . "_" . $_->{'vehicleConfigID'} => $_
        }
        @{$p->{'applications'}})};

    my @index_ops = ();

    for my $app_key (keys %{$existing_apps}) {
        my $app = $existing_apps->{$app_key};
        if(not defined $new_apps->{$app_key}) {
            my $application_table = $p->{'info'}->{'application_table'};
            my $application_id = $app->{$application_table . "ID"};

            if($p->{'info'}->{'item_type'} eq "product") {
                my $catLevelIDs = get_category_level_ids({
                    'dbh'   => $p->{'app_dbh'},
                    'table' => $application_table,
                    'id'    => $application_id,
                    });
                
                $app->{$_} = $catLevelIDs->{$_} for keys %{ $catLevelIDs };
                $app->{'categoryKey'} = build_category_key($catLevelIDs);
            }

            delete_app({
                'dbh'               => $p->{'app_dbh'},
                'table'             => $application_table,
                'id'                => $application_id,
                'item'              => $p->{'info'}->{'item_type'},
                'item_id'           => $p->{'info'}->{'item_id'},
                'vehicleConfigID'   => $app->{'vehicleConfigID'},
                'categoryConfigID'  => $app->{'categoryConfigID'},
                'employeeID'        => $p->{'info'}->{'employee_id'},
                });

            push(@index_ops, {
                'action'=> "Delete",
                'app'   => $app,
                });
        }
    }

    for my $app_key (keys %{$new_apps}) {
        my $app = $new_apps->{$app_key};

        if(not defined $existing_apps->{$app_key}) {
            my $id = add_app({
                'dbh'               => $p->{'app_dbh'},
				'dbhTMS'            => $p->{'dbhTMS'},
                'table'             => $p->{'info'}->{'application_table'},
                'item'              => $p->{'info'}->{'item_type'},
                'id'                => $p->{'info'}->{'item_id'},
                'vehicleConfigID'   => $app->{'vehicleConfigID'},
                'categoryConfigID'  => $app->{'categoryConfigID'},
                'appDesc'           => $app->{'appDesc'},
                'employeeID'        => $p->{'info'}->{'employee_id'},
				'tmsPartnumber'     => $p->{'productIndexDoc'}->{'tms'}->{'partnumber'},
				'ecsPartnumber'     => $p->{'productIndexDoc'}->{'partnumber'},
                });

            my $application_table = $p->{'info'}->{'application_table'};
            $app->{$application_table . "ID"} = $id;

            if($p->{'info'}->{'item_type'} eq "product") {
                my $catLevelIDs = get_category_level_ids({
                    'dbh'   => $p->{'app_dbh'},
                    'table' => $application_table,
                    'id'    => $id,
                    });
                
                $app->{$_} = $catLevelIDs->{$_} for keys %{ $catLevelIDs };
                $app->{'categoryKey'} = build_category_key($catLevelIDs);
            }

            push(@index_ops, {
                'action'=> "Set",
                'app'   => $app,
                });
        }
        else {
            my $old_app = $existing_apps->{$app_key};
            my $application_table = $p->{'info'}->{'application_table'};
            my $application_id = $old_app->{$application_table . "ID"};

            if(defined $app->{'appDesc'}
                    and not defined $old_app->{'appDesc'}
                or not defined $app->{'appDesc'}
                    and defined $old_app->{'appDesc'}
                or defined $app->{'appDesc'} 
                    and defined $old_app->{'appDesc'}
                    and $app->{'appDesc'} ne $old_app->{'appDesc'}
                ) {
                update_app({
                    'dbh'       => $p->{'app_dbh'},
                    'table'     => $application_table,
                    'id'        => $application_id,
                    'appDesc'   => $app->{'appDesc'} || "",
                    'employeeID'=> $p->{'info'}->{'employee_id'},
                    });
                $old_app->{'appDesc'} = $app->{'appDesc'};

                if($p->{'info'}->{'item_type'} eq "product") {
                    
                    my $catLevelIDs = get_category_level_ids({
                        'dbh'   => $p->{'app_dbh'},
                        'table' => $application_table,
                        'id'    => $application_id,
                        });
                    
                    $old_app->{$_} = $catLevelIDs->{$_} for keys %{ $catLevelIDs };
                    $old_app->{'categoryKey'} = build_category_key($catLevelIDs);
                }

                push(@index_ops, {
                    'action'=> "Set",
                    'app'   => $old_app,
                    });
            }
        }
    }

    if($p->{'info'}->{'item_type'} eq "product") {
        
        # skip writing to product index if there are no changes to be made
        return unless @index_ops;
        
        # Product document not in index? Product applications are useless without a product
        # Next run of population script will clean this up if necessary
        return unless $p->{'productIndexDoc'};
        
        my $apps;
        if ($p->{'website'} eq 'ECS') {
            $apps = $p->{'productIndexDoc'}->{'applications'} ||= [];
            $p->{'productIndexApps'}->{'applications'} = $apps;
        }
        elsif ($p->{'website'} eq 'TMS') {
            $apps = $p->{'productIndexDoc'}->{'tms'}->{'applications'} ||= [];
            $p->{'productIndexApps'}->{'tms'}->{'applications'} = $apps;
        }

        update_application_index_data({
            'dbh' => $p->{'dbh'},
            'info' => $p->{'info'},
            'indexApps' => $apps, # contents get updated
            'ops' => \@index_ops,
        });

        if ($p->{'website'} eq 'TMS') {
            $p->{'productIndexApps'}->{'tms'}->{'fitsVehicleGroups'} =
                TMS::Search::Product::getFitsVehicleGroupsIndexData({
                    'site' => $p->{'siteTMS'},
                    'appData' => $apps,
                });
            $p->{'productIndexApps'}->{'tms'}->{'categories'} =
                TMS::Search::Product::getCategoriesIndexData({
                    'site' => $p->{'siteTMS'},
                    'appData' => $apps,
                });
        }

    }

}

###############################################################################

sub set_filters(%) {
    my $p = shift;

    my $table = $p->{'info'}->{'item_type'};
    my $table_id = $table . "ID";

    my $query = "insert into
            filterGroup
        set
            $table_id = ?,
            groupNum = ?";
    my $add_group = $p->{'dbh'}->prepare($query);

    $query = "insert into
            filterCategory
        set
            filterGroupID = ?,
            website = ?,
            categoryID = ?,
            subcategoryID = ?,
            groupingID = ?,
            subgroupingID = ?,
            headingID = ?,
            subheadingID = ?,
            sortOrder = ?";
    my $add_category = $p->{'dbh'}->prepare($query);

    $query = "insert into
            filterVehicle
        set
            filterGroupID = ?,
            sortOrder = ?";
    my $add_vehicle = $p->{'dbh'}->prepare($query);

    $query = "insert into
            filterVehicleOption
        set
            filterVehicleID = ?,
            optionName = ?,
            sortOrder = ?";
    my $add_option = $p->{'dbh'}->prepare($query);

    $query = "insert into
            filterVehicleOptionValue
        set
            filterVehicleOptionID = ?,
            optionValue = ?";
    my $add_value = $p->{'dbh'}->prepare($query);

    # making sure that we either finish adding the new filters
    # or don't delete the old ones
    $p->{'dbh'}->begin_work();

    try {
        delete_filters({
            'dbh'   => $p->{'dbh'},
            'item'  => $p->{'info'}->{'item_type'},
            'id'    => $p->{'info'}->{'item_id'},
            });

        for my $group (@{$p->{'info'}->{'filters'}}) {
            $add_group->execute(
                $p->{'info'}->{'item_id'},
                $group->{'groupNum'},
                );
            $group->{'filterGroupID'} = $p->{'dbh'}->{'mysql_insertid'};
        
            for my $category (@{$group->{'categories'}}) {
                $add_category->execute(
                    $group->{'filterGroupID'},
                    $category->{'website'},
                    $category->{'categoryID'},
                    $category->{'subcategoryID'},
                    $category->{'groupingID'},
                    $category->{'subgroupingID'},
                    $category->{'headingID'},
                    $category->{'subheadingID'},
                    $category->{'sortOrder'},
                );
                $category->{'filterCategoryID'} = $p->{'dbh'}->{'mysql_insertid'};
            }

            for my $vehicle (@{$group->{'vehicles'}}) {
                $add_vehicle->execute(
                    $group->{'filterGroupID'},
                    $vehicle->{'sortOrder'},
                    );
                $vehicle->{'filterVehicleID'} = $p->{'dbh'}->{'mysql_insertid'};

                for my $option (@{$vehicle->{'options'}}) {
                    $add_option->execute(
                        $vehicle->{'filterVehicleID'},
                        $option->{'optionName'},
                        $option->{'sortOrder'},
                        );
                    $option->{'filterVehicleOptionID'} =
                        $p->{'dbh'}->{'mysql_insertid'};

                    for my $value (@{$option->{'values'}}) {
                        $value->{'filterVehicleOptionID'} =
                            $option->{'filterVehicleOptionID'};

                        $add_value->execute(
                            $value->{'filterVehicleOptionID'},
                            $value->{'optionValue'},
                            );
                    }
                }
            }
        }
    } catch {
        $p->{'dbh'}->rollback();
        warn 'failed to set filter';
    };
    
    $p->{'dbh'}->commit();
}

###############################################################################

sub set_matching_rows(%) {
    my $p = shift;

    my $item_id = $p->{'info'}->{'item_type'} . "ID";

    my $query = "insert into
            matchingApplicationCategory
        set
            $item_id = ?,
            website = ?,
            categoryInfo = ?,
            sortOrder = ?";
    my $add_category = $p->{'dbh'}->prepare($query);

    $query = "insert into
            matchingApplicationVehicle
        set
            matchingApplicationCategoryID = ?,
            vehicleInfo = ?,
            sortOrder = ?";
    my $add_vehicle = $p->{'dbh'}->prepare($query);

    $p->{'dbh'}->begin_work();

    try {
        delete_matching_rows({
            'dbh'   => $p->{'dbh'},
            'item'  => $p->{'info'}->{'item_type'},
            'id'    => $p->{'info'}->{'item_id'},
            });

        for my $category (@{$p->{'info'}->{'matching_rows'}}) {
            $add_category->execute(
                $p->{'info'}->{'item_id'},
                $category->{'website'},
                $category->{'categoryInfo'},
                $category->{'sortOrder'},
                );
            $category->{'matchingApplicationCategoryID'} =
                $p->{'dbh'}->{'mysql_insertid'};

            for my $vehicle (@{$category->{'vehicles'}}) {
                $add_vehicle->execute(
                    $category->{'matchingApplicationCategoryID'},
                    $vehicle->{'vehicleInfo'},
                    $vehicle->{'sortOrder'},
                    );
            }
        }
    } catch {
        $p->{'dbh'}->rollback();
        warn 'failed to set matching rows';
    };

    $p->{'dbh'}->commit();
}

###############################################################################

sub add_app(%) {
    my $p = shift;

    my $table = $p->{'table'};
    my $item_id = $p->{'item'} . "ID";

    my $query = "insert into
            $table
        set
            $item_id = ?,
            vehicleConfigID = ?,
            categoryConfigID = ?,
            appDesc = ?,
            dtModified = now(),
            employeeID = ?";
    my $add_app = $p->{'dbh'}->prepare($query);
    $add_app->execute(
        $p->{'id'},
        $p->{'vehicleConfigID'},
        $p->{'categoryConfigID'},
        $p->{'appDesc'},
        $p->{'employeeID'},
        );

    my $appID = $p->{'dbh'}->{'mysql_insertid'};
	uploadSiteImages($p);
        
    if($p->{'item'} eq 'product'){
        my $log_query = "insert into
                    applicationLog
                set
                    productID = ?,
                    vehicleConfigID = ?,
                    categoryConfigID = ?,
                    employeeID = ?,
                    action = ?,
                    modifydate = now()";
        my $add_app_log = $p->{'dbh'}->prepare($log_query);
        $add_app_log->execute(
            $p->{'id'},
            $p->{'vehicleConfigID'},
            $p->{'categoryConfigID'},
            $p->{'employeeID'},
            'ADDED');
    }

    return $appID;
}

###############################################################################

sub add_vehicle_configs(%) {
    my $p = shift;

    my $query = "insert into
            vehicleConfig
        set
            detailVehicle = 0,
            yearID = ?,
            makeID = ?,
            modelID = ?,
            subModelID = ?,
            bodyTypeID = ?,
            bodyCodeID = ?,
            engineID = ?";
    my $add_config = $p->{'dbh'}->prepare($query);

    $query = "select
            vehicleConfig.vehicleConfigID
        from
            vehicleConfig
        where
            vehicleConfig.yearID = ?
            and vehicleConfig.makeID = ?
            and vehicleConfig.modelID = ?
            and vehicleConfig.subModelID = ?
            and vehicleConfig.bodyTypeID = ?
            and vehicleConfig.bodyCodeID = ?
            and vehicleConfig.engineID = ?
        limit 1";
    my $check_config = $p->{'dbh'}->prepare($query);

    my @new_configs = ();
    for my $config_hash (@{$p->{'configs'}}) {
        my $config = $config_hash->{'config'};

        $check_config->execute(
            $config->{'year'},
            $config->{'make'},
            $config->{'model'},
            $config->{'subModel'},
            $config->{'bodyType'},
            $config->{'bodyCode'},
            $config->{'engine'},
            );
        my $config_id = $check_config->fetchrow_array();
        
        if(not defined $config_id) {
            $add_config->execute(
                $config->{'year'},
                $config->{'make'},
                $config->{'model'},
                $config->{'subModel'},
                $config->{'bodyType'},
                $config->{'bodyCode'},
                $config->{'engine'},
                );
            $config_id = $p->{'dbh'}->{'mysql_insertid'};
        }

        push(@new_configs, {
            'website'           => $config_hash->{'website'},
            'vehicleConfigID'   => $config_id,
            'categoryConfigID'  => $config_hash->{'categoryConfigID'},
            'appDesc'           => $config_hash->{'desc'},
            });
    }

    return \@new_configs;
}

###############################################################################

sub delete_app(%) {
    my $p = shift;
	my $item = $p->{'item'} . "ID";
    my $table = $p->{'table'};
    my $table_id = $table . "ID";

    my $query = "delete from
            $table
        where
            $table.$table_id = ?
        limit 1";
    my $delete_app = $p->{'dbh'}->prepare($query);
    $delete_app->execute($p->{'id'});
    
    if($p->{'item'} eq 'product'){
    my $log_query = "insert into
                applicationLog
            set
                $item = ?,
                vehicleConfigID = ?,
                categoryConfigID = ?,
                employeeID = ?,
                action = ?,
                modifydate = now()";
    my $delete_app_log = $p->{'dbh'}->prepare($log_query);
    $delete_app_log->execute(
    	$p->{'item_id'},
        $p->{'vehicleConfigID'},
        $p->{'categoryConfigID'},
        $p->{'employeeID'},
        'REMOVED');
    }
}

###############################################################################

sub delete_filters(%) {
    my $p = shift;

    my $item_id = $p->{'item'} . "ID";

    my $query = "delete
            filterGroup,
            filterCategory,
            filterVehicle,
            filterVehicleOption,
            filterVehicleOptionValue
        from
            filterGroup
            left join filterCategory
                using (filterGroupID)
            left join filterVehicle
                using (filterGroupID)
            left join filterVehicleOption
                using (filterVehicleID)
            left join filterVehicleOptionValue
                using (filterVehicleOptionID)
        where
            filterGroup.$item_id = ?";

    my $delete_filters = $p->{'dbh'}->prepare($query);
    $delete_filters->execute($p->{'id'});
}

###############################################################################

sub delete_matching_rows(%) {
    my $p = shift;

    my $item_id = $p->{'item'} . "ID";
    my $query = "delete
            matchingApplicationCategory,
            matchingApplicationVehicle
        from
            matchingApplicationCategory
            left join matchingApplicationVehicle
                using (matchingApplicationCategoryID)
        where
            matchingApplicationCategory.$item_id = ?";
    my $delete_matching_rows = $p->{'dbh'}->prepare($query);
    $delete_matching_rows->execute($p->{'id'});
}

###############################################################################

sub get_apps(%) {
    my $p = shift;

    my $table = $p->{'table'};
    my $table_id = $table . "ID";
    my $id_field = $p->{'item'} . "ID";
    my $id = $p->{'id'};

    my $query = "select
            $table.$table_id,
            $table.vehicleConfigID,
            $table.categoryConfigID,
            $table.appDesc
        from
            $table
        where
            $table.$id_field = ?";
    my $get_apps = $p->{'dbh'}->prepare($query);

    my %apps = ();
    $get_apps->execute($p->{'id'});
    while(my $app = $get_apps->fetchrow_hashref()) {
        $app->{'appDesc'} ||= "";

        my $key = $app->{'categoryConfigID'} . "_" . $app->{'vehicleConfigID'};
        $apps{$key} = $app;
    }
    return \%apps;
}

###############################################################################

sub get_category_level_ids(%) {
    my $p = shift;

    my $table = $p->{'table'};
    my $table_id = $table . "ID";

    my $query = "select
            categoryConfig.categoryID,
            categoryConfig.subcategoryID,
            categoryConfig.groupingID,
            categoryConfig.subgroupingID,
            categoryConfig.headingID,
            categoryConfig.subheadingID
        from
            categoryConfig
            join $table
                on $table.categoryConfigID = categoryConfig.categoryConfigID
        where
            $table.$table_id = ?
        limit 1";
    my $get_category_fields = $p->{'dbh'}->prepare($query);
    $get_category_fields->execute($p->{'id'});
    
    return $get_category_fields->fetchrow_hashref();
}

###############################################################################

sub build_category_key(%) {
    my $categoryLevelIDs = shift;
    return join("_", map { $categoryLevelIDs->{$_.'ID'} } @ECS::Category::LEVELS);
}

###############################################################################

sub set_worldpac_modified(%) {
    my $p = shift;

    my $query = "select
            product.partnumber
        from
            product
            JOIN productVendor
                USING (productID)
            JOIN vendor
                USING (vendorID)
        where
            vendor.mm_vendorID = 'WORLDPACD'
            and product.productID = ?
        limit 1";
    my $get_worldpac_partnumber = $p->{'dbh'}->prepare($query);
    $get_worldpac_partnumber->execute($p->{'productID'});
    my $worldpac_partnumber = $get_worldpac_partnumber->fetchrow_array();

    if(defined $worldpac_partnumber) {
        $query = "insert ignore into
                wpProductModified
            set
                partnumber = ?,
                k = 'applications'";
        my $add_modified = $p->{'dbh'}->prepare($query);
        $add_modified->execute($worldpac_partnumber);
    }
}

###############################################################################

sub update_app(%) {
    my $p = shift;

    my $table = $p->{'table'};
    my $table_id = $table . "ID";

    my $query = "update
            $table
        set
            $table.appDesc = ?,
            $table.employeeID = ?
        where
            $table.$table_id = ?
        limit 1";
    my $update_app = $p->{'dbh'}->prepare($query);
    $update_app->execute(
        $p->{'appDesc'},
        $p->{'employeeID'},
        $p->{'id'},
        );
}

###############################################################################

sub update_application_index_data {
    my $p = shift;
    my @origIndexApps = @{ $p->{'indexApps'} };
    my $newIndexApps = $p->{'indexApps'}; # update contents as ref
    
    # empty out the array so we can determine what to keep and add
    splice(@{ $newIndexApps });
    
    my %modifiedAppIDs = map { $_->{'app'}->{'applicationID'} => 1 }
        grep { $_->{'app'}->{'applicationID'} }
            @{ $p->{'ops'} };
    
    for my $indexApp (@origIndexApps) {
        next if $modifiedAppIDs{$indexApp->{'applicationID'}};
        push(@{ $newIndexApps }, $indexApp);
    }
    
    my $getVehicleConfig = $p->{'dbh'}->prepare(
        "SELECT
            vehicleConfig.makeID,
            vehicleConfig.yearID,
            vehicleConfig.modelID,
            vehicleConfig.bodyCodeID,
            vehicleConfig.subModelID,
            vehicleConfig.bodyTypeID,
            vehicleConfig.engineID
        FROM
            vehicleConfig
        WHERE
            vehicleConfig.vehicleConfigID = ?
        LIMIT 1"
    );
    
    for my $setOp (grep { $_->{'action'} eq 'Set' } @{ $p->{'ops'} }) {
        my %newApp = (
            'applicationID' => $setOp->{'app'}->{'applicationID'},
            'appDesc' => $setOp->{'app'}->{'appDesc'},
            'categoryKey' => $setOp->{'app'}->{'categoryKey'},
            map { $_.'ID' => $setOp->{'app'}->{$_.'ID'} }
                @ECS::Category::LEVELS,
        );
        
        # General Purpose = all zeros
        if (not $setOp->{'app'}->{'vehicleConfigID'}) {
            $newApp{$_} = 0 for @ECS::Vehicle::STEPS;
            push(@{ $newIndexApps }, \%newApp);
            next;
        }
        
        $getVehicleConfig->execute($setOp->{'app'}->{'vehicleConfigID'});
        my $vehicleConfig = $getVehicleConfig->fetchrow_hashref();
        
        my $bfConfigs = ECS::Vehicle::getBackfilledConfigs({
            'dbh' => $p->{'dbh'},
            'vehicleConfig' => $vehicleConfig,
        });
        
        if (not $bfConfigs) {
            push(@{ $newIndexApps }, {
                %newApp,
                %{ $vehicleConfig },
            });
            next;
        }
        
        for my $bfConfig (@{ $bfConfigs }) {
            push(@{ $newIndexApps }, {
                %newApp,
                %{ $bfConfig },
            });
        }
    }
    
    return;
}

###############################################################################

sub validate_parameters($) {
    my $data = shift;
    
    my $website_applications_template = {
        'required' => 1,
        'default'       => [],
        'strict_type'   => 1,
        'children' => {
            'vehicleConfigID' => {
                'required' => 1,
                'allow' => qr/^\d*$/,
            },
            'categoryConfigID' => {
                'required' => 1,
                'allow' => qr/^\d*$/,
            },
            'appDesc' => {
                'required' => 1,
                'allow' => qr/^[A-Z0]*$/,
            },
        }
    };

    my $template = {
        'site_id' => {
            'required'  => 1,
            'allow'     => qr/^\d+$/,
        },
        'item_type' => {
            'required'  => 1,
            'allow'     => [qw(product htmlBlock newsArticle banner)],
            },
        'item_id'   => {
            'required'  => 1,
            'allow'     => qr/^\d+$/,
            },
        'application_table' => {
            'required'  => 1,
            'allow'     => [qw(application htmlBlockApplication
                            newsApplication bannerApplication)],
            },
        'applications'  => {
            'required'      => 1,
            'default'       => {},
            'strict_type'   => 1,
            'children'      => {
                'ECS' => $website_applications_template,
                'TMS' => $website_applications_template,
            }
        },
        'requires_insert'   => {
            'required'      => 1,
            'default'       => [],
            'strict_type'   => 1,
            'children'      => {
                'website'  => {
                    'required'  => 1,
                    'allow'     => \@ALLOWED_WEBSITES,
                    },
                'desc'  => {
                    'required'  => 1,
                    'allow'     => qr/^[A-Z0]*$/,
                    },
                'categoryConfigID'  => {
                    'required'  => 1,
                    'allow'     => qr/^\d+$/,
                    },
                'config'        => {
                    'required'      => 1,
                    'default'       => {},
                    'strict_type'   => 1,
                    'children'      => {
                        'year'      => {
                            'required'  => 1,
                            'allow'     => qr/^\d+$/,
                            },
                        'make'      => {
                            'required'  => 1,
                            'allow'     => qr/^\d+$/,
                            },
                        'model'     => {
                            'required'  => 1,
                            'allow'     => qr/^\d+$/,
                            },
                        'bodyType'  => {
                            'required'  => 1,
                            'allow'     => qr/^\d+$/,
                            },
                        'bodyCode'  => {
                            'required'  => 1,
                            'allow'     => qr/^\d+$/,
                            },
                        'engine'    => {
                            'required'  => 1,
                            'allow'     => qr/^\d+$/,
                            },
                        'subModel'  => {
                            'required'  => 1,
                            'allow'     => qr/^\d+$/,
                            },
                        },
                    },
                },
            },
        'filters'   => {
            'required'      => 1,
            'default'       => [],
            'strict_type'   => 1,
            'children'      => {
                'groupNum'  => {
                    'required'  => 1,
                    'allow'     => qr/^\d+$/,
                    },
                'vehicles'  => {
                    'required'      => 1,
                    'default'       => [],
                    'strict_type'   => 1,
                    'children'      => {
                        'sortOrder' => {
                            'required'  => 1,
                            'allow'     => qr/^\d+$/,
                            },
                        'options'   => {
                            'required'      => 1,
                            'default'       => [],
                            'strict_type'   => 1,
                            'children'      => {
                                'sortOrder' => {
                                    'required'  => 1,
                                    'allow'     => qr/^\d+$/,
                                    },
                                'optionName'    => {
                                    'required'  => 1,
                                    'allow'     => [qw(year make model subModel
                                                    bodyType bodyCode engine)],
                                    },
                                'values'    => {
                                    'required'      => 1,
                                    'default'       => [],
                                    'strict_type'   => 1,
                                    'children'      => {
                                        'optionValue'   => {
                                            'required'  => 1,
                                            'allow'     => qr/^\d+$/,
                                            },
                                        },
                                    },
                                },
                            },
                        },
                    },
                'categories'    => {
                    'required'      => 1,
                    'default'       => [],
                    'strict_type'   => 1,
                    'children'      => {
                        'website'  => {
                            'required'  => 1,
                            'allow'     => \@ALLOWED_WEBSITES,
                            },
                        'sortOrder' => {
                            'required'  => 1,
                            'allow'     => qr/^\d+$/,
                            },
                        'categoryID'    => {
                            'required'  => 1,
                            'allow'     => qr/^\d+$/,
                            },
                        'subcategoryID' => {
                            'required'  => 1,
                            'allow'     => qr/^\d*$/,
                            },
                        'groupingID'    => {
                            'required'  => 1,
                            'allow'     => qr/^\d*$/,
                            },
                        'subgroupingID' => {
                            'required'  => 1,
                            'allow'     => qr/^\d*$/,
                            },
                        'headingID' => {
                            'required'  => 1,
                            'allow'     => qr/^\d*$/,
                            },
                        'subheadingID' => {
                            'required'  => 1,
                            'allow'     => qr/^\d*$/,
                            },
                        },
                    },
                },
            },
        'matching_rows' => {
            'required'      => 1,
            'default'       => [],
            'strict_type'   => 1,
            'children'      => {
                'website'  => {
                    'required'  => 1,
                    'allow'     => \@ALLOWED_WEBSITES,
                    },
                'categoryInfo'  => {
                    'required'  => 1,
                    },
                'sortOrder' => {
                    'required'  => 1,
                    'allow'     => qr/^\d+$/,
                    },
                'vehicles'  => {
                    'required'      => 1,
                    'default'       => [],
                    'strict_type'   => 1,
                    'children'      => {
                        'sortOrder' => {
                            'required'  => 1,
                            'allow'     => qr/^\d+$/,
                            },
                        'vehicleInfo'   => {
                            'required'  => 1,
                            },
                        },
                    },
                },
            },
        };

    return validate($template, $data);
}

###############################################################################

sub validate($$) {
    my $template = shift;
    my $hash = shift;

    # without this it will convert everything to lowercase
    # and nothing will match correctly
    $Params::Check::PRESERVE_CASE = 1;
    $Params::Check::ALLOW_UNKNOWN = 1;

    $Params::Check::VERBOSE ++;

    for my $key (keys %{$template}) {
        my $item = $template->{$key};

        if(defined $item->{'children'}) {
            my $child_template = $item->{'children'};
            delete $item->{'children'};

            if(ref $hash->{$key} eq "ARRAY") {
                for my $child (@{$hash->{$key}}) {
                    &validate($child_template, $child)
                        or return;
                }
            }
            elsif(ref $hash->{$key} eq "HASH") {
                &validate($child_template, $hash->{$key})
                    or return;
            }
        }
    }

    return Params::Check::check($template, $hash);
}

###############################################################################
# This subroutine checks whether the product is Applicated for $TMS::Vehicle::MAKE_IDS
# has tms.imageref entry If not then change imageID status to AddingTMS so imageAdmin Daemon
# will upload images to CDN for TMS.

sub uploadSiteImages {
	my $p = shift;

	my $makeID;
	my $partnumber = $p->{'tmsPartnumber'} || $p->{'ecsPartnumber'};
	# use estore prefix for tables in estore, since we are in tms with 'dbh'
	my $estorePrefix = ECS::Site::fromName(ECS)->prefix;
	if($p->{'vehicleConfigID'}){
		my $makeIDQuery = "SELECT makeID FROM $estorePrefix.vehicleConfig WHERE vehicleConfigID = ?";
		my $makeIDsth = $p->{'dbh'}->prepare($makeIDQuery);
		$makeIDsth->execute($p->{'vehicleConfigID'});
		$makeID = $makeIDsth->fetchrow_array();
	}

	if ($TMS::Vehicle::MAKE_IDS{$makeID} || !$p->{'vehicleConfigID'}){
		my $imageQuery = "SELECT imageID FROM imageref WHERE imageref.partnumber = ?";
		my $findImages = $p->{'dbhTMS'}->prepare($imageQuery);
		$findImages->execute($partnumber);
		my $images = $findImages->fetchall_arrayref({});
		if (!scalar @$images){
			my $updateQuery = "UPDATE
				$estorePrefix.image
					JOIN imageref ON image.imageID = imageref.imageID
				SET status = 'AddingTMS'
					WHERE imageref.partnumber = ?";
			my $updateImageTMS = $p->{'dbh'}->prepare($updateQuery);
			$updateImageTMS->execute($p->{'ecsPartnumber'});
		}
	}
}

###############################################################################
