<?php

  /**
   * Framework level application mailer implementation
   *
   * @package angie.frameworks.email
   * @subpackage models
   */
  final class ApplicationMailer {
  	
  	// Send constants
  	const SEND_INSTANTNLY = 'instantly';
  	const SEND_IN_BACKGROUD = 'in_background';
  	const SEND_HOURLY = 'hourly';
  	const SEND_DAILY = 'daily';
  	
  	/**
     * Mailing adapter
     * 
     * @var ApplicationMailerAdapter
     */
    static private $adapter;
    
    /**
     * Return mailer adapter
     * 
     * @return ApplicationMailerAdapter
     */
    static function getAdapter() {
      if(!(self::$adapter instanceof ApplicationMailerAdapter)) {
        switch(ApplicationMailer::getConnectionType()) {
      		case MAILING_DISABLED:
      			ApplicationMailer::setAdapter(new DisabledMailerAdapter());
      			break;
      		case MAILING_SILENT:
      			ApplicationMailer::setAdapter(new SilentMailerAdapter());
      			break;
      		case MAILING_NATIVE: 
      		 	ApplicationMailer::setAdapter(new NativeSwiftMailerAdapter());
      			break;
      		case MAILING_SMTP:
      			ApplicationMailer::setAdapter(new SmtpSwiftMailerAdapter());
      			break;
      		default:
      		  throw new InvalidParamError('mailing', ApplicationMailer::getConnectionType(), 'Invalid mailer type');
      	} // if
      } // if
      
    	return self::$adapter;
    } // getAdapter
    
    /**
     * Set mailer adapter
     * 
     * @param ApplicationMailerAdapter $adapter
     */
    static function setAdapter(ApplicationMailerAdapter $adapter) {
      self::$adapter = $adapter;
    } // setAdapter
    
    /**
     * Try to establish SMTP connection with given parameters
     * 
     * @param string $host
     * @param string $port
     * @param string $message
     * @param boolean $authenticate
     * @param string $username
     * @param string $password
     * @param string $security
     * @return boolean
     */
    static function testSmtp($host, $port, &$message, $authenticate = false, $username = null, $password = null, $security = null) {
      try {
        SwiftMailerForAngie::testSmtpConnection($host, $port, $authenticate, $username, $password, $security);
        $message = lang('Connection has been established successfully');
        
        return true;
      } catch(Exception $e) {
        $message = $e->getMessage();
        return false;
      } // try
    } // testSmtp
    
    /**
     * Mailer decorator
     * 
     * @var ApplicationMailerDecorator
     */
    static private $decorator;
    
    /**
     * Return mailer decorator
     * 
     * @return ApplicationMailerDecorator
     */
    static function getDecorator() {
    	return self::$decorator;
    } // getDecorator
    
    /**
     * Set mailer decorator
     * 
     * @param ApplicationMailerDecorator $decorator
     */
    static function setDecorator(ApplicationMailerDecorator $decorator) {
    	self::$decorator = $decorator;
    } // setDecorator
  	
    /**
     * Default sender instance
     * 
     * @var AnonymousUser
     */
  	static private $default_sender;
  	
  	/**
  	 * Return default sender
  	 * 
  	 * @return AnonymousUser
  	 */
  	static function getDefaultSender() {
  	  if(!(self::$default_sender instanceof IUser)) {
  	    list($from_email, $from_name) = ApplicationMailer::getFromEmailAndName();
        
        if($from_email) {
        	ApplicationMailer::setDefaultSender(new AnonymousUser($from_name, $from_email));
        } else {
          ApplicationMailer::setDefaultSender(new AnonymousUser($from_name, ADMIN_EMAIL));
        } // if
  	  } // if
  	  
  		return self::$default_sender;
  	} // getDefaultSender
  	
  	/**
  	 * Set default from user
  	 * 
  	 * @param AnonymousUser $sender
  	 */
  	static function setDefaultSender(AnonymousUser $sender) {
  		self::$default_sender = $sender;
  	} // setDefaultSender
  	
  	/**
  	 * Interface to notifier method of default sender
  	 * 
  	 * @return INotifierImplementation
  	 */
  	static function notifier() {
  		return self::$default_sender->notifier();
  	} // notifier
  	
  	/**
  	 * Cached default mailing method
  	 * 
  	 * @var string
  	 */
  	static private $default_mailing_method = false;
  	
  	/**
  	 * Return default mailing method
  	 * 
  	 * @return string
  	 */
  	static function getDefaultMailingMethod() {
  		if(self::$default_mailing_method === false) {
  			self::$default_mailing_method = ConfigOptions::getValue('mailing_method');
  			
  			if(!self::isValidMailingMethod(self::$default_mailing_method)) {
  				self::$default_mailing_method = self::SEND_INSTANTNLY;
  			} // if
  		} // if
  		
  		return self::$default_mailing_method;
  	} // getDefaultMailingMethod
  	
  	/**
  	 * Set default mailing method
  	 * 
  	 * @param string $method
  	 */
  	static function setDefaultMailingMethod($method) {
  		if(self::isValidMailingMethod($method)) {
  			ConfigOptions::setValue('mailing_method', $method);
  			self::$default_mailing_method = $method;
  		} else {
  			throw new InvalidParamError('method', $method, '$method value is not a valid mailing method name');
  		} // if
  	} // setDefaultMailingMethod
  	
  	/**
  	 * Return true if $method is a valid mailing method
  	 * 
  	 * @param string $method
  	 * @return boolean
  	 */
  	static function isValidMailingMethod($method) {
  		return in_array($method, array(self::SEND_INSTANTNLY, self::SEND_IN_BACKGROUD, self::SEND_HOURLY, self::SEND_DAILY));
  	} // isValidMailingMethod
  	
  	// ---------------------------------------------------
  	//  Connect / disconnect / send
  	// ---------------------------------------------------
  	
  	/**
  	 * Connect mailer adapter
  	 */
  	static function connect() {
  		if(self::$adapter instanceof ApplicationMailerAdapter) {
  			self::$adapter->connect();
  		} // if
  	} // connect
  	
  	/**
  	 * Disconnect mailer adapter
  	 */
  	static function disconnect() {
  		if(self::$adapter instanceof ApplicationMailerAdapter) {
  			self::$adapter->disconnect();
  		} // if
  	} // disconnect
  	
  	/**
  	 * Returns true if we have proper mailer adapter set and it is connected
  	 * 
  	 * @return boolean
  	 */
  	static function isConnected() {
  		return self::$adapter instanceof ApplicationMailerAdapter ? self::$adapter->isConnected() : false;
  	} // isConnected
  	
  	// ---------------------------------------------------
  	//  Send Message
  	// ---------------------------------------------------
  	
  	/**
  	 * Send a message to one or more recipients
  	 * 
  	 * Supported additional parameters:
  	 * 
  	 * - context - Context in which notification is sent
  	 * - decorate - Whether email should be decorated or not. This parameter is 
  	 *   taken into account only if message is sent instantly. Default is TRUE
  	 * 
  	 * @param mixed $recipients
  	 * @param string $subject
  	 * @param string $body
  	 * @param array $additional
  	 * @param string $method
  	 * @return mixed
  	 * @throws InvalidParamError
  	 */
  	static function send($recipients, $subject = '', $body = '', $additional = null, $method = ApplicationMailer::SEND_INSTANTNLY) {
  		if($recipients instanceof IUser) {
  			return self::sendToRecipient($recipients, $subject, $body, $additional, $method);
  		} elseif(is_array($recipients)) {
  			$sent = array();
  			
  			foreach($recipients as $recipient) {
  				$sent[] = self::sendToRecipient($recipient, $subject, $body, $additional, $method);
  			} // foreach
  			
  			return $sent;
  		} else {
  			throw new InvalidParamError('recipients', $recipients, 'Recipient should be an IUser instance or a list of users');
  		} // if
  	} // send
  	
  	/**
  	 * Send message to a single recipient
  	 * 
  	 * @param IUser $recipients
  	 * @param string $subject
  	 * @param string $body
  	 * @param array $additional
  	 * @param string $method
  	 * @return OutgoingMessage
  	 */
  	static private function sendToRecipient(IUser $recipient, $subject = '', $body = '', $additional = null, $method = ApplicationMailer::SEND_INSTANTNLY) {
      // we cannot send email to inactive user
      if ($recipient instanceof User && !$recipient->isActive()) {
        return false;
      } // if

  		$outgoing_message = new OutgoingMessage();
  		
  		$context = $additional && isset($additional['context']) && $additional['context'] ? $additional['context'] : null;
  		
  		if($context instanceof INotifierContext) {
  		  $outgoing_message->setParent($context);
  		} // if
  		
  		$outgoing_message->setSender(ApplicationMailer::getDefaultSender());
  		$outgoing_message->setRecipient($recipient);
  		$outgoing_message->setSubject($subject);
  		$outgoing_message->setBody($body);
      $outgoing_message->setCode(array_var($additional, 'subscription_code', null));
  		
  		if(is_array($additional)) {
  			if($context) {
  				if($context instanceof INotifierContext) {
            if($context instanceof IComments) {
              $outgoing_message->setContextId($context->getNotifierContextId());
            } // if
  				} elseif(is_string($context)) {
  					$outgoing_message->setContextId($context);
  				} else {
  					throw new InvalidParamError('context', $context, 'Message context is not valid instance of INotifierContext class or valid context string');
  				} // if
  			} // if
  			
  			if(isset($additional['sender']) && $additional['sender']) {
  				$outgoing_message->setSender($additional['sender']);
  			} // if
  			
  			if(isset($additional['attachments']) && is_foreachable($additional['attachments'])) {
  			  foreach($additional['attachments'] as $attachment_path => $attachment_name) {
            if(is_numeric($attachment_path)) {
              $outgoing_message->attachments()->attachFile($attachment_name, basename($attachment_name), 'application/octet-stream'); // Use file name
            } else {
              $outgoing_message->attachments()->attachFile($attachment_path, $attachment_name, 'application/octet-stream');
            } // if
  				} // foreach
  			} // if
  		} // if
  		
  		if($method === null) {
  			$method = $recipient instanceof User && $recipient->isLoaded() ? ConfigOptions::getValueFor('mailing_method', $recipient) : ConfigOptions::getValue('mailing_method');
  		} // if

      $outgoing_message->setMailingMethod($method);
      $outgoing_message->save();
  		
  		if($method == ApplicationMailer::SEND_INSTANTNLY) {
  			$outgoing_message->send(($additional && isset($additional['decorate']) ? (boolean) $additional['decorate'] : true));
  		} // if
  		
  		return $outgoing_message;
  	} // sendToRecipient
  	
  	/**
  	 * Send multiple messages as digest
  	 * 
  	 * @param array $messages
  	 * @return array
  	 */
  	static function sendDigest($messages) {
  		if(is_foreachable($messages)) {
	  		$by_recipient = array();
	  		foreach($messages as $message) {
	  			$email = $message->getRecipient()->getEmail();
	  			
	  			if(!isset($by_recipient[$email])) {
	  				$by_recipient[$email] = array();
	  			} // if
	  			
	  			$by_recipient[$email][] = $message;
	  		} // foreach
	  		
	  		foreach($by_recipient as $recipient_messages) {
	  			self::getAdapter()->sendDigest($recipient_messages);
	  		} // foreach
  		} // if
  	} // sendDigest
  	
  	// ---------------------------------------------------
  	//  Configuration Options
  	// ---------------------------------------------------
  	
  	/**
  	 * Returns true if connection settings are locked
  	 * 
  	 * @return boolean
  	 */
  	static function isConnectionConfigurationLocked() {
  	  return defined('MAILING_CONNECTION_CONFIGURATION_LOCKED') && MAILING_CONNECTION_CONFIGURATION_LOCKED;
  	} // isConnectionConfigurationLocked
  	
  	/**
  	 * Return true if mailing message configuration locked
  	 */
  	static function isMessageConfigurationLocked() {
  	  return defined('MAILING_MESSAGE_CONFIGURATION_LOCKED') && MAILING_MESSAGE_CONFIGURATION_LOCKED;
  	} // isMessageConfigurationLocked
  	
  	/**
  	 * Cached connetion type value
  	 *
  	 * @var string
  	 */
  	static private $connection_type = false;
  	
  	/**
  	 * Return connection type
  	 * 
  	 * @return string
  	 */
  	static function getConnectionType() {
  	  if(self::$connection_type === false) {
  	    if(ApplicationMailer::isConnectionConfigurationLocked()) {
  	      self::$connection_type = defined('MAILING_CONNECTION_TYPE') && MAILING_CONNECTION_TYPE ? MAILING_CONNECTION_TYPE : 'silent';
  	    } else {
  	      self::$connection_type = ConfigOptions::getValue('mailing');
  	    } // if
  	  } // if
  	  
  	  return self::$connection_type;
  	} // getConnectionType
  	
  	/**
  	 * Return SMTP connection parameters
  	 * 
  	 * @return array
  	 */
  	static function getSmtpConnectionParameters() {
  	  if(ApplicationMailer::isConnectionConfigurationLocked()) {
  	    return array(
    	    defined('MAILING_CONNECTION_SMTP_HOST') ? MAILING_CONNECTION_SMTP_HOST : '', 
    	    defined('MAILING_CONNECTION_SMTP_PORT') ? MAILING_CONNECTION_SMTP_PORT : '', 
    	    defined('MAILING_CONNECTION_SMTP_AUTHENTICATE') ? MAILING_CONNECTION_SMTP_AUTHENTICATE : false, 
    	    defined('MAILING_CONNECTION_SMTP_USERNAME') ? MAILING_CONNECTION_SMTP_USERNAME : '', 
    	    defined('MAILING_CONNECTION_SMTP_PASSWORD') ? MAILING_CONNECTION_SMTP_PASSWORD: '', 
    	    defined('MAILING_CONNECTION_SMTP_SECURITY') && MAILING_CONNECTION_SMTP_SECURITY ? MAILING_CONNECTION_SMTP_SECURITY : 'off', 
    	  );
  	  } else {
  	    $options = ConfigOptions::getValue(array(
    	    'mailing_smtp_host', 
    		  'mailing_smtp_port', 
    		  'mailing_smtp_authenticate',
    		  'mailing_smtp_username', 
    		  'mailing_smtp_password', 
    		  'mailing_smtp_security',
    	  ));
    	  
    	  return array(
    	    $options['mailing_smtp_host'], 
    	    $options['mailing_smtp_port'], 
    	    $options['mailing_smtp_authenticate'], 
    	    $options['mailing_smtp_username'], 
    	    $options['mailing_smtp_password'], 
    	    $options['mailing_smtp_security']
    	  );
  	  } // if
  	} // getSmtpConnectionParameters
  	
  	/**
  	 * Return native mailer options
  	 * 
  	 * @return string
  	 */
  	static function getNativeMailerOptions() {
  	  if(ApplicationMailer::isConnectionConfigurationLocked()) {
  	    return defined('MAILING_CONNECTION_NATIVE_OPTIONS') ? MAILING_CONNECTION_NATIVE_OPTIONS : '-oi -f %s';
  	  } else {
  	    return ConfigOptions::getValue('mailing_native_options');
  	  } // if
  	} // getNativeMailerOptions
  	
  	/**
  	 * Return email address and name that are used to set From email parameters
  	 * 
  	 * @return array
  	 */
  	static function getFromEmailAndName() {
  	  if(ApplicationMailer::isMessageConfigurationLocked()) {
        return array(
          defined('MAILING_MESSAGE_FROM_EMAIL') ? MAILING_MESSAGE_FROM_EMAIL : '', 
          defined('MAILING_MESSAGE_FROM_NAME') ? MAILING_MESSAGE_FROM_NAME : '', 
        );
      } else {
        return array(ConfigOptions::getValue('notifications_from_email'), ConfigOptions::getValue('notifications_from_name'));
      } // if
  	} // getFromEmailAndName
  	
  	/**
  	 * Cached force message from
  	 *
  	 * @var boolean
  	 */
  	static private $force_message_from = null;
  	
  	/**
  	 * Returns true if message from is forced
  	 * 
  	 * @return boolean
  	 */
  	static function getForceMessageFrom() {
  	  if(self::$force_message_from === null) {
  	    if(ApplicationMailer::isMessageConfigurationLocked()) {
    	    self::$force_message_from = defined('MAILING_MESSAGE_FROM_FORCE') && MAILING_MESSAGE_FROM_FORCE;
    	  } else {
    	    self::$force_message_from = (boolean) ConfigOptions::getValue('notifications_from_force');
    	  } // if
  	  } // if
  	  
  	  return self::$force_message_from;
  	} // getForceMessageFrom
  	
  	/**
     * Cached mark messages as bulk value
     *
     * @var boolean
     */
    static private $mark_messages_as_bulk = null;
    
    /**
     * Returns true if messages should be marked as bulk mail
     *
     * @return boolean
     */
    static function getMarkAsBulk() {
      if(self::$mark_messages_as_bulk === null) {
        if(ApplicationMailer::isMessageConfigurationLocked()) {
          self::$mark_messages_as_bulk = defined('MAILING_MESSAGE_MARK_AS_BULK') && MAILING_MESSAGE_MARK_AS_BULK;
        } else {
          self::$mark_messages_as_bulk = (boolean) ConfigOptions::getValue('mailing_mark_as_bulk');
        } // if
      } // if
      
      return self::$mark_messages_as_bulk;
    } // getMarkAsBulk
    
    /**
     * Set mark as bulk value
     * 
     * @param boolean $value
     */
    static function setMarkAsBulk($value) {
    	ConfigOptions::setValue('mailing_mark_as_bulk', $value);
    	self::$mark_messages_as_bulk = $value;
    } // setMarkAsBulk 
    
  }