<?php



require_once(dirname(__FILE__) . '/../../../functions/api/api.php');



/**

 * This is used to retrieve, process and otherwise manipulate split test statistics.

 *

 * @author Fredrick Gabelmann <fredrick.gabelmann@interspire.com>

 *

 * @uses API

 *

 * @package SendStudio

 * @subpackage SplitTests

 */

class Splittest_Stats_API extends API

{



	/**

	 * statsColumnNameLabels

	 * A useful lookup table between DB column names and UI Labels.

	 *

	 * @var Array

	 */

	private $statsColumnNameLabels = array();



	/**

	 * campaignIdNameCache

	 *

	 * A lookup table of campaign names nad campaing Ids

	 * made up of campaings used in a Splittest send.

	 *

	 * @var Array

	 */

	private $campaignIdNameCache = array();



	/**

	 * listCount

	 * Number of lists sent to in this Split Test.

	 *

	 * @var Int

	 */

	private $listCount = 0;





	/**

	 * newsletterCount

	 * A count of the number of newsletters sent.

	 *

	 * @var Interger

	 */

	private $newsletterCount = 0;



	/**

	 * sendSize

	 * Total number of recipients for a Split Test Campaign Send.

	 *

	 * @var Int

	 */

	private $sendSize = 0;





	/**

	 * newsletterStats

	 * Data derived from the stats_newsletter table.

	 *

	 * @var Array

	 */

	private $newsletterStats = array();





	/**

	 * campaignRecipientCountCache

	 * For each campaign keep a count of what the recipient count was at the time of campaign winner testing.

	 *

	 * @var Array

	 */

	private $campaignRecipientCountCache = array();





	/**

	 * splitType

	 * Type of Split Test Campaign ('percentage' | 'distributed').

	 *

	 * @var String

	 */

	private $splitType;





	/**

	 * splitDetails

	 * Configuration parameters for a Split Test.

	 *

	 * @var Array

	 */

	private $splitDetails = array();



	/**

	 * campaignStats

	 * Records from the stats_newsletters table for each given job id.

	 *

	 * @var Array

	 */

	private $campaignStats = array();





	/**

	 * finishTime

	 * The time the Split Test finished sending if this is 0 the campaign is in progress or timed out

	 * in which case the stats are not final.

	 *

	 * @var Integer

	 */

	private $finishTime = 0;





	/**

	 * recpientsAtCalculation

	 * Determine the number of recipients the winning campaign was sent to at the time we decided who the winner was

	 * this is because we want to keep track of what winners scores were at that time.

	 *

	 * @var Integer

	 */

	private $recpientsAtCalculation;





	/**

	 * winnerRecipientCount

	 * The approx number of recipients the winning campaign was sent to

	 * at the time of calculating who the winner was.

	 *

	 * @var Integer

	 */

	private $winnerRecipientCount = 0;



	/**

	 * campaignWinner

	 * Details about the campaign winner.

	 *

	 * @var Array

	 */

	private $campaignWinner = array();



	/**

	 * splittestPercentage

	 * The percentage value for the splittest.

	 *

	 * @var Integer

	 */

	private $splittestPercentage = 50;





	/**

	 * statsNewsletterFields

	 * A list of relevant column names from the _stats_newsletters table.

	 *

	 * @var Array

	 */

	private $statsNewsletterFields = array('statid', 'finishtime', 'linkclicks', 'emailopens', 'emailopens_unique', 'htmlopens', 'htmlopens_unique', 'textopens', 'textopens_unique', 'unsubscribecount', 'bouncecount_soft', 'bouncecount_hard', 'bouncecount_unknown');



	/**

	 * __construct

	 * Calls the API::__construct method only

	 *

	 */

	public function __construct()

	{

		parent::__construct();

		/**

		 * campaignIdNameCache

		 *

		 * A lookup table of campaign names nad campaing Ids

		 * made up of campaings used in a Splittest send

		 *

		 * @var Array

		 */

		$this->statsColumnNameLabels = array(

			'emailopens_unique' => GetLang('Addon_splittest_emailopens_unique'),

			'emailopens_unique_percent' => GetLang('Addon_splittest_emailopens_unique_precent'),

			'linkclicks' => GetLang('Addon_splittest_linkclicks'),

			'linkclicks_percent' => GetLang('Addon_splittest_linkclicks_percent'),

			'linkclicks_unique' => GetLang('Addon_splittest_linkclicks_unique'),

			'linkclicks_unique_percent' => GetLang('Addon_splittest_linkclicks_unique_percent'),

			'bouncecount_total' => GetLang('Addon_splittest_bouncecount_total'),

			'bouncecount_total_percent' => GetLang('Addon_splittest_bouncecount_total_percent'),

			'unsubscribecount' => GetLang('Addon_splittest_unsubscribecount'),

			'unsubscribecount_percent' => GetLang('Addon_splittest_unsubscribecount_percent'),

		);

	}





	/**

	 * DeleteSplittestStats

	 * Delete split test statistics by job Id

	 *  - a list of job Ids can be provided (by creating the appropriate join)

	 *  tables from which records are removed :

	 *		- email_stats_newsletters

	 *		- email_splittest_statistics

	 *		- splittest_statistics_newsletters

	 * 		- email_splittest_statistics_newsletters

	 *

	 * @param Array $jobids	a list of job Ids (correspnding to records in the stats_newsletters table

	 *

	 * @return Boolean True if the stats were deleted, otherwise false.

	 *

	 */

	public function DeleteSplittestStats($jobids)

	{

		if (empty($jobids)) {

			return false;

		}



		for ($i = 0; $i < count($jobids); $i++ ) {

			$this->Db->StartTransaction();

			$jobid = $jobids[$i];

			$stats = $this->Db->Query('SELECT statid FROM email_stats_newsletters WHERE jobid = ' . intval($jobid));

			if (!$stats) {

				$this->Db->RollBackTransaction();

				return false;

			}



			while ($row = $this->Db->Fetch($stats)) {

				$statid = $row['statid'];

				$query = 'SELECT split_statid FROM email_splittest_statistics_newsletters WHERE newsletter_statid = ' . intval($statid);

				$res = $this->Db->Query($query);



				while ($r = $this->Db->Fetch($res)) {

					$split_statid = $r['split_statid'];

					$deleteSplittestStatsQuery = 'DELETE FROM email_splittest_statistics

													WHERE split_statid = '. intval($split_statid) . '

													AND jobid = ' . intval($jobid);



					$result = $this->Db->Query($deleteSplittestStatsQuery);



					if (!$result) {

						$this->db->RollBackTransaction();

						return false;

					}



					$deleteNewsletterStatsQuery = 'DELETE FROM email_splittest_statistics

													WHERE jobid = ' . intval($jobid);



					$result = $this->Db->Query($deleteNewsletterStatsQuery);



					if (!$result) {

						$this->Db->RollBackTransaction();

						return false;

					}

				}

				$deleteSplittestStatsNewsletterQuery = 'DELETE FROM email_splittest_statistics_newsletters WHERE newsletter_statid = '

				. intval($statid);

				$result = $this->Db->Query($deleteSplittestStatsNewsletterQuery);

				if (!$result) {

					$this->Db->RollBackTransaction();

					return false;

				}

			}



			// Delete records from the stats_newsletters table for the given Splittest job

			$deleteNewsletterStatsQuery = 'DELETE FROM email_stats_newsletters WHERE jobid = ' . intval($jobid);

			$result = $this->Db->Query($deleteNewsletterStatsQuery);

			if (!$result) {

				$this->Db->RollBackTransaction();

				return false;

			}

			$this->Db->CommitTransaction();

		}

		return true;

	}



	/**

	 * GetStats

	 * Gets an array of split test statistics based on the listids etc passed in.

	 * It returns an array including:

	 * - split test name

	 * - start/finish times for the stat

	 * - the split id

	 * - the newsletter names used in the split test

	 * - which list(s)/segment(s) were used in the send

	 *

	 * Then returns the details.

	 *

	 * The sort details include a field name and an order.

	 * The field name defaults to the splitname, but can be one of the extra fields:

	 * - splitname

	 * - starttime

	 * - finishtime

	 *

	 * The sort order has to either be 'asc' or 'desc' and defaults to 'asc'.

	 *

	 * The sort details are passed through as an array:

	 * <code>

	 * $sortinfo = array (

	 * 	'sort' => $fieldname,

	 * 	'direction' => $direction,

	 * );

	 * </code>

	 *

	 * @param Array $listids The lists we are showing statistics for.

	 * @param Array $sortinfo How to sort the resulting data.

	 * @param Boolean $countonly Whether to return a count of the number of stats only, or whether to actually return the stats themselves.

	 * @param Int $start The start position (passed to sql as the offset)

	 * @param Int $result_limit The number of results to return (passed to sql as the limit).

	 * @param Boolean $displayAll Set to true if you want to query for all Split Tests otherwise query based on listids

	 * @param Integer $jobid The jobid for a particular Split Test send job. If null the query is based on listids or all all Split Tests

	 *

	 * @return Mixed Returns the number of split tests if you are only doing a count. Otherwise, returns an array of split test stat details. Returns false on error.

	 */

	public function GetStats($listids=array(), $sortinfo=array(), $countonly=false, $start=0, $perpage=10, $displayAll=true, $jobid=null)

	{

		// FIXME: This function call is a big code smell with the presence of the $jobid param. It

		// - overloads the first parameter's meaning (either split_id or list_ids), and

		// - invalidates all the other parameters in between.

		// It should really be refactored.



		$listids = $this->CheckIntVars($listids);

		if (empty($listids)) {

			return false;

		}

		$this->listCount = count($listids);



		// return array

		$results = array();

		$results_jobs = array();



		$query = 'SELECT s.splitid, s.splitname, s.splittype, s.splitdetails, ss.split_statid, ss.starttime, ss.finishtime, ss.jobid, ';

		$query .= 'CASE WHEN ss.finishtime > 0 THEN 0 ELSE 1 END AS finishtime_check';

		$from_clause = '

				FROM

					[|PREFIX|]splittests s INNER JOIN [|PREFIX|]splittest_statistics ss ON (s.splitid=ss.splitid)

				WHERE

					ss.split_statid IN

					(

						SELECT ssn.split_statid

						  FROM [|PREFIX|]stats_newsletter_lists snl

							INNER JOIN

							[|PREFIX|]splittest_statistics_newsletters ssn

							  ON (snl.statid = ssn.newsletter_statid)';



		if ($jobid != null) {

			$from_clause .= '

							INNER JOIN [|PREFIX|]stats_newsletters news

								ON (news.statid = ssn.newsletter_statid)';

		}



		if ($displayAll == true) {

			$from_clause .= ' WHERE snl.listid IN (' . implode(',', $listids) . ')

						GROUP BY ssn.split_statid';

		} else {

			$from_clause .= ' WHERE s.splitid = ' . intval($listids[0]);

			if ($jobid != null) {

				$from_clause .= ' AND news.jobid = ' . intval($jobid);

			}

		}



		$from_clause .= ' )	AND	ss.hiddenby = 0';



		if ($countonly && $displayAll == true) {

			$query = "SELECT COUNT(1) AS count";

			$query .= $from_clause;

			$count = $this->Db->FetchOne($query);

			return $count;

		}



		$query .= $from_clause;



		$valid_fields = array (

			'splitname',

			'splittype',

			'starttime',

			'splitdetails',

			'finishtime',

			'jobid',

		);



		$valid_directions = array (

			'asc',

			'desc',

		);



		$order_field = 'finishtime';

		$order_direction = 'DESC';



		if (isset($sortinfo['SortBy'])) {

			$field = strtolower($sortinfo['SortBy']);

			if (in_array($field, $valid_fields)) {

				$order_field = $field;

			}

		}



		if (isset($sortinfo['Direction'])) {

			$dir = strtolower($sortinfo['Direction']);

			if (in_array($dir, $valid_directions)) {

				$order_direction = $dir;

			}

		}



		if ($displayAll == true) {

			$query .= ' ORDER BY ';

			if ($order_field == 'finishtime') {

				$query .= 'finishtime_check ' . $order_direction . ', ';

			}

			$query .= $order_field . ' ' . $order_direction;

			// If we're not ordering by splitname, use that as a secondary sort.

			if ($order_field != 'splitname') {

				$query .= ', splitname ASC';

			}

		}

		$query .= ' ' . $this->Db->AddLimit(($start - 1) * $perpage, $perpage);

		$result = $this->Db->Query($query);

		if (!$result) {

			trigger_error(__FILE__ . '::' . __METHOD__ . ' -- Unable to query statistics', E_USER_WARNING);

			return $results;

		}



		// Get some statistics about emails campaigns sent

		while ($row = $this->Db->Fetch($result)) {

			// We need the jobid to get the campaign stats for this splittest send

			$jobid = $row['jobid'];



			$row['campaign_names'] = array();



			// Split Test Configuration data

			$this->splitType = $row['splittype'];

			$this->splitDetails = $this->getSplitDetails($row['splitdetails']);

			$row['splitdetails'] = $this->splitDetails;



			// stats_newsletters records for each newsletter sent

			$campaignStats = array();



			// Get some overall Campaign send statistics for the Splittest

			$sendStats = $this->splittestSendStats($row['split_statid'], $jobid);

			$row['bouncecount'] = $sendStats['bouncecount'];

			$row['lists'] = $sendStats['lists'];



			$campaignIds = $this->getCampaignIDs($jobid);



			// keep a count of all the subscribers campaigns for a splittest are sent to

			$totalRecipientCount = 0;



			// if we don't have any campaign IDs in the Split Test configuration then we will never find any stats records

			if (empty($campaignIds)) {

				continue;

			}

			$this->campaignStats = array();

			for ($i=0; $i<count($campaignIds); $i++) {

				$campaignSendData = array();



				// Get the stats for this particular campaign send

				$newsletterStatsData['stats_newsletters'] = $this->getCampaignStats($campaignIds[$i], $jobid);

				$campaignName = $newsletterStatsData['stats_newsletters']['campaign_name'];



				// Add some meta data for the campaign

				$newsletterStatsData['campaign_name'] = $campaignName;

				$row['campaign_names'][] = $campaignName;

				$newsletterStatsData['campaign_id'] = $campaignIds[$i];

				$newsletterStatsData['statistics_id'] = $row['split_statid'];



				$this->campaignStats[$campaignIds[$i]] = $newsletterStatsData;



				if (!array_key_exists($campaignIds[$i], $this->campaignIdNameCache)) {

					$this->campaignIdNameCache[$campaignIds[$i]] = $campaignName;

				}



				$this->campaignRecipientCountCache[$campaignIds[$i]] = $newsletterStatsData['stats_newsletters']['recipients'];

				$totalRecipientCount += $newsletterStatsData['stats_newsletters']['recipients'];

				$this->newsletterCount++;

			}



			$this->sendSize = $totalRecipientCount;



			// report array on campaign statistics and order by best performing

			$compareCampaignStats = $this->compareCampaignStats($this->campaignStats);



			// list campaign details and score for best performing campaign

			$this->campaignWinner = $this->getCampaignWinners($row['splitdetails'], $compareCampaignStats);



			// once we have dertermined the winner set the recipient count at time of calculation

			$this->getRecpientsAtCalculation();



			// stats_newsletters result with final percentage calculations added in

			$this->newsletterStats = $this->finalPercentageStats();



			if ($this->splitType != 'percentage') {

				$row['winner_type_message'] = sprintf(GetLang('Addon_splittest_WonMessage'), $this->campaignWinner['winnerName']);

			}



			$winnerType = $this->campaignWinner['winnerType'];

			$winner = $compareCampaignStats['campaignStatsWinners'];

			if ($winner['emailopens']['value'] == 0 && $winner['linkclicks']['value'] == 0) {

				$winnerType = 'None';

			}



			// some values useful displaying from the template

			$row['campaigns'] = $this->newsletterStats;

			$row['campaign_names'] = implode($row['campaign_names'], ', ');

			$row['campaign_statistics'] = $compareCampaignStats;

			$row['campaign_winner_id'] = $this->campaignWinner['winnerId'];

			$row['campaign_winner_name'] = $this->campaignWinner['winnerName'];

			$row['campaign_winner_type'] = $winnerType;

			$row['winner_message'] = $this->getWinnerMessage();

			$row['total_recipient_count'] = $totalRecipientCount;

			$row['total_recipients_at_calculation'] = $this->recpientsAtCalculation;

			$row['winner_recipient_count'] = $this->campaignWinner['winnerRecipientCount'];

			$row['finishtime'] = self::userTimestamp(self::fixTimestamp($row['finishtime']));

			$row['starttime'] = self::userTimestamp(self::fixTimestamp($row['starttime']));



			$results[$jobid] = $row;

			$results_jobs[] = $jobid;

		}

		$this->Db->FreeResult($result);



		if (!empty($results_jobs)) {

			switch (SENDSTUDIO_DATABASE_TYPE) {

				case 'mysql':

					$query = "

						SELECT	GROUP_CONCAT(l.name SEPARATOR ', ') AS list_names,

								ss.jobid



						FROM	[|PREFIX|]lists AS l

								INNER JOIN [|PREFIX|]stats_newsletter_lists AS snl

									ON (l.listid = snl.listid)

								INNER JOIN [|PREFIX|]splittest_statistics_newsletters AS ssn

									ON (snl.statid = ssn.newsletter_statid)

								INNER JOIN [|PREFIX|]splittest_statistics AS ss

									ON (ssn.split_statid = ss.split_statid)



			            WHERE	ss.jobid IN (" . implode(',', $results_jobs) . ")



			            GROUP BY l.name



			            ORDER BY l.name ASC LIMIT 1

					";



					$result = $this->Db->Query($query);

					if (!$result) {

						trigger_error(__FILE__ . '::' . __METHOD__ . ' -- Unable to query statistics', E_USER_WARNING);

						return $results;

					}



					while ($row = $this->Db->Fetch($result)) {

						$results[$row['jobid']]['list_names'] = $row['list_names'];

					}



					$this->Db->FreeResult($result);

				break;



				case 'pgsql':

					$query = "

						SELECT DISTINCT l.name AS list_names, ss.jobid



						FROM	[|PREFIX|]lists AS l

								INNER JOIN [|PREFIX|]stats_newsletter_lists AS snl

									ON (l.listid = snl.listid)

								INNER JOIN [|PREFIX|]splittest_statistics_newsletters AS ssn

									ON (snl.statid = ssn.newsletter_statid)

								INNER JOIN [|PREFIX|]splittest_statistics AS ss

									ON (ssn.split_statid = ss.split_statid)



			            WHERE	ss.jobid IN (" . implode(',', $results_jobs) . ")



						GROUP BY l.name, ss.jobid



						ORDER BY l.name ASC

					";



					$result = $this->Db->Query($query);

					if (!$result) {

						trigger_error(__FILE__ . '::' . __METHOD__ . ' -- Unable to query statistics', E_USER_WARNING);

						return $results;

					}



					$names = array();

					while ($row = $this->Db->Fetch($result)) {

						if (!array_key_exists($row['jobid'], $names)) {

							$names[$row['jobid']] = array();

						}



						$names[$row['jobid']][] = $row['list_names'];

					}



					$keys = array_keys($names);

					foreach ($keys as $jobid) {

						$results[$jobid]['list_names'] = implode(' ,', $names[$jobid]);

					}



					$this->Db->FreeResult($result);

				break;



				default:

					trigger_error(__FILE__ . '::' . __METHOD__ . ' - Invalid database type', E_USER_WARNING);

					return $results;

				break;

			}

		}



		return $results;

	}



	/**

	 * fixTimestamp

	 * IEM currently stores all timestamps incorrectly. By definition ALL

	 * timestamps are to be interpreted as being GMT (see http://php.net/time).

	 * However, IEM timestamps have had the server timezone offset subtracted

	 * from it, because of a mistaken assumption that the GMT timestamps had

	 * the server timezone offset added to it already.

	 *

	 * Because we now dynamically determine the server timezone offset, this

	 * function will return inaccurate results (off by one hour) when the

	 * server goes into DST for broken timestamps saved outside of DST (and

	 * vice-versa). Hopefully we can change stored timestamps to be in GMT one

	 * day.

	 *

	 * @param Int|String $broken_ts A broken timestamp (one that has had the server timezone offset subtracted from it).

	 *

	 * @return Int A timestamp in GMT.

	 */

	private static function fixTimestamp($broken_ts)

	{

		if (!$broken_ts) {

			// If the timestamp is 0 then maintain this, because its value

			// probably has special meaning.

			return 0;

		}



		// Server timezone offset (seconds).

		$server_offset = date('Z');



		return intval($broken_ts) + $server_offset;

	}



	/**

	 * userTimestamp

	 * When provided with a GMT Unix timestamp, it will return a timestamp

	 * adjusted for the user's timezone, taking into account the server's

	 * timezone offset.

	 *

	 * @see fixTimestamp

	 *

	 * @param Int|String $gmt_ts A valid GMT timestamp.

	 *

	 * @return Int A timestamp that has been adjusted with the current user's timezone offset.

	 */

	private static function userTimestamp($gmt_ts)

	{

		if (!$gmt_ts) {

			return 0;

		}



		$user = GetUser();



		// User timezone offset (seconds).

		$user_offset = $user->Get('usertimezone'); // "GMT-11:30"

		$user_offset = substr($user_offset, 3); // "-11:30"

		$user_offset = str_replace(':3', '.5', $user_offset); // "-11.50"

		$user_offset = str_replace(':', '.', $user_offset);

		$user_offset = floatval($user_offset); // -11.5

		$user_offset = $user_offset * 60 * 60; // to seconds



		$server_offset = date('Z');



		return intval($gmt_ts) + ($user_offset - $server_offset);

	}





	/**

	 * getCampaignStats

	 * For a given Campaign ID get the send statistics for a particular job

	 * this jobid is associated with a particular Splittest send.

	 *

	 * @param Int $campaignId the Newsletter ID.

	 * @param Int $jobid the jobid for the Splittest statstics.

	 *

	 * @return Mixed Array of values from the appropriate record in the stats_newsletters table.

	 */

	private function getCampaignStats($campaignId, $jobid)

	{

		$query = 'SELECT ' . implode(',', $this->statsNewsletterFields) .

						', SUM(bouncecount_soft + bouncecount_hard + bouncecount_unknown) AS bouncecount_total,

						SUM(htmlrecipients + textrecipients + multipartrecipients) AS recipients

				FROM [|PREFIX|]stats_newsletters

				WHERE newsletterid = ' . intval($campaignId) . '

					AND jobid = ' . intval($jobid) . '

				GROUP BY ' . implode(',', $this->statsNewsletterFields);



		$rs = $this->Db->Query($query);



		require_once(dirname(dirname(dirname(dirname(__FILE__)))) . '/functions/api/stats.php');

		$stats_api = new Stats_API();



		$row = array();

		while ($row = $this->Db->Fetch($rs)) {

			$row['linkclicks_unique'] = $stats_api->GetUniqueClickers($row['statid']);

			$row['recipients'] > 0 ? $recipients = $row['recipients'] : $recipients = 0;

			$row['percent_emailopens_unique'] = $this->_doPercentage($row['emailopens_unique'], $recipients);

			$row['percent_linkclicks_unique'] = $this->_doPercentage($row['linkclicks_unique'], $recipients);

			$row['percent_bouncecount_total'] = $this->_doPercentage($row['bouncecount_total'], $recipients);

			$row['percent_unsubscribecount'] = $this->_doPercentage($row['unsubscribecount'], $recipients);

			$row['campaign_name'] = $this->getNewsletterNameFromStatid($row['statid']);

			$this->finishTime = $row['finishtime'];

			break;

		}

		$this->Db->FreeResult($rs);

		return $row;

	}



	/**

	 * getWinnerMessage

	 * Return a string describing the win state, i.e. the winner campaign name, type and score.

	 *

	 * @return String Message.

	 */

	private function getWinnerMessage()

	{

		$winnerType = $this->campaignWinner['winnerType'];

		$winnerName = $this->campaignWinner['winnerName'];

		$winnerId = $this->campaignWinner['winnerId'];

		$winnerOpenPercent = $this->newsletterStats[$winnerId]['stats_newsletters']['percent_emailopens_unique'];

		$winnerLinkPercent = $this->newsletterStats[$winnerId]['stats_newsletters']['percent_linkclicks_unique'];



		$message = sprintf(GetLang('Addon_splittest_WonMessage'), $winnerName) . ', ';

		if ($winnerType == 'Open') {

			$message .= sprintf(GetLang('Addon_splittest_OpenRate'), $winnerOpenPercent);

		} else {

			$message .= sprintf(GetLang('Addon_splittest_ClickRate'), $winnerLinkPercent);

		}

		return $message;

	}





	/**

	 * getRecpientsAtCalculation

	 * try and work how many recipients were sent to when the Campaign paused to work out who the winner is

	 *

	 */

	private function getRecpientsAtCalculation()

	{

		// If the campaign is Evenly Distributed then recipientCount = totalRecipients / $this->listCount

		// If the campaign is Percentage Based then the recipientCount = ((totalRecipient / $this->splittestPercentage) * 100) / $this->listCount

		if ($this->splitType == 'percentage') {

			$recipientCount = ($this->splittestPercentage / $this->sendSize ) * 100;

			$recipientCount = floor($recipientCount / $this->newsletterCount);

		} else {

			$recipientCount = $this->sendSize / $this->newsletterCount;

		}

		$this->recpientsAtCalculation = $recipientCount;

	}





	/**

	 * finalPercentageStats

	 * Work out the statistics given the ratio of the final recipient count.

	 *

	 * @param Array $campaignStats statistics derived from the _stats_newsletters table

	 * @return Mixed appended final percentage values to the statistics array

	 */

	private function finalPercentageStats()

	{

		while (list($id, $statistics) = each($this->campaignStats)) {

			for ($i=0; $i<count($this->statsNewsletterFields); $i++) {

				$statistics['stats_newsletters']['final_percent_' . $this->statsNewsletterFields[$i]] = $this->_doPercentage($statistics['stats_newsletters'][$this->statsNewsletterFields[$i]], $this->sendSize);

			}



			$statistics['stats_newsletters']['final_percent_bouncecount_total'] = $this->_doPercentage($statistics['stats_newsletters']['bouncecount_total'], $this->sendSize);

			$statistics['stats_newsletters']['final_percent_linkclicks_unique'] = $this->_doPercentage($statistics['stats_newsletters']['linkclicks_unique'], $this->sendSize);

			$statistics['stats_newsletters']['final_total_recipient_count'] = $this->sendSize;

			$this->campaignStats[$id] = $statistics;

		}

		return $this->campaignStats;

	}





	/**

	 * getCampaignWinners

	 * Get Statistics data about the best performing campaign.

	 *

	 * @param Array $weighting The weighting that opens and clicks have, to help decide the winner type.

	 * @param Array $compareCampaignStats stats_newsletter data about each campaing used in the send.

	 *

	 * @return Mixed statistical information about the best performing campaign.

	 *

	 */

	private function getCampaignWinners($weighting, $compareCampaignStats)

	{

		$stats = $compareCampaignStats['campaignStatsWinners'];

		// FIXME: should these be based off unique opens/clicks?

		// Get the names ofthe best performing campaigns

		$openrateName = $stats['emailopens']['name'];

		$clickrateName = $stats['linkclicks']['name'];



		// Get the raw and weighted values of the best performing campaigns

		$openrateValue = $stats['emailopens']['value'];

		$weightedOpenRate = $openrateValue * $weighting['openrate'] / 100;

		$clickrateValue = $stats['linkclicks']['value'];

		$weightedclickRate = $clickrateValue * $weighting['clickrate'] / 100;



		// this is the best performing open rate campaign

		$openrateCampaignId = $stats['emailopens']['id'];



		// this is the best performing click rate campaign

		$clickrateCampaignId = $stats['linkclicks']['id'];



		// the winner score was derived by adding the highest open rate + click rates scores together

		$winnerId = $stats['weighted']['id'];

		$winnerName = $stats['weighted']['name'];

		$winnerScore = $stats['weighted']['value'];

		$winnerPercentScore = $winnerScore;



		if ($weightedOpenRate > $weightedclickRate) {

			$winnerType = 'Open';

			$winnerRawValue = $openrateValue;

		} else {

			$winnerType = 'Click';

			$winnerRawValue = $clickrateValue;

		}



		if ($openrateValue == 0 && $clickrateValue == 0) {

			$winnerType = 'None';

		}



		$results = array(

			'winnerId' => $winnerId,

			'winnerType' => $winnerType,

			'winnerName' => $winnerName,

			'winnerCount' => $winnerRawValue,

			'winnerPercentScore' => $winnerPercentScore,

			'winnerRecipientCount' => $this->campaignRecipientCountCache[$winnerId],

			'winnerOpenName' => $openrateName,

			'winnerClickName' => $clickrateName,

			'winnerOpenAbsoluteValue' => $openrateValue,

			'winnerClickAbsoluteValue' => $clickrateValue

			);

		return $results;

	}





	/**

	 * barChartDataURL

	 * Produces a string suitable for passing to the InsertChart function for a custom_bar Chart type.

	 *   Eg ; xLabels=Opens,Clicks,Bounces,Unsubscribes&data=John:8.9,11,10.1Paul:33.3,29.2,21,34,5George:58.3,64.3.64,0,Ringo:41.7,40.1,0,2

	 *

	 * @param Array $stats Statistics fo a campaign send derived from newsletter_stats table.

	 * @param String $statType The type of statistics to report ie emailopens, bouncecount.

	 * @param Boolean $rawValue Report raw values.

	 * @param Booleam $pcValue Report percentage of recipients value.

	 * @param Boolean $final If true then we want to get the final percentage value which is the percentage value of the total split test send size rather than the individual campaign size.

	 *

	 * @return String $urlStr A string suitable for sending to the am charts class.

	 */

	public function barChartDataURL($stats, $statType, $rawValue, $pcValue, $final=false)

	{

		// if rawValue and pcValue are false nothing is looked up, therefore let's not continue

		if ($rawValue == false && $pcValue == false ) {

			return false;

		}



		// a list of labels for the chart X Axis

		$xLabels = array();



		// a list of traces and their respective data values

		$members = array();



		// loop through an array of campaigns and their associated 'stat_newsletters' data

		while (list($id, $details) = each($stats)) {

			$valueStr = urlencode($details['campaign_name']) . ':';

			$values = array();



			// Use our manually-determined unique link clicks instead of the raw value.

			if ($statType == 'linkclicks') {

				$statType = 'linkclicks_unique';

			}



			if ($rawValue === true) {

				if (!in_array($this->statsColumnNameLabels[$statType], $xLabels)) {

					$xLabels[] = $this->statsColumnNameLabels[$statType];

				}

				$values[] = $details['stats_newsletters'][$statType];

			}

			if ($pcValue === true) {

				$pcLabel = $statType . '_percent';

				if (!in_array($this->statsColumnNameLabels[$pcLabel], $xLabels)) {

					$xLabels[] = $this->statsColumnNameLabels[$pcLabel];

				}

				$values[] = $details['stats_newsletters']['percent_' . $statType];

			}

			$valueStr .= implode(':', $values);

			$members[] = $valueStr;

		}

		foreach($xLabels as &$label){

			$label=urlencode($label);

		}

		$urlStr = 'xLabels=' . implode(',', $xLabels) . '&data=';

		$urlStr .= implode(',', $members);

		return $urlStr;

	}





	/**

	 * barChartDataURL

	 * Produces a string suitable for a custom bar chart.

	 * xLabels=Opens,Clicks,Bounces,Unsubscribes&data=John:8.9,11,10.1Paul:33.3,29.2,21,34,5George:58.3,64.3.64,0,Ringo:41.7,40.1,0,2

	 *

	 * @param Array $stats The stats_newsletter data for the campaigns used in a Split Test send.

	 * @param String $xLabels Labels to be used on the X Axis of the chart.

	 *

	 * @return String $urlStr A string of campaign name and statistics values for each campaign.

	 */

	public function barChartSummaryDataURL($stats, $xLabels)

	{

		$urlStr = 'xLabels=' . $xLabels . '&data=';

		while (list($id, $details) = each($stats) ) {

			$urlStr .= urlencode($details['campaign_name']) . ':';

			$urlStr .= $details['stats_newsletters']['emailopens_unique'] . ':';

			$urlStr .= $details['stats_newsletters']['linkclicks_unique'] . ':';

			$urlStr .= $details['stats_newsletters']['bouncecount_total'] . ':';

			$urlStr .= $details['stats_newsletters']['unsubscribecount'] . ',' ;

		}

		return substr($urlStr, 0, -1);

	}





	/**

	 * getSplitDetails

	 * Process the splitdetails column into some sensible values (openrate, clickrate, percentage, etc).

	 *

	 * @param String $splitdetails A serialized string of options, e.g. 'a:3:{s:10:"percentage";i:80;s:10:"hoursafter";d:2...'

	 *

	 * @return Array $details an array of configurations for the given Split Test.

	 */

	private function getSplitDetails($splitdetails)

	{

		$data = unserialize($splitdetails);

		if (!count($data)) {

			return array();

		}

		$details = array(

			'openrate' => $data['weights']['openrate'],

			'clickrate' => $data['weights']['linkclick'],

			);

		if (isset($data['percentage'])) {

			$details['percentage'] = $data['percentage'];

		}

		return $details;

	}





	/**

	 * compareCampaignStats

	 * Compare an array of statistics for a series of Campaign sends

	 * to determine the best performing 'winner' for a Splittest send

	 * and rank the individual components of a send (Opens, Clicks, Bounce etc) in order of values.

	 *

	 * @param Array $stats an array of data for a given newsletter send (i.e. from the stats_newsletters table)

	 *

	 * @return Mixed array of campaign stats results,

	 *					ranked by highest values

	 *					and ranked by weighted performance as determined by the Split Test configuration

	 *

	 */

	private function compareCampaignStats($stats)

	{

		$linkclicksPercentHash = array();

		$emailopensPercentHash = array();

		$bouncecountPercentHash = array();

		$unsubscribesPercentHash = array();



		$campaignStatsWinners = array();

		$weightedHash = array();

		$weightedAbsolutePercentageHash = array();



		while (list($id, $data) = each($stats)) {

			$stats_newsletters = $data['stats_newsletters'];



			// these will be the sort fields used when listing the newsletters on each stats pane

			$hash_linkclicks[$id] = $stats_newsletters['linkclicks_unique'];

			$hash_emailopens[$id] = $stats_newsletters['emailopens'];

			$hash_bouncecount[$id] = $stats_newsletters['bouncecount_total'];

			$hash_unsubscribes[$id] = $stats_newsletters['unsubscribecount'];



			$recipientCount = $stats_newsletters['recipients'];

			$openrateWeight = $this->splitDetails['openrate'] / 100;

			$clickrateWeight =  $this->splitDetails['clickrate'] / 100;



			// Use a 'weighted' multiplier to determine a 'handicap' based result

			$linkWeighted = $openWeighted = 0;

			if ($recipientCount > 0) {

				$linkWeighted = (($stats_newsletters['linkclicks_unique'] / $recipientCount) * 100) * $clickrateWeight;

				$openWeighted = (($stats_newsletters['emailopens_unique'] / $recipientCount) * 100) * $openrateWeight;

			}



			$weightedResult = $linkWeighted + $openWeighted;



			$hash_weighted[$id] = round($weightedResult, 1);

			$weightedAbsolutePercentageHash[$id] = array(

				'linkclicks_unique' => $stats_newsletters['linkclicks_unique'],

				'emailopens_unique' => $stats_newsletters['emailopens_unique']

			);



		}

		// The following adds the best performing for the various stat types

		list($id, $value) = $this->processWinnerRank($this->rankHash($hash_weighted));

		$campaignStatsWinners['weighted'] = array(

								'name' => $this->campaignIdNameCache[$id],

								'value' => $value,

								'id' => $id

		);



		list($id, $value) = $this->processWinnerRank($this->rankHash($hash_linkclicks));

		$campaignStatsWinners['linkclicks'] = array(

								'name' => $this->campaignIdNameCache[$id],

								'value' => $value,

								'id' => $id

		);



		list($id, $value) = $this->processWinnerRank($this->rankHash($hash_emailopens));

		$campaignStatsWinners['emailopens'] = array(

								'name' => $this->campaignIdNameCache[$id],

								'value' => $value,

								'id' => $id

		);



		list($id, $value) = $this->processWinnerRank($this->rankHash($hash_bouncecount));

		$campaignStatsWinners['bouncecount_total'] = array(

								'name' => $this->campaignIdNameCache[$id],

								'value' => $value,

								'id' => $id

		);



		list($id, $value) = $this->processWinnerRank($this->rankHash($hash_unsubscribes));

		$campaignStatsWinners['unsubscribes'] = array(

								'name' => $this->campaignIdNameCache[$id],

								'value' => $value,

								'id' => $id

		);



		$results['campaignStatsWinners'] = $campaignStatsWinners;

		$results['rankings'] = array(

			'linkclicks_description' => 'percentage of newsletter recipients who click',

			'linkclicks' => $this->rankHash($hash_linkclicks),

			'emailopens_description' => 'percentage of newsetter recipients who unique opens',

			'emailopens' => $this->rankHash($hash_emailopens),

			'bouncecount_total_description' => 'percentage of newletters recipients who bounce',

			'bouncecount_total' => $this->rankHash($hash_bouncecount),

			'unsubscribes_description' => 'percentage of newsletter recipients who unsubscribed',

			'unsubscribes' => $this->rankHash($hash_unsubscribes),

			'weighted_description' => 'weighted score based on split test percentages',

			'weighted' => $this->rankHash($hash_weighted),

			'weighted_absolute' => $weightedAbsolutePercentageHash

		);

		return $results;

	}





	/**

	 * processWinnerRank

	 * Returns the winning ID and total of a newsletter from a group of newsletter results.

	 *

	 * @param Array $candidates A list of newsletters in the form array(array(id => total)).

	 *

	 * @return Mixed A key=>value pair containing the newsletter ID with the highest total in the form array(id => total).

	 */

	private function processWinnerRank($candidates)

	{

		$max_id = 0;

		$max_total = -1;

		foreach ($candidates as $candidate) {

			$id = key($candidate);

			if ($candidate[$id] > $max_total) {

				$max_id = $id;

				$max_total = $candidate[$id];

			}

		}

		return array($max_id, $max_total);

	}





	/**

	 * _doPercentage

	 * Returns the $value as a percentage of $divisor.

	 * For example, _doPercentage(5, 10) = 50.0.

	 *

	 * @param Integer $value The number of items to get a percentage for (a subset of the total).

	 * @param Integer $divisor The total number of items in the set.

	 * @param Integer $round Number of decimal places to permit for rounding purposes.

	 *

	 * @return Float A percentage value.

	 */

	private function _doPercentage($value, $divisor, $round=1)

	{

		$percentage = 0;

		if ($value > 0 && $divisor != 0) {

			$percentage = ($value / $divisor) * 100;

			$percentage = round($percentage, $round);

		}

		return $percentage;

	}





	/**

	 * getCampaignIDs

	 * For a given Splittest job return a list ot its Campaigns IDs.

	 *

	 * @param Int $job_id The ID of the job.

	 *

	 * @return Array returns An array of Campaign Ids set for the given job.

	 *

	 */

	private function getCampaignIDs($job_id)

	{

            $query = "SELECT sc.campaignid AS id FROM [|PREFIX|]splittest_statistics s, [|PREFIX|]splittest_campaigns sc WHERE s.jobid = " . intval($job_id) . " AND sc.splitid = s.splitid";

            $result = $this->Db->Query($query);

            $details = array();

            while ($row = $this->Db->Fetch($result)) {

			$details[] = $row['id'];

            }

            $campaign_ids = $details;

            return $campaign_ids;

	}





	/**

	 * splittestSendStats

	 * Get some sending Statistics for a particular Splittest.

	 * For each splittest send there will multiple matching jobids (for each campaign sent)

	 * which we will match with a record in the _splittest_statistics table.

	 *

	 * @param Int $split_statid the statistics ID for a given Splittest send.

	 * @param Int $jobid the jobid associated with a particular Splittest send.

	 * @param Array $stats_newsletters_fields Array of fields to query for in the stats_newsletters table.

	 *

	 * @return Mixed array of overall statistcs for a given Splittest send

	 */

	private function splittestSendStats($split_statid, $jobid, $stats_newsletters_fields=array())

	{

		$query = 'SELECT SUM(news.bouncecount_soft + news.bouncecount_hard + news.bouncecount_unknown) AS bouncecount';

		for ($i = 0; $i < count($stats_newsletters_fields); $i++) {

			$query .= ', SUM(news.' . $stats_newsletters_fields[$i] . ') AS ' . $stats_newsletters_fields[$i];

			if ($i < count($stats_newsletters_fields)-1) {

				$query .= ', ';

			}

		}

		$query .= ' FROM [|PREFIX|]splittests s, [|PREFIX|]splittest_statistics ss, [|PREFIX|]splittest_statistics_newsletters ssn, [|PREFIX|]stats_newsletters news

						WHERE ss.split_statid = ' . intval($split_statid) . '

						AND ss.jobid = ' . intval($jobid) . '

						AND ss.splitid = s.splitid

						AND ssn.split_statid = ss.split_statid

						AND ssn.newsletter_statid =  news.statid

						AND news.sendtype = \'splittest\'';

		$result = $this->Db->Query($query);

		$row = $this->Db->Fetch($result);

		$row['lists'] = $this->getCampaignLists($split_statid, $jobid);

		$this->Db->FreeResult($result);

		return $row;

	}





	/**

	 * getCampaignLists

	 * For a given campaign stat fetch the list id and names associated with that send.

	 *

	 * @param Int $statid The campaign stat id.

	 * @param Int $jobid The campaign job id.

	 *

	 * @return Array A list of List Ids and names belonging to that campaign send.

	 */

	private function getCampaignLists($statid, $jobid)

	{

		$lists = array();

		$query = 'SELECT DISTINCT l.listid, l.name

					FROM [|PREFIX|]lists l, [|PREFIX|]stats_newsletters sn, [|PREFIX|]stats_newsletter_lists snl

					WHERE jobid = ' . intval($jobid) . '

					AND sn.statid = snl.statid

					AND snl.listid = l.listid';

		$rs = $this->Db->Query($query);

		while ($row = $this->Db->Fetch($rs)) {

			$lists[] = $row;

		}

		$this->Db->FreeResult($rs);

		return $lists;

	}





	/**

	 * getNewsletterNameFromStatid

	 * For a given jobid return the Newsletter name for this job.

	 * This is a convenience function so we don't have to load the Newsletter API.

	 *

	 * @param Integer $statid A newsletter send statid.

	 *

	 * @return String $newsletter Name.

	 */

	private function getNewsletterNameFromStatid($statid)

	{

		$query = 'SELECT n.name

					FROM [|PREFIX|]newsletters n, [|PREFIX|]stats_newsletters sn

				   WHERE sn.statid = ' . intval($statid) . ' AND sn.newsletterid = n.newsletterid';

		return $this->Db->FetchOne($query);

	}





	/**

	 * rankHash

	 * Sort a hash into an order array based on hash values from highest to lowest.

	 *

	 * @param Array $hash An array of keys and corresponding values.

	 *

	 * @return Array $ranked An array of keys and corresponding values sorted by values highest to lowest.

	 */

	private function rankHash($hash)

	{

		$ranked = array();

		arsort($hash);

		while (list($key, $val) = each($hash)) {

			$ranked[] = array($key => $val);

		}

		return $ranked;

	}

}

