<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

//@require 'Swift/Transport.php';
//@require 'Swift/Mime/Message.php';
//@require 'Swift/Events/EventListener.php';

/**
 * Redudantly and rotationally uses several Transports when sending.
 * 
 * @package Swift
 * @subpackage Transport
 * @author Chris Corbyn
 */
class Swift_Transport_LoadBalancedTransport implements Swift_Transport
{
  
  /** Transports which are deemed useless */
  private $_deadTransports = array();
  
  /**
   * The Transports which are used in rotation.
   * 
   * @var array Swift_Transport
   * @access protected
   */
  protected $_transports = array();
  
  /**
   * Creates a new LoadBalancedTransport.
   */
  public function __construct()
  {
  }
  
  /**
   * Set $transports to delegate to.
   * 
   * @param array $transports Swift_Transport
   */
  public function setTransports(array $transports)
  {
    $this->_transports = $transports;
    $this->_deadTransports = array();
  }
  
  /**
   * Get $transports to delegate to.
   * 
   * @return array Swift_Transport
   */
  public function getTransports(array $transports)
  {
    return array_merge($this->_transports, $this->_deadTransports);
  }
  
  /**
   * Test if this Transport mechanism has started.
   * 
   * @return boolean
   */
  public function isStarted()
  {
    return count($this->_transports) > 0;
  }
  
  /**
   * Start this Transport mechanism.
   */
  public function start()
  {
    $this->_transports = array_merge($this->_transports, $this->_deadTransports);
  }
  
  /**
   * Stop this Transport mechanism.
   */
  public function stop()
  {
    foreach ($this->_transports as $transport)
    {
      $transport->stop();
    }
  }
  
  /**
   * Send the given Message.
   * 
   * Recipient/sender data will be retreived from the Message API.
   * The return value is the number of recipients who were accepted for delivery.
   * 
   * @param Swift_Mime_Message $message
   * @param string[] &$failedRecipients to collect failures by-reference
   * @return int
   */
  public function send(Swift_Mime_Message $message, &$failedRecipients = null)
  {
    $maxTransports = count($this->_transports);
    $sent = 0;
    
    for ($i = 0; $i < $maxTransports
      && $transport = $this->_getNextTransport(); ++$i)
    {
      try
      {
        if (!$transport->isStarted())
        {
          $transport->start();
        }
        if ($sent = $transport->send($message, $failedRecipients))
        {
          break;
        }
      }
      catch (Swift_TransportException $e)
      {
        $this->_killCurrentTransport();
      }
    }
    
    if (count($this->_transports) == 0)
    {
      throw new Swift_TransportException(
        'All Transports in LoadBalancedTransport failed, or no Transports available'
        );
    }
    
    return $sent;
  }
  
  /**
   * Register a plugin.
   * 
   * @param Swift_Events_EventListener $plugin
   */
  public function registerPlugin(Swift_Events_EventListener $plugin)
  {
    foreach ($this->_transports as $transport)
    {
      $transport->registerPlugin($plugin);
    }
  }
  
  // -- Protected methods
  
  /**
   * Rotates the transport list around and returns the first instance.
   * 
   * @return Swift_Transport
   * @access protected
   */
  protected function _getNextTransport()
  {
    if ($next = array_shift($this->_transports))
    {
      $this->_transports[] = $next;
    }
    return $next;
  }
  
  /**
   * Tag the currently used (top of stack) transport as dead/useless.
   * 
   * @access protected
   */
  protected function _killCurrentTransport()
  {
    if ($transport = array_pop($this->_transports))
    {
      try
      {
        $transport->stop();
      }
      catch (Exception $e)
      {
      }
      $this->_deadTransports[] = $transport;
    }
  }
  
}
