<?php 
/*
* Copyright (c) 2009-2010 ATMAIL. All rights reserved
* See http://atmail.com/license.php for license agreement
*/

class Atmail_Mail_Storage_Writable_Maildir extends Zend_Mail_Storage_Writable_Maildir
{	
	public $mailStorageType = 'Maildir';
	public $dataSource = 'unknown';
	public $updateCacheStamp = 0;
	// protected $_files;
	protected $_messageClass = 'Atmail_Mail_Message_File';
	private $_sort = 'unknown';
 	private $_order = 'unknown';
	private $_defaultnamespaceName = 'Atmail_Mail_Storage_Writable_Maildir';
	private $_namespaceName = null;
	private $_session;
	private $_config;
	private $_cache = null;
	private $_filesFetched = false; // for Maildir same as connected because connect will automatically get _files
	private $_filesHeadersFetched = false;
	private $_connected = false;
	private $_foldersFetched = false;
	
	public function __construct($config)
	{
		if( !isset($config['dirname']) )
			throw new Atmail_Mail_Exception('Compulsory dirname not found in args');
		$this->_config = $config;
		if(isset($this->_config['namespaceName']))
			$this->_namespaceName = $this->_config['namespaceName'];
		else
			$this->_namespaceName = $this->_defaultnamespaceName;
		$this->_session = new Zend_Session_Namespace($this->_namespaceName); // session used as seperate persistent store for if caching disabled
		if(isset($this->_config['folder']))
			$this->_session->folder = $this->_config['folder']; //commonly used to open specific folder or change folder 
		elseif(isset($this->_session->folder))
			$this->_config['folder'] = $this->_session->folder;
		elseif( isset($this->_config['defaultFolder'])) {
			$this->_session->folder = $this->_config['defaultFolder'];
			$this->_config['folder'] = $this->_config['defaultFolder']; //_config not persistent but useful in other classes
		}
		//at this point either $this->_config['folder'] will have the default userConfig logon folder, or be null in which case class will open default folder  
	}
	
	private function freshConnect()
	{
		Zend_Registry::get('log')->info(__METHOD__ . ' called while ' . ($this->_connected?'connected':'not connected') . ' at the time');
		$this->_connected = true; //careful with moving this: the parent::_constructor recalled $this->connect() and if not true will loop/fail
	   	if(!isset($this->_config['folder']) && isset($this->_session->folder) )
			$this->_config['folder'] = $this->_session->folder;
		parent::__construct($this->_config);
		$this->_sort = 'unknown';
		$this->_order = 'unknown';  
		$this->_filesFetched = true;
		$this->_foldersFetched = true;
		$this->_filesHeadersFetched = false;
		$this->dataSource = 'fresh';
		$this->updateCacheStamp = time();
		$_filesC = count($this->_files);
		for( $a=0 ; $a<$_filesC ; $a++ ) //prepare _files for fluent access
		{
			$this->_files[$a]['uniqueid'] = $this->_files[$a]['uniq'];
			$this->_files[$a]['ordinalid'] = $a+1;
		}

		Zend_Registry::get('log')->info(__METHOD__ . ' finished');
	}

	public function connect()
	{
		if($this->_connected)
			return;
		$this->freshConnect();
	}
	
	// MAGIC FUNCTIONS
	////////////////////////////////////////////////////////////////////////////////
    
	public function current() //overloaded the class iterator to handle overloading getMessage to use uniqueId
	{
		return $this->getMessage( $this->getUniqueId($this->_iterationPos) );
	}
	
	public function getOrdinalIdByUniqueId($uniqueId)
	{
	   // BRAD: had to force the call to freshConnect() for the
	   // Unit Test to read the correct email. Not sure what's up here
		//if( !$this->_filesFetched )
			$this->freshConnect();
	    foreach($this->_files AS $_file)
			if($_file['uniqueid'] == $uniqueId) {
				return $_file['ordinalid'];
	        }
		throw new Atmail_Mail_Exception('No ordinalId found for uniqueId:' . $uniqueId);
	}
	
	public function getMessage($uniqueId)
	{
		//$this->connect();
		return parent::getMessage($this->getOrdinalIdByUniqueId($uniqueId));
	}
	
	public function copyMessage($uniqueId, $toFolder)
	{
		//$this->connect();
		return parent::copyMessage($this->getOrdinalIdByUniqueId($uniqueId), $toFolder);
	}
	
	public function removeMessage($uniqueId)
	{
		//$this->connect();
		$ordinalId = $this->getOrdinalIdByUniqueId($uniqueId);
		if(!$ordinalId)
			throw new Atmail_Mail_Exception('Message ( ' . $uniqueId . ':' . $ordinalId . ') not found.');
	   	parent::removeMessage($ordinalId);
		$_filesC = $this->countMessages();
		$foundAt = $_filesC;
		for( $a=0 ; $a<$_filesC ; $a++) 
		{
			if($this->_files[$a]['uniq'] == $uniqueId) //who knows y ZF callit uniq here and uniqueid in pop3
				$foundAt = $a;
			if( $a > $foundAt)
				$this->_messages[$a-1] = $this->_messages[$a];
			if( $a >= $foundAt && $a == ($_messagesC-1) ) //if found and at last message
				array_pop($this->_messages);
		}
	}
	
	public function getFolders($rootFolder = null)
	{
		return parent::getFolders($rootFolder);
	}   
	
	public function getCurrentFolder()
	{
		if( !isset($this->_currentFolder) )
			$this->connect();
		return parent::getCurrentFolder();
	}
	
	public function countMessages()
	{
		Zend_Registry::get('log')->info(__METHOD__ . ' called ');
		if( !$this->_filesFetched )
			$this->freshConnect(); //can only be if not connected so will reconnect (fresh to make handle bad _connected == true) and fetch _files
		return count($this->_files);
	}
	
	public function getFreshMessagesWithHeaders()
	{
		$this_filesC = $this->countMessages(); //will handle necessary conecting
		for( $a=0 ; $a<$this_filesC ; $a++)
		{
			$messageClassInstance = new $this->_messageClass(array('file'  => $this->_files[$a]['filename'], 'flags' => $this->_files[$a]['flags']));
			if( !isset($this->_files[$a]['ordinalid']) )
				$this->_files[$a]['ordinalid'] = 0;
			$this->_files[$a]['_headers'] = $messageClassInstance->processHeaders( array('uniq' => $this->_files[$a]['uniq'], 
																						 'uniqueid' => $this->_files[$a]['uniq'],
																						 'ordinalid' => $this->_files[$a]['ordinalid']) );
			/* 	IMPORTANT: headers are stored in similar structure to when _files magically referenced as a message class instance 
				NB incorrect access can generate strange sesults like thinking you are accessing this array (possibly out of date) 
				when PHP magically serving a fresh class instance.
			*/
		}
		$this->_filesHeadersFetched = true;
		//TODO: toplines  
	}
	
	public function getMessagesHeaders()
	{
		if( !$this->_filesHeadersFetched )
			$this->getFreshMessagesWithHeaders();
		return $this->_files;
	}
	
	public function getMessageHeaders($uniqueId)
	{
		//TODO: toplines
		if($this->_filesHeadersFetched) {
			$_filesC = $this->countMessages();
			for( $a=0 ; $a<$_filesC ; $a++ )
			{
				if( $this->_files[$a]['uniqueid'] == $uniqueId)
					return $this->_files[$a]['_headers'];
			}
		} else {
			return $this->getMessage($uniqueId)->processHeaders( array('uniqueid' => $uniqueId, 
																	   'ordinalid' => 'notimplimented cos needs to build entire message list to get ordinalid') );
		}
	}
	
	
	// CACHE FUNCTIONS
	////////////////////////////////////////////////////////////////////////////////
	
	public static function createCache($config)
	{
		$namespaceName = isset($config['namespaceName'])?$config['namespaceName']:self::_defaultnamespaceName;
		$session = new Zend_Session_Namespace($namespaceName);
		if(!isset($config['folder']))
		{
			if( isset($session->folder) )
				$config['folder'] = $session->folder;
			elseif( isset($config['defaultFolder']) )
				$config['folder'] = $config['defaultFolder']; //_config not persistent but useful in other classes
			else
				$config['folder'] = 'default';
		}
		if( !isset($config['tmpFolderBaseName']) )
			throw new Atmail_Mail_Exception('Compulsory tmpFolderBaseName not found in args');
		$cache_dir = $config['tmpFolderBaseName'];
		if(!is_dir($cache_dir))
			mkdir($cache_dir);
		$safeAccount = simplifyString($config['Account']);
		$accountFirstLetter = substr($safeAccount,0, 1);
		$accountSecondLetter = substr($safeAccount,1, 1);
		$cache_dir .= DIRECTORY_SEPARATOR . $accountFirstLetter;
		if(!is_dir($cache_dir))
			mkdir($cache_dir);
		$cache_dir .= DIRECTORY_SEPARATOR . $accountSecondLetter;
		if(!is_dir($cache_dir))
			mkdir($cache_dir);
		$cache_dir .= DIRECTORY_SEPARATOR . $safeAccount;
		if(!is_dir($cache_dir))
			mkdir($cache_dir);
		$cache_dir .= DIRECTORY_SEPARATOR . $namespaceName;
		if(!is_dir($cache_dir))
			mkdir($cache_dir);
		return $config;
	}
	
	public static function tryLoadFromCache($config)
	{
		$config = self::createCache($config);
		$namespaceName = isset($config['namespaceName'])?$config['namespaceName']:self::_defaultnamespaceName;
		$session = new Zend_Session_Namespace($namespaceName);
		if( !isset($session->folder) )
			return false;
		$folder = isset($config['folder'])?$config['folder']:$session->folder;
		$session->folder = $folder;
		$cached = Atmail_Cache::fetch(Atmail_Cache::TYPE_MAIL, simplifyString($folder) );
		//if($cached !== false)	
		//	$cached->_connected = false; //commented out cos Maildir can save and reload as connected
		return $cached;
	}
	
	/**
	* Pseudo send recieve
	* does light dir hit and culls or adds messages to the view. 
	*/
	public function updateCache()
	{
		//This will also update new messages with get and process headers if _filesHeadersFetched in old cache cos 90% usercase is list, 
		// else would loose all cache data and therefore value, should make more generic if usage expands. (e.g. fetched flag for each message)
		//make copy of old then reconnect to storage and compare
		//only update what was cached, i.e., connect, then if !_filesFetched then dont update list , if !_filesHeadersFetched then dont update headers
		//consider preserving cached sort and order for performance (minor for short lists)
		if( $this->_filesFetched ) { //catch possible cache load where files werent even fetched (never actually connected to get files and folders) (possible pop3 local usage and start on INBOX therefore dont need to ocnnect yet before caching)
			$oldCache_files = $this->_files; //in case needed for _filesHeadersFetched 
			$oldCache_filesHeadersFetched = $this->_filesHeadersFetched;
			$this->freshConnect();
			//at this point _filesFetched = true;
			if( $oldCache_filesHeadersFetched ) { // only update headers if we have cached headders to benefit from
				$fresh_filesC = count($this->_files);
			    $oldCache_filesC = count($oldCache_files);
				for( $a=0 ; $a<$fresh_filesC ; $a++ ) {
					//go through each new message trying to copy cached data across
					$foundInCache = false;
					for( $b=0 ; $b<$oldCache_filesC ; $b++ ) {
						 if( $this->_files[$a]['uniqueid'] == $oldCache_files[$b]['uniqueid']) {
							$this->_files[$a]['_headers'] = $oldCache_files[$b]['_headers'];//NB only copy headers across cos ordinal may have changed
							$foundInCache = true;
							break; //break out of searching old cache once found
						 }
					}
					if( !$foundInCache )
					{                                                                               
						$this->_files[$a]['uniqueid'] = $this->_files[$a]['uniq'];
						$this->_files[$a]['ordinalid'] = $a+1;
						$this->_files[$a]['_headers'] = $this->getMessage($this->_files[$a]['uniqueid'])->processHeaders( array('uniqueid' => $this->_files[$a]['uniqueid'], 
																																'ordinalid' => $this->_files[$a]['ordinalid']) ); 
					}
				}
				$this->_filesHeadersFetched = true;
			}
			$this->dataSource = 'refreshed';
		} 
	}
	
	public function saveCache()
	{
		Zend_Registry::get('log')->info('saving ' . $this->getCurrentFolder() . ' to cache');
		$this->dataSource = 'cached'; 
		return Atmail_Cache::store(Atmail_Cache::TYPE_MAIL, simplifyString($this->getCurrentFolder()), $this );
	}	
	
	// SORT FUNCTIONS
	////////////////////////////////////////////////////////////////////////////////

	public function sort($sort, $order)
	{
		$this->getMessagesHeaders();
		//handle folders with no messages
		if($this->countMessages() < 2)
		{
			$this->_sort = $sort;
			$this->_order = $order;
			return;
		}	
		if($this->_sort != $sort)
		{
			switch($sort)
			{
				case 'date':
					$sortFunction = 'sortByDate';
				    break;
				case 'subject':
					$sortFunction = 'sortBySubject'; 
					break;
				case 'to':
					$sortFunction = 'sortByTo';
				    break;
				case 'from':
					$sortFunction = 'sortByFrom'; 
					break;
				case 'size':
					$sortFunction = 'sortBySize'; 
					break;  
				case 'hasattachments':
					$sortFunction = 'sortByHasAttachments'; 
					break;  
				default:
					$sortFunction = 'sortByDate';
			}
			//$sortFunction = 'sortBySize'; //overide
			usort($this->_files, array($this, $sortFunction));
			$this->_sort = $sort;
			$this->_order = 'asc'; //N.B. once usort is complete, array will be an asc order
		}
		if($this->_order != $order)
		{
			$this->_files = array_reverse($this->_files);
			$this->_order = $order;
		}
	}
	
	private function sortBySubject($a,$b)
	{
		$sortable = array( strtolower($a['_headers']['subject']),strtolower($b['_headers']['subject']) );
        $sorted = $sortable;
        sort($sorted);   
        return ($sorted[0] == $sortable[0]) ? -1 : 1;
	}
	
	private function sortByDate($a,$b)
	{
		$sortable = array($a['_headers']['epoch'],$b['_headers']['epoch']);
        $sorted = $sortable;
        sort($sorted);   
        return ($sorted[0] == $sortable[0]) ? -1 : 1;
	}
	
	private function sortByTo($a,$b)
	{
		$sortable = array( strtolower($a['_headers']['to']),strtolower($b['_headers']['to']) );
		$sorted = $sortable;
        sort($sorted);   
        return ($sorted[0] == $sortable[0]) ? -1 : 1;
	}
	
	private function sortByFrom($a,$b)
	{
		$sortable = array( strtolower($a['_headers']['from']),strtolower($b['_headers']['from']) );
		$sorted = $sortable;
        sort($sorted);   
        return ($sorted[0] == $sortable[0]) ? -1 : 1;
	}
	
	private function sortBySize($a,$b)
	{
		$sortable = array($a['_headers']['sizeraw'],$b['_headers']['sizeraw']);
        $sorted = $sortable;
        sort($sorted);   
        return ($sorted[0] == $sortable[0]) ? -1 : 1;
	}
	
	private function sortByHasAttachments($a,$b)
	{
		$sortable = array($a['_headers']['hasattachments'],$b['_headers']['hasattachments']);
        $sorted = $sortable;
        sort($sorted);   
        return ($sorted[0] == $sortable[0]) ? -1 : 1;
	}
	
	/**
	* Search functions
	* Some functions only exist as a virtue of the class type so care should be taken when calling and make sure of class type before doing so
	* Another approach can be to implement empty functions to simulate complete implementation of similar class types (mail storage class)
	*/

	private function optimizeCurrentIndex()
	{
		$folder = $this->getCurrentFolder();
		$indexPath = $this->_config['dirname'] . DIRECTORY_SEPARATOR . 'search' . DIRECTORY_SEPARATOR . $folder;
		$log = Zend_Registry::get('log');
		$log->info('beginning index optimization.');
		try {
			$index = Zend_Search_Lucene::open($indexPath); 
		} catch (Zend_Search_Lucene_Exception $e) {
		    $index = Zend_Search_Lucene::create($indexPath);
			$log->info('Index did exist so created: ' . $indexPath);
			return;
		}
		$index->optimize();
	}   

	public function indexCurrentFolder()
	{
		$startTime = microtime(true);
		$log = Zend_Registry::get('log');
		$indexPath = $this->_config['dirname'] . DIRECTORY_SEPARATOR . 'search' . DIRECTORY_SEPARATOR . $this->getCurrentFolder();
		try {
			$index = Zend_Search_Lucene::open($indexPath); 
		} catch (Zend_Search_Lucene_Exception $e) {
		    $index = Zend_Search_Lucene::create($indexPath);
			$log->info('Index did exist so created: ' . $indexPath);
		}
		set_time_limit(300);

		$log->info('Pre-optimizing: ' . $indexPath);
		$index->optimize(); //optomize index before processing to attempt to avoid corrupted index
        
		$log->info('Indexing: ' . $indexPath);
		$log->info('Skipping already indexed messages: ' . $indexPath);
		$messagesIndexed = 0;
		$messagesC = $this->countMessages();
		for( $a=0 ; $a<$messagesC ; $a++ )
		{
			$currentMessageNumber = $a;
			$message = $this->getMessage($this->_files[$a]['uniq']);
			set_time_limit(300);
			$timerStart = microtime(true);
			if(++$messagesIndexed > 199)
			{
				$log->info($messagesIndexed . ' messages indexed into memory, beginning commit');
				$index->optimize();
				$log->info('Commit complete');
				break;
			}
			//if(microtime(true) - $startTime > 25)
			//{
			//	$log->info('About to run out of time so cleaning up and processing index');
			//	break;
			//}
			$uniqueId = $this->_files[$a]['uniq'];
			//$docIds  = $index->termDocs(new Zend_Search_Lucene_Index_Term($uniqueId, 'uniqueId'));

			//if(count($docIds) > 1)
			if($index->termDocs(new Zend_Search_Lucene_Index_Term($uniqueId, 'uniqueId')))
			{
 				$messagesIndexed--;
				continue;
			}
   			$log->info('About to index: ' . $this->getCurrentFolder() . ': ' . $uniqueId);
			$headers = $message->processHeaders();
			// if message larger than available time to process then commit ready to start with beg message next pass
			//$viableSecondsLeft = 60 - (microtime(true) - $startTime);
			//if(($headers['sizeraw']/1000) > $viableSecondsLeft*100)
			//{
				//TODO: consider skipping messages too large to index, or store them with cruncated content;
			//	$log->info('Message too big (' . $headers['size'] . ') to handle in time left (' . round($viableSecondsLeft) . ') so deferring to next pass and comitting now.');
			//	break;
			//}
			$bodyContents = '';
			$attachmentContents = '';
			$attachmentNames = '';
			$partsList = $message->recurseParts(null,0);
			foreach($partsList AS $part)
			{
				$isInline = true;
				$partHeaders = $part['part']->getHeaders();
				if(
					isset($partHeaders['content-type']) 
					&& 
					( 
						!(
							stripos($partHeaders['content-type'],'filename') === false 
							&& 
							stripos($partHeaders['content-type'],'name') === false
						) 
					) 
				)
				{
					$isInline = false; // inline body parts will not have a file name, only attachments have a filename
					if(preg_match('/ame="(.+?)"/', $partHeaders['content-type'], $matches))
					{
						$filename = $matches[1];
						$attachmentNames .= $filename . ' ';
					}
				}
				//defined part name can be called name, Name, Filename
				//TODO: indexing of files not just html and text
				$fileNameExtension = '.notindexable';
				if(isset($partHeaders['content-type']))
				{
					if(strpos($partHeaders['content-type'], 'text/html') === 0)
					{
						include_once('class.html2text.inc');
						//TODO: consider converting character type to utf-8 before processing
						$converter = new html2text($part['part']->getContent());
						$content = quoted_printable_decode($converter->get_text());
						//$content = $converter->get_text();
						if($isInline)
							$bodyContents .= $content;
						else
							$attachmentContents .= $content;
					}
					elseif(strpos($partHeaders['content-type'], 'text/plain') === 0)
					{	
						$content = quoted_printable_decode($part['part']->getContent());
						if($isInline)
							$bodyContents .= $content;
						else
							$attachmentContents .= $content;
					}
					elseif(strpos($partHeaders['content-type'], 'text/pdf') === 0 || $fileNameExtension == 'pdf')
					{
						//dumy elseif for handling other content type indexing
					}
				}	
				else
				{
					//normally if content-type not set it is because it is a multipart container for other parts
					//possible very old email format with not multipart, then inline
					//echo '<font color="red">content-type: <b>Not Set</b></font><br />';
					//$bodyContents .= $content;
					//echo $part['part']->getContent();
				}

			}
			$doc = new Zend_Search_Lucene_Document(); 
			$doc->addField(Zend_Search_Lucene_Field::Keyword('uniqueId', $uniqueId));        
			$doc->addField(Zend_Search_Lucene_Field::Text('from', utf8_encode($headers['from']), 'utf-8')); 
			$doc->addField(Zend_Search_Lucene_Field::Text('to', utf8_encode($headers['to']), 'utf-8')); 
			$doc->addField(Zend_Search_Lucene_Field::Text('subject', utf8_encode($headers['subject']), 'utf-8')); 
			$doc->addField(Zend_Search_Lucene_Field::Keyword('epoch', $headers['epoch'])); 
			$doc->addField(Zend_Search_Lucene_Field::Keyword('sizeraw', $headers['sizeraw'])); 
			$doc->addField(Zend_Search_Lucene_Field::Unstored('bodyContents', utf8_encode($bodyContents), 'utf-8')); 
			$doc->addField(Zend_Search_Lucene_Field::Unstored('attachmentContents', utf8_encode($attachmentContents), 'utf-8' )); 
    		$doc->addField(Zend_Search_Lucene_Field::Text('attachmentNames', utf8_encode($attachmentNames), 'utf-8' )); //Is this really useful or should we drop 
			//$log->info(round((microtime(true)-$timerStart),0) . ' seconds to prep document for index: ' . $uniqueId);
			//$timerStart = microtime(true);
			$index->addDocument($doc);
			//$log->info(round((microtime(true)-$timerStart),0) . ' seconds to add document to index: ' . $uniqueId);
			$log->info('Added to index: ' . $this->getCurrentFolder() . ': ' . $uniqueId);

		}
		//$indexSize = $index->count(); 
		//$documents = $index->numDocs();
		$log->info('Finished indexing: ' . $this->getCurrentFolder());
		//echo "Index currently contains: " . $indexSize . ' documents, ' . $documents . ' not deleted<br />';
	}

	public function search( $searchArray )
	{
		// folder/(possible any resource in future) (only first record folder used)
		// field = list of accepted fields (currently: from, to, subject, epoch, sizeraw, flag, bodyContent)
		// modifier = exactly, contains, beginswith, endswith, greaterthan, lessthan, not (default = contains) (modifiers may be field type specific)
		// compoundWithNext = and, or (default or, and only if there is a next record)
	    // value = value 
		$indexPaths = array();
		$indexBasePath = $this->_config['dirname'] . DIRECTORY_SEPARATOR . 'search' . DIRECTORY_SEPARATOR;
		if($searchArray[0]['folder'] != 'all')
			$indexPaths = array($indexBasePath . $searchArray['folder']);
        else {
			$folders = $this->getFolders();
			// /ddie($folders);
			foreach($folders as $folder)
				if( $folder->getLocalName() != 'INBOX' )
					$indexPaths[] = $indexBasePath . $folder->getLocalName();//TODO: confirm wether all folders returned including sub folders or if have to recurse sub folders
		}
		// ddie($indexPaths, __METHOD__ . '#' . __LINE__ . ': $this');
		
		
		$results = array();
		$resultsIndex = 0;
		foreach($indexPaths AS $indexPath) {
			try {
				$index = Zend_Search_Lucene::open($indexPath); 
				$query = new Zend_Search_Lucene_Search_Query_Boolean(); 
			 	foreach($searchArray as $searchTerm) {
					$term = new Zend_Search_Lucene_Index_Term('*' . strtolower($searchTerm['value']) . '*', $searchTerm['field']); 
					$subquery = new Zend_Search_Lucene_Search_Query_Wildcard($term); 
					$query->addSubquery($subquery, true); 
				} 
				try {
					$hits  = $index->find($query);
				} catch (Zend_Search_Lucene_Search_QueryParserException $e) { 
				    echo "Query syntax error: " . $e->getMessage() . "\n"; 
				}
				foreach($hits AS $hit) {
					$results['score'][$resultsIndex] = $hit->score;
					$results['uniqueId'][$resultsIndex] = $hit->uniqueId;
					$results['to'][$resultsIndex] = $hit->to;
					$results['from'][$resultsIndex] = $hit->from;
					$results['subject'][$resultsIndex] = $hit->subject;
					$results['epoch'][$resultsIndex] = $hit->epoch;
					$results['sizeraw'][$resultsIndex++] = $hit->sizeraw;
				}  
			} catch (Zend_Search_Lucene_Exception $e) {
			    Zend_Registry::get('log')->info('skipping failed search on index: ' . $indexPath);
			}
		}                                     
		//sort results
		if(isset($results['epoch']) && count($results['epoch']) > 1)
		array_multisort(
			$results['epoch'], SORT_NUMERIC, SORT_DESC, 
			$results['score'], 
			$results['uniqueId'], 
			$results['from'], 
			$results['to'], 
			$results['subject'], 
			$results['sizeraw']
		);
		//normalize array for fluent access
		$searchResults = array();
		foreach($results AS $key => $valueArray)
			for($a=0 ; $a<$resultsIndex ; $a++)
				$searchResults[$a][$key] = $results[$key][$a];
	    return $searchResults;  
	}
   
	/* other code snippets we will prob need as we flesh out the app */
	//$query = Zend_Search_Lucene_Search_QueryParser::parse($queryString); // string search syntax     
	//$hits  = $index->find($query, 'epoch', SORT_NUMERIC, SORT_DESC); //sort syntax
	//$highlightedHTML = $query->highlightMatches($sourceHTML);
	//$query = new Zend_Search_Lucene_Search_Query_MultiTerm(); 
	//new Zend_Search_Lucene_Search_Query_Wildcard($pattern);
	//$subquery1 = new Zend_Search_Lucene_Search_Query_Wildcard($pattern); 
	//$subquery2 = new Zend_Search_Lucene_Search_Query_MultiTerm(); 
	//$subquery2->addTerm(new Zend_Search_Lucene_Index_Term('word4', 'author')); 
	//$subquery2->addTerm(new Zend_Search_Lucene_Index_Term('word5', 'author')); 
	//$subquery3 = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term('word6')); 
	//$query->addSubquery($subquery1, true  /* required */); 
	//$query->addSubquery($subquery2, null  /* optional */); 
	//$query->addSubquery($subquery3, false /* prohibited */); 
	//$hits  = $index->find($query); 
	//$term = new Zend_Search_Lucene_Index_Term('' . $value . '', $requestParams['field'][$key]);
	//$query->addTerm($term, true);
	//$query->addTerm(new Zend_Search_Lucene_Index_Term('word1'), true); 
	//$query->addTerm(new Zend_Search_Lucene_Index_Term('word2', 'author'), null); 
	//$query->addTerm(new Zend_Search_Lucene_Index_Term('word3'), false); 
	//limit result set
	//$currentResultSetLimit = Zend_Search_Lucene::getResultSetLimit(); 
	//echo '$currentResultSetLimit: ' . $currentResultSetLimit;
	//Zend_Search_Lucene::setResultSetLimit(0);
}