/**
  * Validate a bundle against an order, and trigger an exception if the bundle is not valid.
  * Bundles will be marked as invalid if:
  *   - The order has a discount code assigned to it and the bundle is set to not work in conjunction with
  *     discount codes
  *   - An order does not have the minimum number of items needed for the bundle
  *
  * @param Bundle $bundle
  * @param Order\Order $order
  * @throws \LogicException        Throws exception if product row ID not set in count arrays
  *
  * @return bool
  */
 public function validate(Bundle $bundle, Order\Order $order)
 {
     if (!$bundle->inTimeRange()) {
         $this->_error('ms.discount.bundle.validation.time', ['%name%' => $bundle->getName()]);
     }
     list($expectedCounts, $currentCounts) = $this->_getCounts($bundle);
     $this->validateAllowsCodes($bundle, $order);
     foreach ($order->items as $item) {
         foreach ($bundle->getProductRows() as $row) {
             if (!array_key_exists($row->getID(), $expectedCounts) || !array_key_exists($row->getID(), $currentCounts)) {
                 throw new \LogicException('Expected counts arrays to have a value with a key of `' . $row->getID() . '` but it doesn\'t');
             }
             // Do not increment current counts beyond the expected count
             if ($currentCounts[$row->getID()] >= $expectedCounts[$row->getID()]) {
                 continue;
             }
             // If the item fits the requirements of the product row, increment the current count
             if ($this->itemIsApplicable($item, $row)) {
                 $currentCounts[$row->getID()]++;
                 $item->id = $item->id ?: uniqid();
                 $this->_alreadyInBundle[] = $item->id;
                 break;
             }
         }
     }
     foreach ($expectedCounts as $key => $value) {
         if ($currentCounts[$key] != $value) {
             $this->_error('ms.discount.bundle.validation.items', ['%name%' => $bundle->getName()]);
         }
     }
     return true;
 }