/**
  * A method to distribute the calculated required campaign impressions between the campaign's
  * children advertisements. Impression allocation takes in to account ad weight, and the number
  * of operations intervals the ad will be active in given date/time delivery limitations, and
  * the pattern of available impressions for the zone(s) the advertisements are linked to.
  *
  * The calculated ad impressions are written to the temporary table tmp_ad_required_impression
  * for later analysis by the {@link OA_Maintenance_Priority_AdServer_Task_AllocateZoneImpressions}
  * class.
  *
  * @param array $aCampaigns An array of {@link OX_Maintenance_Priority_Campaign} objects which require
  *                          that their total required impressions be distributed between the
  *                          component advertisements.
  */
 function distributeCampaignImpressions($aCampaigns)
 {
     // Create an array for storing required ad impressions
     $aRequiredAdImpressions = array();
     // Get the current operation interval start/end dates
     $aCurrentOperationIntervalDates = OX_OperationInterval::convertDateToOperationIntervalStartAndEndDates($this->_getDate());
     // For each campaign
     foreach ($aCampaigns as $oCampaign) {
         OA::debug('  - Distributing impression inventory requirements for campaign ID: ' . $oCampaign->id, PEAR_LOG_DEBUG);
         $adsCount = count($oCampaign->aAds);
         OA::debug("    - Campaign has {$adsCount} ads.", PEAR_LOG_DEBUG);
         // Get date object to represent campaign expiration date
         if ($oCampaign->impressionTargetDaily > 0 || $oCampaign->clickTargetDaily > 0 || $oCampaign->conversionTargetDaily > 0) {
             // The campaign has a daily target to meet, so treat the
             // campaign as if it expires at the end of "today", regardless
             // of the existance of any activation or expiration dates that
             // may (or may not) be set for the campaign
             $oCampaignExpiryDate = new Date($this->_getDate());
             $oCampaignExpiryDate->setTZ($this->currentTz);
             $oCampaignExpiryDate->setHour(23);
             $oCampaignExpiryDate->setMinute(59);
             $oCampaignExpiryDate->setSecond(59);
             $oCampaignExpiryDate->toUTC();
             // Unless the campaign has an expiry date and it happens before the end of today
             if (!empty($oCampaign->expireTime)) {
                 if ($oCampaignExpiryDate->after($this->_getDate($oCampaign->expireTime))) {
                     $oCampaignExpiryDate = $this->_getDate($oCampaign->expireTime);
                 }
             }
         } else {
             if (!empty($oCampaign->expireTime) && ($oCampaign->impressionTargetTotal > 0 || $oCampaign->clickTargetTotal > 0 || $oCampaign->conversionTargetTotal > 0)) {
                 // The campaign has an expiration date, and has some kind of
                 // (total) inventory requirement, so treat the campaign as if
                 // it expires at the expiration date/time
                 $oCampaignExpiryDate = $this->_getDate($oCampaign->expireTime);
             } else {
                 // Error! There should not be any other kind of high-priority
                 // campaign in terms of activation/expiration dates and
                 // either (total) inventory requirements or daily targets
                 $message = "- Error calculating the end date for Campaign ID {$oCampaign->id}";
                 OA::debug($message, PEAR_LOG_ERR);
                 continue;
             }
         }
         // Determine number of remaining operation intervals for campaign
         $message = "    - Calculating campaign remaining operation intervals.";
         OA::debug($message, PEAR_LOG_DEBUG);
         $campaignRemainingOperationIntervals = OX_OperationInterval::getIntervalsRemaining($aCurrentOperationIntervalDates['start'], $oCampaignExpiryDate);
         // For all ads in the campaign, determine:
         // - If the ad is capable of delivery in the current operation
         //   interval, or not, based on if it is linked to any zones, and,
         //   if so:
         // - If the ad is capable of delivery in the current operation
         //   interval, or not, based on delivery limitation(s), and if so;
         // - The result of the weight of the ad multiplied by the
         //   number of operation intervals remaining in which the ad
         //   is capable of delivering
         $aAdZones = array();
         $aAdDeliveryLimitations = array();
         $aAdBlockedForCurrentOI = array();
         $aAdWeightRemainingOperationIntervals = array();
         $aInvalidAdIds = array();
         reset($oCampaign->aAds);
         while (list($key, $oAd) = each($oCampaign->aAds)) {
             // Only calculate values for active ads
             if ($oAd->active && $oAd->weight > 0) {
                 $message = "    - Calculating remaining operation intervals for ad ID: {$oAd->id}";
                 OA::debug($message, PEAR_LOG_DEBUG);
                 // Get all zones associated with the ad
                 $aAdsZones = $this->oDal->getAdZoneAssociationsByAds(array($oAd->id));
                 $aAdZones[$oAd->id] = @$aAdsZones[$oAd->id];
                 if (is_null($aAdZones[$oAd->id])) {
                     $aInvalidAdIds[] = $oAd->id;
                     $message = "      - Ad ID {$oAd->id} has no linked zones, will skip...";
                     OA::debug($message, PEAR_LOG_ERR);
                     continue;
                 }
                 // Prepare a delivery limitation object for the ad
                 $aAdDeliveryLimitations[$oAd->id] = new OA_Maintenance_Priority_DeliveryLimitation($oAd->getDeliveryLimitations());
                 // Is the ad blocked from delivering in the current operation interval?
                 $aAdBlockedForCurrentOI[$oAd->id] = $aAdDeliveryLimitations[$oAd->id]->deliveryBlocked($aCurrentOperationIntervalDates['start']);
                 // Determine how many operation intervals remain that the ad can deliver in
                 $adRemainingOperationIntervals = $aAdDeliveryLimitations[$oAd->id]->getActiveAdOperationIntervals($campaignRemainingOperationIntervals, $aCurrentOperationIntervalDates['start'], $oCampaignExpiryDate);
                 // Determine the value of the ad weight multiplied by the number
                 // of operation intervals remaining that the ad can deliver in
                 if ($oAd->weight > 0) {
                     $aAdWeightRemainingOperationIntervals[$oAd->id] = $oAd->weight * $adRemainingOperationIntervals;
                 } else {
                     $aAdWeightRemainingOperationIntervals[$oAd->id] = 0;
                 }
             }
         }
         // Get the total sum of the ad weight * remaining OI values
         $sumAdWeightRemainingOperationIntervals = array_sum($aAdWeightRemainingOperationIntervals);
         // For each (active) ad that is capable of delivering in the current
         // operation interval, determine how many of the campaign's required
         // impressions should be alloced as the ad's required impressions
         // For each advertisement
         reset($oCampaign->aAds);
         while (list($key, $oAd) = each($oCampaign->aAds)) {
             if (in_array($oAd->id, $aInvalidAdIds)) {
                 OA::debug('       - Skipping ad ID: ' . $oAd->id, PEAR_LOG_DEBUG);
                 continue;
             }
             OA::debug('     - Calculating required impressions for ad ID: ' . $oAd->id, PEAR_LOG_DEBUG);
             // Get impressions required
             $totalRequiredAdImpressions = 0;
             if ($oAd->active && $oAd->weight > 0 && $aAdBlockedForCurrentOI[$oAd->id] !== true) {
                 $totalRequiredAdImpressions = $oCampaign->requiredImpressions * ($aAdWeightRemainingOperationIntervals[$oAd->id] / $sumAdWeightRemainingOperationIntervals);
             }
             if ($totalRequiredAdImpressions <= 0) {
                 OA::debug('       - No required impressions for ad ID: ' . $oAd->id, PEAR_LOG_DEBUG);
                 continue;
             }
             // Based on the average zone pattern of the zones the ad is
             // linked to, calculate how many of these impressions should
             // be delivered in the next operation interval
             OA::debug('       - Calculating next OI required impressions for ad ID: ' . $oAd->id, PEAR_LOG_DEBUG);
             $oAd->requiredImpressions = $this->_getAdImpressions($oAd, $totalRequiredAdImpressions, $aCurrentOperationIntervalDates['start'], $oCampaignExpiryDate, $aAdDeliveryLimitations[$oAd->id], $aAdZones[$oAd->id]);
             $aRequiredAdImpressions[] = array('ad_id' => $oAd->id, 'required_impressions' => $oAd->requiredImpressions);
         }
     }
     // Save the required impressions into the temporary database table
     OA::setTempDebugPrefix('- ');
     // Check if table exists
     if (!isset($GLOBALS['_OA']['DB_TABLES']['tmp_ad_required_impression'])) {
         if ($this->oTable->createTable('tmp_ad_required_impression', null, true) !== false) {
             // Remember that table was created
             $GLOBALS['_OA']['DB_TABLES']['tmp_ad_required_impression'] = true;
         }
     }
     $this->oDal->saveRequiredAdImpressions($aRequiredAdImpressions);
 }