<?php

class Atmail_Provider_Voicemail_Twilio extends Atmail_Provider_Voicemail_Abstract
{


	/* 
		For purposes of proof of concept we need a 3rd party service we can query for voice mails. 
		Twilio is suitable, except that they dont house the VM info for us, they want to call a partner script to 
		log the messages (they only host the VM file) so we will need to impliment a mini provider server to 
		answer queries for current VM, get, delete etc.
		
		N.B. with RAD parts of the client server seperation concept have become merger due to implimenting a virtual service (MySQL so made sense to talk to "server" via SQL layer for speed of development).
		Obviously in final implimentation of a real service, what has been learned in this process must be applied into a cleanly seperated client server model
		where Server will do all the communication to the external service, and client is the local cached/archived data acquired from such server layer 
		e.g. SWL table must be split or modded to server client purposes only (cached/archived VM content, transcriptions, readFlag etc.)

	*/
	private $_tableName = 'Virtualcarrier1servermessages';
	public $fieldMap = array('oid' => 'message_id', 'from' => 'message_from', 'to' => 'message_to', 'received' => 'message_date'); //maps public field names to internal field names
	
	public function __construct( Array $configArray )
	{
		
		$this->dbAdapter = Zend_Registry::get('dbAdapter');
		$this->PhoneNumber = $configArray['PhoneNumber'];
		$this->Account = $this->_config['Account'];
//Zend_Registry::get('log')->debug( "\n" . print_r( $this->Account, true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$this->Account \n");		
		if( !parent::__construct($configArray) )
			return false;
		
	}
	
	public function add( Array $args )
	{
		$requiredArgs = array('from', 'to', 'data');
		foreach( $requiredArgs as $requiredArg)
			if( !array_key_exists($requiredArg, $args) )
				throw new Exception('Error: Required argument missing: ' . $requiredArg);
		
		$from = $this->dbAdapter->quote($args['from']);
		$to = $this->dbAdapter->quote($args['to']);
		$data = mysql_real_escape_string($args['data']);
		$q = sprintf("INSERT INTO `" . $this->_tableName . "` (`message_date` ,`message_from` ,`message_to` ,`contentMP3`) " . 
			"VALUES (NOW(), %s, %s, '%s' )",$from, $to, $data);
		
		$this->dbAdapter->query($q);
		
		return array_merge($args, array("response" => array("message" => "Voicemail added."),"status" => "success") );
		
	}

	public function getList( Array $args = array() )
	{
        
		//Zend_Registry::get('log')->debug( "\n" . print_r($args, true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$args \n");
		if( empty($args['sort']) || !is_array($args['sort']) )
			$orderClauses = "message_id ASC";
		else
		{

			$sortC = count($args['sort']);
			$a = 1;
			foreach( $args['sort'] as $orderClause )
			{
			
				$orderClauses .= $this->mapToInternal($orderClause[0]) . ' ' . $orderClause[1];
				if( $a++ < $sortC )
					$orderClauses .= ", ";
			
			}

		}
		$whereExtra = '';
		if( !empty($args['where']) )
			$whereExtra .= " AND " . $args['where'];
		if( !empty($args['from']) )
		{
			if( !is_array($args['from']) )
				$args['from'] = array($args['from']);
			$whereExtra .= " AND (";
			foreach( $args['from'] as $from )
			{
				$whereExtra .= "message_from = " . $this->dbAdapter->quote($from) . " OR ";
			}
			$whereExtra .= " FALSE )";
			
		}
			
		
		$q = "SELECT v.*, UNIX_TIMESTAMP(`message_date`) AS 'message_date' FROM Virtualcarrier1servermessages AS v WHERE message_to = " . $this->dbAdapter->quote($this->PhoneNumber) . $whereExtra . " ORDER BY " . $orderClauses;
//Zend_Registry::get('log')->debug( __METHOD__ . ' #' . __LINE__ . ': $q=' . $q);
		$rows = $this->dbAdapter->fetchAll($q);
		foreach( $rows as &$row )
		{
			
			$row['MessageDateISO8601'] = date('c',$row['message_date']);
			$row['message_date'] = date('r',$row['message_date']);
			foreach( $this->fieldMap as $public => $internal )
			{
				if( $public != $internal )
				{
					$row[$public] = $row[$internal];
					unset($row[$internal]);
				}
			}
			unset($row['sid']);
			unset($row['contentOgg']);
			unset($row['contentMP3']);
		}
		return array_merge($args, array("status" => "success", "totalNumberOfItems" => count($rows), "page" => 1, "itemsPerPage" => count($rows), "serviceData" => $rows) );
		
	}

	public function getSummary( Array $args = array( 'groupBy' => 'from', 'groupOrder' => 'ASC') )
	{
		
		//N.B. For proof of contept parts of the provider client and local storge are merged (will ultimately be seperated.)
		//Gets grouped summary of voicemail showing total vms from each sender and content of last message
		//For proof of concept this provider will directly query the provider every time with no local storage and proceesing
		//In final structure, provider is assumed to have limited and slow access, and long term storage not guaranteed, so cache all data locally
		//Order will ultimately obviously be by display field (First Name Last Name, and not phone number if matched (possible with left joining contacts on local store of VM))
		//Zend_Registry::get('log')->debug( "\n" . print_r($args, true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$args \n");
		
		if( empty($args['searchFuzzy']) )
		{
			$q = "SELECT 
					(SELECT COUNT(message_id) FROM Virtualcarrier1servermessages WHERE message_from = vSearch.message_from AND message_to = vSearch.message_to) AS VMCount, 
					vLatest.*, UNIX_TIMESTAMP(vLatest.message_date) AS 'message_date', 
					Abook.UserFirstName, Abook.UserLastName
					FROM Virtualcarrier1servermessages AS vSearch 
					LEFT JOIN Virtualcarrier1servermessages AS vLatest ON vSearch.message_from = vLatest.message_from
					LEFT JOIN Abook ON (
						vLatest.message_from = UserHomePhone || 
						vLatest.message_from = UserHomeMobile || 
						vLatest.message_from = UserWorkPhone || 
						vLatest.message_from = UserWorkMobile
					) AND Account = ?
					WHERE 
					vLatest.message_id=(SELECT MAX(message_id) from Virtualcarrier1servermessages WHERE message_from = vSearch.message_from)
					AND vSearch.message_to=? 
					GROUP BY vSearch.message_from 
					ORDER BY message_date DESC
			";
			
		}
		else
		{
            
			foreach( array('vSearch.message_from', 'vSearch.message_to', 'vSearch.transcription') as $v )
				$searchTerms[] = "$v LIKE " . $this->dbAdapter->quote('%' . $args['query'] . '%');
			$searchString = implode(" OR ", $searchTerms);
			
			$q = "SELECT 
					DISTINCT( CONCAT(vSearch.message_from, vSearch.message_to) ), 
					(SELECT COUNT(message_id) FROM Virtualcarrier1servermessages WHERE message_from = vSearch.message_from AND message_to = vSearch.message_to) AS VMCount, 
					vLatest.*, UNIX_TIMESTAMP(vLatest.message_date) AS 'message_date', 
					Abook.UserFirstName, Abook.UserLastName
					FROM Virtualcarrier1servermessages AS vSearch
					LEFT JOIN Virtualcarrier1servermessages AS vLatest ON vSearch.message_from = vLatest.message_from
					LEFT JOIN Abook ON (
						vLatest.message_from = UserHomePhone || 
						vLatest.message_from = UserHomeMobile || 
						vLatest.message_from = UserWorkPhone || 
						vLatest.message_from = UserWorkMobile
					) AND Account = ?
					WHERE vLatest.message_id=(SELECT MAX(message_id) from Virtualcarrier1servermessages WHERE message_from = vSearch.message_from)
					AND vSearch.message_to=? AND (" . $searchString . ") 
					ORDER BY message_date DESC
			";

		}
		//Zend_Registry::get('log')->debug( "\n" . print_r($searchString, true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$searchString \n");
		
		$rows = $this->dbAdapter->fetchAll( $q, array( $this->Account, $this->PhoneNumber ));
		foreach( $rows as &$row )
		{
			
			$row['MessageDateISO8601'] = date('c',$row['message_date']);
			$row['message_date'] = date('r',$row['message_date']);
			foreach( $this->fieldMap as $public => $internal )
			{
				if( $public != $internal )
				{
					$row[$public] = $row[$internal];
					unset($row[$internal]);
				}
			}
			unset($row['sid']);
			unset($row['contentOgg']);
			unset($row['contentMP3']);
		}
		return array_merge($args, array("status" => "success", "totalNumberOfItems" => count($rows), "page" => 1, "itemsPerPage" => count($rows), "serviceData" => $rows) );
		
	}
	
	public function getThread( Array $args = array() )
	{
        
//Zend_Registry::get('log')->debug( "\n" . print_r($args, true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$args \n");
		
		if( !array_key_exists('from', $args) )
			throw new Exception("Error: need (from resolved from Abook.id) in order to get thread of messages sent from that number");
		
		if( empty($args['sort']) || !is_array($args['sort']) )
			$orderClauses = "message_id ASC";
		else
		{

			$sortC = count($args['sort']);
			$a = 1;
			foreach( $args['sort'] as $orderClause )
			{
			
				$orderClauses .= $this->mapToInternal($orderClause[0]) . ' ' . $orderClause[1];
				if( $a++ < $sortC )
					$orderClauses .= ", ";
			
			}

		}
		$whereExtra = '';
		if( !empty($args['where']) )
			$whereExtra .= " AND " . $args['where'];
		
		if( !empty($args['from']) )
		{
			$whereExtra .= " AND (";
			if( !is_array($args['from']) )
				$args['from'] = array($args['from']);
			foreach( $args['from'] as $from )
			{
				$whereExtra .= "message_from = " . $this->dbAdapter->quote($from) . " OR ";
			}
			$whereExtra .= "FALSE )";
		}
		
		$q = "SELECT v.*, UNIX_TIMESTAMP(v.message_date) AS 'message_date' FROM Virtualcarrier1servermessages AS v WHERE message_to = " . $this->dbAdapter->quote($this->PhoneNumber) . $whereExtra . " ORDER BY " . $orderClauses;

//Zend_Registry::get('log')->debug( __METHOD__ . ' #' . __LINE__ . ": \$q = \n" . $q);

		$rows = $this->dbAdapter->fetchAll($q);
		foreach( $rows as &$row )
		{
			
			$row['MessageDateISO8601'] = date('c',$row['message_date']);
			$row['message_date'] = date('r',$row['message_date']);
			foreach( $this->fieldMap as $public => $internal )
			{
				if( $public != $internal )
				{
					$row[$public] = $row[$internal];
					unset($row[$internal]);
				}
			}
			unset($row['sid']);
			unset($row['contentOgg']);
			unset($row['contentMP3']);
		}
		return array_merge($args, array("status" => "success", "totalNumberOfItems" => count($rows), "page" => 1, "itemsPerPage" => count($rows), "serviceData" => $rows) );
		
	}
	
	public function getStream( Array $args = array() )
	{
		
		$rows = $this->getList($args);
//Zend_Registry::get('log')->debug( "\n" . print_r($rows, true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$rows \n");
		
		$stream = array();
		foreach( $rows['serviceData'] as $row )
		{

			$stream[] = array(
				'pid' => $args['pid'], 
				'providerType' => 'Voicemail', 
				'providerName' => 'Twilio', 
				'providerDescription' => 'Main Voicemail Account', 
				'id' => $row['oid'], 
				'receivedDateISO8601' => $row['MessageDateISO8601'],
				'timeAgo' => timeago(strtotime($row['MessageDateISO8601'])),
				'seen' => 'false', 
				'from' => array(
					'id' => null,
					'name' => null, 
					'phoneNumber' => $row['from']
				),
				'subject' => null,
				'messageType' => 'Voicemail', 
				'summary' => $row['transcription'],
				'avatar' => null, 
				'message_audio_url' => $row['message_audio_url'], 
				'message_audio_duration' => $row['message_audio_duration']
			);
			
		}
		return $stream;
		
	}

	
	
	public function updateVM( Array $args )
	{
		
		//Zend_Registry::get('log')->debug( "\n" . print_r($args, true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$args \n");
		$allowedUpdatableFields = array('contentOgg', 'contentMP3');
		$newData = array();
		foreach($allowedUpdatableFields as $allowedUpdatableField )
			if( !empty($args[$allowedUpdatableField]) )
				$newData[$allowedUpdatableField] = base64_encode($args[$allowedUpdatableField]);
		if( count($newData) == 0)
			throw new Exception('Failed updating VM, no new data provided. did transcoding fail?');
		$itemsAffected = $this->dbAdapter->update('Virtualcarrier1servermessages', $newData, "message_id = " . $this->dbAdapter->quote($args['messageId']) )->rowCount(); 
		//return $this->dbAdapter->query($q);
		return array_merge($args, array("status" => "success") );
		
	}

	public function getInfo( Array $args = array() )
	{
		
		if( !array_key_exists('messageId', $args) )
			throw new Exception('Error: messageId must be specified');
		
		$q = "SELECT * FROM Virtualcarrier1servermessages WHERE message_to = " . $this->dbAdapter->quote($this->PhoneNumber) . " AND message_id = " . $this->dbAdapter->quote($args['messageId']);
		$rows = $this->dbAdapter->fetchAll($q);
		$row[0]['contentOgg'] = base64_decode($row[0]['contentOgg']);
		$row[0]['contentMP3'] = base64_decode($row[0]['contentMP3']);
		return array_merge($args, array("status" => "success", "serviceData" => $rows[0]) );
		
	}
	
	public function getRecordingContents( Array $args )
	{
		
		if( !array_key_exists('messageId', $args) )
			throw new Exception('Error: messageId must be specified');
		
		$url = $this->getRecordingUrl($args);
		$result = file_get_contents($url);
		
		if( $result !== false )
			return $result;
		else
			throw new Exception('Error: Failed retrieving recording contents');
		
	}
	
	public function getRecordingUrl( Array $args )
	{
		
		if( !array_key_exists('messageId', $args) )
			throw new Exception('Error: messageId must be specified');
		
		$info = $this->getInfo( $args );
		if( $info === false )
			return false;
		
		return array_merge($args, array("status" => "success", "messageAudioUrl" => $info['message_audio_url']) );
		
	}

	public function transcribe( Array $args ) 
	{
		//other providers might require this method
		return false;
		
	}

	public function flagAsSeen( Array $args )
	{
		
		if( !array_key_exists('messageId', $args) )
			throw new Exception('Error: messageId must be specified');
		
		$q = sprintf("UPDATE Virtualcarrier1servermessages SET message_flag=1 WHERE message_to = " . $this->dbAdapter->quote($this->PhoneNumber) . " AND message_id=%d", $args['messageId']);
		$itemsAffected = $this->dbAdapter->query($q)->rowCount();
		return array("status" => "success");
		
	}
	
	public function flagAsUnseen( Array $args )
	{
		
		if( !array_key_exists('messageId', $args) )
			throw new Exception('Error: messageId must be specified');
		
		$q = sprintf("UPDATE Virtualcarrier1servermessages SET message_flag=0 WHERE message_to = " . $this->dbAdapter->quote($this->PhoneNumber) . " AND message_id=%d", $args['messageId']);
		$itemsAffected = $this->dbAdapter->query($q)->rowCount();
		return array("status" => "success");
		
	}
	
	public function deleteThread( Array $args )
	{
		
		//TODO: can have multiple threads from same user participating in different discussions. deleting thread on from will mess this up, should possibly delete based on message id of message belonging to a thread.
		if( !array_key_exists('from', $args) )
			throw new Exception('Error: from field must be specified to delete thread!');
		
		$q = "DELETE FROM Virtualcarrier1servermessages WHERE message_to = " . $this->dbAdapter->quote($this->PhoneNumber) . " AND message_from = " . $this->dbAdapter->quote($args['from']);
		$itemsAffected = $this->dbAdapter->query($q)->rowCount();
		return array("response" => array("message" => "VM thread deleted"), "status" => "success", "numberOfMessagesDeleted" => $itemsAffected);

	}

	public function delete( Array $args )
	{
		
		if( !array_key_exists('id', $args) )
			throw new Exception('Error: id must be specified');
			
		$q = sprintf("DELETE FROM Virtualcarrier1servermessages WHERE message_to = " . $this->dbAdapter->quote($this->PhoneNumber) . " AND message_id=%d", $args['id']);
		$itemsAffected = $this->dbAdapter->query($q)->rowCount();
		return array("response" => array("message" => "VM messageId deleted"), "status" => "success");

	}
	
	private function mapToInternal( $publicField )
	{
		
		if( array_key_exists($publicField, $this->fieldMap) )
			return $this->fieldMap[$publicField];
		else
			return $publicField;
		
	}

	private function mapToPublic( $internalField )
	{
		
		$result = array_search($internalField, $this->fieldMap);
		if( $result )
			return $result;
		else
			return $internalField;
		
	}
	
}
