<?php namespace ProcessWire;

/**
 * A field that stores notifications 
 *
 */

class FieldtypeNotifications extends FieldtypeMulti {

	public static function getModuleInfo() {
		return array(
			'title' => 'Notifications',
			'version' => 4,
			'summary' => 'Field that stores user notifications.',
			'requires' => 'SystemNotifications',
			);
	}

	public function init() {
		parent::init(); 
	}

	/**
	 * Return the required Inputfield used to populate a field of this type
	 *
	 */
	public function getInputfield(Page $page, Field $field) {
		return $this->wire(new InputfieldWrapper());
	}

	/**
	 * Return a blank ready-to-populate version of a field of this type
	 *
	 */
	public function getBlankValue(Page $page, Field $field) {
		$notifications = $this->wire(new NotificationArray($page));
		$notifications->setTrackChanges(true); 
		return $notifications; 
	}

	/**
	 * Given a raw value (value as stored in DB), return the value as it would appear in a Page object
	 *
	 * @param Page $page
	 * @param Field $field
	 * @param string|int|array $value
	 * @return string|int|array|object $value
	 *
	 */
	public function ___wakeupValue(Page $page, Field $field, $value) {
	
		// if for some reason we already get a valid value, then just return it
		if($value instanceof NotificationArray) return $value; 
	
		// start a blank value to be populated
		$notifications = $this->getBlankValue($page, $field); 
	
		// if we were given a blank value, then we've got nothing to do: just return a blank NotificationArray
		if(empty($value) || !is_array($value)) return $notifications; 
	
		// create new Notification objects from each item in the array
		foreach($value as $v) {
			$notification = $this->wire(new Notification());
			$notification->title = $v['title'];
			$notification->flags = $v['flags']; 
			$notification->created = $v['created']; 
			$notification->modified = $v['modified']; 
			$notification->src_id = $v['src_id']; 
			$notification->qty = $v['qty'];
	
			$data = json_decode($v['data'], true); 
			if(is_array($data)) $notification->setArray($data); 
	
			$notification->resetTrackChanges(); 
			if(!$notification->isExpired()) $notifications->add($notification); 
		}
	
		$notifications->resetTrackChanges(); 
		return $notifications;  
	}

	/**
	 * Email the given notifications to the pages (users) they are associated with
	 * 
	 * Removes email flag after mail has been successfully sent. 
	 * 
	 * @param array $notifications
	 * 
	 */
	protected function emailNotifications(array $notifications) {
		foreach($notifications as $notification) {
			if(!$notification->is("email")) continue;
			$page = $notification->page; 
			if(!$page || !$page->email) continue; 
			$of = $page->of();
			$page->of(false);
			$mail = $this->wire('mail')->new();
			$mail->to($page->email); 
			if($notification->emailFrom) $mail->from($notification->emailFrom); 
			$mail->subject($notification->title . " (" . $this->wire('config')->httpHost . ")");
			$mail->body((string) $notification->text);
			if((string) $notification->html) $mail->bodyHTML((string) $notification->html); 
			if($mail->send()) {
				$notification->removeFlag("email"); 
				$this->wire('log')->message("Sent notification email to: $page->email, Subject: $notification->title"); 
			} else {
				$this->wire('log')->error("Error sending notification email to: $page->email, Subject: $notification->title");
			}
			$page->of($of);
		}
	}

	/**
	 * Given an 'awake' value, as set by wakeupValue, convert the value back to a basic type for storage in DB. 
	 *              
	 * @param Page $page
	 * @param Field $field
	 * @param string|int|array|object $value
	 * @return string|int
	 *
	 */
	public function ___sleepValue(Page $page, Field $field, $value) {

		$sleepValue = array();
		$notifications = array();
		$emails = array();
	
		// if we are given something other than an NotificationArray, 
		// then just return a blank array
		if(!$value instanceof NotificationArray) return $sleepValue; 
	
		// make the notifications sort by created date ascending
		$value->sort('created'); 
	
		// filter out notifications that don't need to be stored in the DB
		foreach($value as $notification) {
			
			if(!$notification->created) $notification->created = time();
			if(!$notification->src_id) $notification->src_id = (int) $this->wire('page')->id;
			if(!$notification->title) $notification->title = 'Untitled';
			if(!$notification->is('message') && !$notification->is('error') && !$notification->is('warning')) {
				$notification->setFlag('message', true);
			}

			$hash = $notification->getHash();

			if($notification->is("debug") && !$this->wire('config')->debug) continue;
			//if($notification->isExpired()) continue;
			if($notification->is("email")) $emails[$hash] = $notification; // an email for this notification is pending
			if($notification->is("notice")) continue; // don't save runtime Notices
			if($notification->is("session")) continue; // don't save session notifications
			if($notification->isChanged()) $notification->modified = time();
		
			// check for duplicates
			if(isset($notifications[$hash])) {
				// found a duplicate notification
				$no = $notifications[$hash];
				// check which one we should keep
				if($no->modified > $notification->modified || $no->created > $notification->created) {
					// existing notification is newer than the one we have, so use the existing
					unset($notifications[$hash]); // remove
					// keep created date from whichever is older
					// $no->created = $notification->created > 0 && $notification->created < $no->created ? $notification->created : $no->created;
					$notification = $no; // then it'll be added back, appended to array
				} 
				// qty indicates how many times the same notification was repeated
				$notification->qty = $no->qty + 1; 
			}
			// add the notification
			$notifications[$hash] = $notification;
		}
		
		if(count($emails)) $this->emailNotifications($emails); 

		// convert each Notification to an array within sleepValue
		foreach($notifications as $notification) {
			
			$data = array(
				'id' => $notification->getID(), 
				'text' => $notification->text, 
				'html' => $notification->html,
				'from' => $notification->from, 
				'icon' => $notification->icon,
				'href' => $notification->href,
				'progress' => $notification->progress, 
				'expires' => $notification->expires, 
				); 
	
			$json = wireEncodeJSON($data); 
	
			if(!$notification->modified || $notification->isChanged()) $notification->modified = time();
	
			$sleep = array(
				'title' => $notification->title, 
				'flags' => $notification->flags, 
				'created' => date('Y-m-d H:i:s', $notification->created),
				'modified' => date('Y-m-d H:i:s', $notification->modified), 
				'src_id' => $notification->src_id, 
				'data' => $json,
				'qty' => $notification->qty, 
				); 
	
			$sleepValue[] = $sleep; 
		}
		
		return $sleepValue;
	}

	/**
	 * Given a value, make it clean for storage within a Page
	 *
	 */
	public function sanitizeValue(Page $page, Field $field, $value) {

		// if given a blank value, return a valid blank value
		if(empty($value)) return $this->getBlankValue($page, $field, $value); 

		// if given something other than an NotificationArray, throw an error
		if(!$value instanceof NotificationArray) {
			throw new WireException("Value set to field '$field->name' must be a NotificationArray"); 
		}

		// note that sanitization of individual fields within a given Notification is already 
		// performed by the Notification::set() method, so we don't need to do anything else here.

		return $value; 	
	}

	/**
	 * Format a value for output, called when a Page's outputFormatting is on
	 *
	 */
	public function formatValue(Page $page, Field $field, $value) {
		// we actually don't need to do anything in here since each Notification object
		// is doing this work in the Notification::get() method. But I've included this
		// comment here just to explain where that is taking place. 
		return $value; 
	}

	/**
	 * Return the database schema that defines an Notification
	 *
	 */
	public function getDatabaseSchema(Field $field) {
		$schema = parent::getDatabaseSchema($field); 

		$schema['title'] = 'TINYTEXT NOT NULL'; 
		$schema['src_id'] = 'INT UNSIGNED NOT NULL'; 
		$schema['flags'] = 'INT UNSIGNED NOT NULL';
		$schema['created'] = 'DATETIME NOT NULL';
		$schema['modified'] = 'DATETIME NOT NULL';
		$schema['data'] = 'TEXT NOT NULL'; 
		$schema['qty'] = 'INT UNSIGNED NOT NULL DEFAULT 1';

		$schema['keys']['data'] = 'FULLTEXT data(data)'; 
		$schema['keys']['src_id'] = 'KEY src_id(src_id)'; 
		$schema['keys']['flags'] = 'KEY flags(flags)'; 
		$schema['keys']['created'] = 'KEY created(created)'; 

		return $schema; 
	}

	/**
	 * Method called when the field is database-queried from a selector 
	 *
	 */
	public function getMatchQuery($query, $table, $subfield, $operator, $value) {
		return parent::getMatchQuery($query, $table, $subfield, $operator, $value); 
	}


}

