<?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/Mime/Headers/UnstructuredHeader.php';
//@require 'Swift/Mime/HeaderEncoder.php';
//@require 'Swift/Mime/ParameterizedHeader.php';
//@require 'Swift/Encoder.php';

/**
 * An abstract base MIME Header.
 * @package Swift
 * @subpackage Mime
 * @author Chris Corbyn
 */
class Swift_Mime_Headers_ParameterizedHeader
  extends Swift_Mime_Headers_UnstructuredHeader
  implements Swift_Mime_ParameterizedHeader
{
  
  /**
   * The Encoder used to encode the parameters.
   * @var Swift_Encoder
   * @access private
   */
  private $_paramEncoder;
  
  /**
   * The parameters as an associative array.
   * @var string[]
   * @access private
   */
  private $_params = array();
  
  /**
   * RFC 2231's definition of a token.
   * @var string
   * @access private
   */
  private $_tokenRe;
  
  /**
   * Creates a new ParameterizedHeader with $name.
   * @param string $name
   * @param Swift_Mime_HeaderEncoder $encoder
   * @param Swift_Encoder $paramEncoder, optional
   */ 
  public function __construct($name, Swift_Mime_HeaderEncoder $encoder,
    Swift_Encoder $paramEncoder = null)
  {
    $this->setFieldName($name);
    $this->setEncoder($encoder);
    $this->_paramEncoder = $paramEncoder;
    $this->initializeGrammar();
    $this->_tokenRe = '(?:[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+)';
  }
  
  /**
   * Get the type of Header that this instance represents.
   * @return int
   * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
   * @see TYPE_DATE, TYPE_ID, TYPE_PATH
   */
  public function getFieldType()
  {
    return self::TYPE_PARAMETERIZED;
  }
  
  /**
   * Set the character set used in this Header.
   * @param string $charset
   */
  public function setCharset($charset)
  {
    parent::setCharset($charset);
    if (isset($this->_paramEncoder))
    {
      $this->_paramEncoder->charsetChanged($charset);
    }
  }
  
  /**
   * Set the value of $parameter.
   * @param string $parameter
   * @param string $value
   */
  public function setParameter($parameter, $value)
  {
    $this->setParameters(array_merge($this->getParameters(), array($parameter => $value)));
  }
  
  /**
   * Get the value of $parameter.
   * @return string
   */
  public function getParameter($parameter)
  {
    $params = $this->getParameters();
    return array_key_exists($parameter, $params)
      ? $params[$parameter]
      : null;
  }
  
  /**
   * Set an associative array of parameter names mapped to values.
   * @param string[]
   */
  public function setParameters(array $parameters)
  {
    $this->clearCachedValueIf($this->_params != $parameters);
    $this->_params = $parameters;
  }
  
  /**
   * Returns an associative array of parameter names mapped to values.
   * @return string[]
   */
  public function getParameters()
  {
    return $this->_params;
  }
  
  /**
   * Get the value of this header prepared for rendering.
   * @return string
   */
  public function getFieldBody() //TODO: Check caching here
  {
    $body = parent::getFieldBody();
    foreach ($this->_params as $name => $value)
    {
      if (!is_null($value))
      {
        //Add the parameter
        $body .= '; ' . $this->_createParameter($name, $value);
      }
    }
    return $body;
  }
  
  // -- Protected methods
  
  /**
   * Generate a list of all tokens in the final header.
   * This doesn't need to be overridden in theory, but it is for implementation
   * reasons to prevent potential breakage of attributes.
   * @return string[]
   * @access protected
   */
  protected function toTokens($string = null)
  {
    $tokens = parent::toTokens(parent::getFieldBody());
    
    //Try creating any parameters
    foreach ($this->_params as $name => $value)
    {
      if (!is_null($value))
      {
        //Add the semi-colon separator
        $tokens[count($tokens)-1] .= ';';
        $tokens = array_merge($tokens, $this->generateTokenLines(
          ' ' . $this->_createParameter($name, $value)
          ));
      }
    }
    
    return $tokens;
  }
  
  // -- Private methods
  
  /**
   * Render a RFC 2047 compliant header parameter from the $name and $value.
   * @param string $name
   * @param string $value
   * @return string
   * @access private
   */
  private function _createParameter($name, $value)
  {
    $origValue = $value;
    
    $encoded = false;
    //Allow room for parameter name, indices, "=" and DQUOTEs
    $maxValueLength = $this->getMaxLineLength() - strlen($name . '=*N"";') - 1;
    $firstLineOffset = 0;
    
    //If it's not already a valid parameter value...
    if (!preg_match('/^' . $this->_tokenRe . '$/D', $value))
    {
      //TODO: text, or something else??
      //... and it's not ascii
      if (!preg_match('/^' . $this->getGrammar('text') . '*$/D', $value))
      {
        $encoded = true;
        //Allow space for the indices, charset and language
        $maxValueLength = $this->getMaxLineLength() - strlen($name . '*N*="";') - 1;
        $firstLineOffset = strlen(
          $this->getCharset() . "'" . $this->getLanguage() . "'"
          );
      }
    }
    
    //Encode if we need to
    if ($encoded || strlen($value) > $maxValueLength)
    {
      if (isset($this->_paramEncoder))
      {
        $value = $this->_paramEncoder->encodeString(
          $origValue, $firstLineOffset, $maxValueLength
          );
      }
      else //We have to go against RFC 2183/2231 in some areas for interoperability
      {
        $value = $this->getTokenAsEncodedWord($origValue);
        $encoded = false;
      }
    }
    
    $valueLines = isset($this->_paramEncoder) ? explode("\r\n", $value) : array($value);
    
    //Need to add indices
    if (count($valueLines) > 1)
    {
      $paramLines = array();
      foreach ($valueLines as $i => $line)
      {
        $paramLines[] = $name . '*' . $i .
          $this->_getEndOfParameterValue($line, $encoded, $i == 0);
      }
      return implode(";\r\n ", $paramLines);
    }
    else
    {
      return $name . $this->_getEndOfParameterValue(
        $valueLines[0], $encoded, true
        );
    }
  }
  
  /**
   * Returns the parameter value from the "=" and beyond.
   * @param string $value to append
   * @param boolean $encoded
   * @param boolean $firstLine
   * @return string
   * @access private
   */
  private function _getEndOfParameterValue($value, $encoded = false, $firstLine = false)
  {
    if (!preg_match('/^' . $this->_tokenRe . '$/D', $value))
    {
      $value = '"' . $value . '"';
    }
    $prepend = '=';
    if ($encoded)
    {
      $prepend = '*=';
      if ($firstLine)
      {
        $prepend = '*=' . $this->getCharset() . "'" . $this->getLanguage() .
          "'";
      }
    }
    return $prepend . $value;
  }
  
}
