<?php
// (c) Copyright 2002-2016 by authors of the Tiki Wiki CMS Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
// $Id: creditslib.php 57948 2016-03-17 19:29:43Z jyhem $

// TODO: product_id is meant for storing some information about the product
// purchased that led to the credits being added.

/**
 * CreditsLib
 *
 * @uses TikiLib
 */
class CreditsLib extends TikiLib
{

	function getRawCredits( $userId )
	{
		$result = $this->query(
			"SELECT `creditId`, `credit_type`, `creation_date`, `expiration_date`, `total_amount`, `used_amount`" .
			" FROM `tiki_credits` WHERE `userId` = ? ORDER BY `credit_type`, `creation_date`, `expiration_date`",
			array($userId)
		);

		$credits = array();

		while ( $row = $result->fetchRow() ) {
			$credits[$row['creditId']] = $row;
		}

		return $credits;
	}

	function getRawCreditsByType( $userId, $credit_type )
	{
		$result = $this->query(
			"SELECT `creditId`, `creation_date`, `expiration_date`, `total_amount`, `used_amount`" .
			" FROM `tiki_credits`" .
			" WHERE `userId` = ? AND `credit_type` = ? ORDER BY `credit_type`, `creation_date`, `expiration_date`",
			array($userId, $credit_type)
		);

		$credits = array();

		while ( $row = $result->fetchRow() ) {
			$credits[$row['creditId']] = $row;
		}

		return $credits;
	}

	function updateCreditType($credit_type, $display_text, $unit_text, $is_static_level = 'n', $scaling_divisor = 1)
	{
		$bindvars = array($credit_type, $display_text, $unit_text, $is_static_level, $scaling_divisor);
		$result = $this->query(
			"REPLACE INTO `tiki_credits_types`" .
			" (`credit_type`, `display_text`, `unit_text`, `is_static_level`, `scaling_divisor`)" .
			" VALUES (?, ?, ?, ?, ?)",
			$bindvars
		);

		return $result;
	}

	function getCreditTypes($staticonly = false)
	{
		$result = $this->query("SELECT `credit_type`, `display_text`, `unit_text`, `is_static_level`, `scaling_divisor` FROM `tiki_credits_types`");
		$creditTypes = array();

		while ( $row = $result->fetchRow() ) {
			if ($staticonly && $row['is_static_level'] == 'y') {
				$creditTypes[ $row['credit_type'] ] = $row;
			} elseif (!$staticonly) {
				$creditTypes[ $row['credit_type'] ] = $row;
			}
		}

		return $creditTypes;
	}

	function getCredits( $userId )
	{
		$result = $this->query(
			"SELECT `credit_type`, SUM(`total_amount`) total_amount, SUM(`used_amount`) used_amount" .
			" FROM `tiki_credits`" .
			" WHERE" .
			" `userId` = ?" .
			" AND (`expiration_date` IS NULL OR `expiration_date` > NOW())" .
			" AND `creation_date` <= NOW()" .
			" GROUP BY `credit_type`",
			array($userId)
		);

		$credits = array();

		while ( $row = $result->fetchRow() ) {
			$credits[$row['credit_type']] = array(
				'remain' => $row['total_amount'] - $row['used_amount'],
				'used' => $row['used_amount'],
				'total' => $row['total_amount'],
			);
		}
		// Handle level-type credits in a different manner
		// Level of used amount stored in user preferences
		// Total used (flow) from credits table
		$userlib = TikiLib::lib('user');
		$tikilib = TikiLib::lib('tiki');
		$info = $userlib->get_userid_info($userId);

		$creditTypes = $this->getCreditTypes();

		foreach ($credits as $type => $crVal) {
			if ( $creditTypes[$type]['is_static_level'] == 'y' ) {
				$prefName = "credits_level_" . $type;
				$credits[$type]['used'] = (float) $tikilib->get_user_preference($info['login'], $prefName);
				$credits[$type]['remain'] = $credits[$type]['total'] - $credits['used'];
			}
		}

		// set zero for creditTypes that user does not have
		foreach ($creditTypes as $k => $c) {
			if (!array_key_exists($k, $credits)) {
				$credits[$k]['used'] = 0;
				$credits[$k]['remain'] = 0;
				$credits[$k]['total'] = 0;
			}
		}
		return $credits;
	}

	function getScaledCredits( $userId )
	{
		$creditTypes = $this->getCreditTypes();
		$credits = $this->getCredits($userId);

		foreach ( $credits as $type => &$data ) {
			$factor = 1;
			if ( isset($creditTypes[$type]) && $creditTypes[$type]['scaling_divisor'] ) {
				$factor = $creditTypes[$type]['scaling_divisor'];
			}
			if ( isset($creditTypes[$type]) && $display_text = $creditTypes[$type]['display_text'] ) {
				$data['display_text'] = $display_text;
			} else {
				$data['display_text'] = $type;
			}
			if ( isset($creditTypes[$type]) && $unit_text = $creditTypes[$type]['unit_text'] ) {
				$data['unit_text'] = $unit_text;
			} else {
				$data['unit_text'] = '';
			}

			$data['discreet_total'] = $this->scale($data['total'], $factor);
			$data['discreet_used'] = empty($data['discreet_total'])? 0:$data['used'] / ($data['total'] / $data['discreet_total']);
			$data['discreet_remain'] = $data['discreet_total'] - $data['discreet_used'];
			$data['empty'] = $data['remain'] <= 0;
			$data['low'] = $data['remain'] <= $data['total']*.15;
		}

		return $credits;
	}

	private function scale( $value, $factor = 1 )
	{
		// 1.5 log( (x/fac)^2 + 1 )
		return floor(1.5 * log(($value / $factor) * ($value/$factor) + 1));
	}

	function removeCreditBlock( $creditId )
	{
		$this->query("DELETE FROM `tiki_credits` WHERE `creditId` = ?", array($creditId));
	}

	function replaceCredit( $creditId, $type, $used, $total, $validFrom, $expirationDate )
	{
		if ( !empty( $expirationDate ) )
			$expirationDate = date('Y-m-d H:i:s', $time = strtotime($expirationDate));

		if ( $time === false )
			return false;

		$validFrom = date('Y-m-d H:i:s', $time = strtotime($validFrom));

		if ( $time === false )
			return false;

		$this->query(
			"UPDATE `tiki_credits`
			SET `credit_type` = ?, `used_amount` = ?, `total_amount` = ?, `expiration_date` = if (?='',NULL,?), `creation_date` = ?
			WHERE `creditId` = ?",
			array($type, $used, $total, $expirationDate, $expirationDate, $validFrom, $creditId)
		);
	}

	/**
	 * Adds a new credits entry for the user.
	 */
	function addCredits( $userId, $creditType, $amount, $expirationDate = null, $validFrom = null )
	{
		if ( !$amount )
			return false;

		if ( !empty( $expirationDate ) )
			$expirationDate = date('Y-m-d H:i:s', $time = strtotime($expirationDate));

		if ( $time === false )
			return false;

		if ( !empty( $validFrom ) )
			$validFrom = date('Y-m-d H:i:s', $time = strtotime($validFrom));

		if ( $time === false )
			return false;

		$this->query(
			"INSERT INTO `tiki_credits`
			(`userId`, `credit_type`, `total_amount`, `expiration_date`, `creation_date`)
			VALUES(?,?,?,if (? = '', NULL, ?),if (?='', NULL, ?))",
			array($userId, $creditType, $amount, $expirationDate, $expirationDate, $validFrom, $validFrom)
		);

		return true;
	}

	/**
	 * Use the user's credits of a certain type. If the user does not have
	 * enough credits, the function will return false. Credits may be used from
	 * different entries. Entries expiring soon will be used first.
	 */
	function useCredits( $userId, $creditType, $amount, $product_id = null )
	{
		if ( $amount == 0 ) {
			return true;
		}

		// Level-type credits
		$creditTypes = $this->getCreditTypes();
		if ( $creditTypes[$creditType]['is_static_level'] == 'y' ) {
			$credits = $this->getCredits($userId);
			if ( ! array_key_exists($creditType, $credits)) {
				return false;
			}

			if ( $credits[$creditType]['remain'] > 0 ) {
				$userlib = TikiLib::lib('user');
				$tikilib = TikiLib::lib('tiki');
				$info = $userlib->get_userid_info($userId);

				// Expense all credits if not enough
				$toUse = min($credits[$creditType]['used'] + $amount, $credits[$creditType]['remain']);
				$prefName = "credits_level_" . $creditType;

				$tikilib->set_user_preference(
					$info['login'],
					$prefName,
					$toUse
				);

				if ($amount > 0) {
					$this->_recCredits($userId, $creditType, $amount, $product_id);
				}

				return true;
			}

			return false;
		}

		// Expendable credits
		$result = $this->query(
			"SELECT `creditId`, `product_id`, `total_amount` - `used_amount` as available
			FROM `tiki_credits`
			WHERE
			( `expiration_date` > NOW() OR `expiration_date` IS NULL )
			AND `creation_date` <= NOW()
			AND `userId` = ?
			AND `credit_type` = ?
			ORDER BY if (`expiration_date` IS NULL, 1000000, DATEDIFF(`expiration_date`, NOW())) ASC",
			array($userId, $creditType)
		);

		$total = 0;
		$list = array();
		while ( $row = $result->fetchRow() ) {
			$total += $row['available'];
			$list[] = $row;
		}

		if ( $total == 0 ) {
			return false;
		}

		if ( $amount > $total ) {
			$amount = $total;
		}

		if ($amount > 0) {
			$this->_recCredits($userId, $creditType, $amount, $product_id);
		}

		foreach ( $list as $row ) {
			$amount = $this->_useCredits($row['creditId'], $row['available'], $amount);
			if ( $amount <= 0 ) {
				return true;
			}
		}

		die("The verification failed in using credits.");
	}

	function restoreCredits( $userId, $creditType, $amount, $product_id = null )
	{
		// Only valid for level-type credits
		if ( ! array_key_exists($creditType, $this->getCreditTypes(true)) ) {
			return false;
		}

		$userlib = TikiLib::lib('user');
		$tikilib = TikiLib::lib('tiki');
		$info = $userlib->get_userid_info($userId);

		$prefName = "credits_level_" . $creditType;

		$used = (float) $tikilib->get_user_preference($info['login'], $prefName);
		if ( $used === 0 ) {
			return false;
		}

		$used -= $amount;
		if ( $used < 0 ) {
			$used = 0;
		}

		$tikilib->set_user_preference($info['login'], $prefName, $used);

		if ($amount > 0) {
			$this->_recCredits($userId, $creditType, -1 * $amount, $product_id);
		}

		return true;
	}

	/**
	 * Uses up a credit id and returns the amount of credits remaining to use.
	 */
	function _useCredits( $creditId, $available, $amount )
	{
		if ( $available >= $amount ) {
			$this->query(
				"UPDATE `tiki_credits` SET `used_amount` = `used_amount` + ? WHERE `creditId`= ?",
				array($amount, $creditId)
			);

			return 0;
		} else {
			$this->query(
				"UPDATE `tiki_credits` SET `used_amount` = `total_amount` WHERE `creditId`= ?",
				array($creditId)
			);

			return $amount - $available;
		}
	}


	/**
	 * Uses up a credit id and returns the amount of credits remaining to use.
	 */
	function _recCredits( $userId, $creditType, $amount, $product_id = null )
	{
		return $this->query(
			"INSERT INTO `tiki_credits_usage` (`userId`, `usage_date`, `credit_type`, `used_amount`, `product_id`)" .
			" VALUES (?, NOW(), ?, ?, ?)",
			array($userId, $creditType, $amount, $product_id)
		);
	}


	/**
	 * Delete expired credit entries and those completely used up.
	 */
	function purgeCredits()
	{
		$this->query("DELETE FROM `tiki_credits` WHERE `expiration_date` IS NOT NULL AND `expiration_date` < NOW()");
		$this->query("DELETE FROM `tiki_credits` WHERE `total_amount` = `used_amount`");
	}


	function getPlanExpiry( $userId, $creditType )
	{
		$result = $this->getOne(
			"SELECT MAX(`expiration_date`) FROM `tiki_credits` WHERE `userId` = ? AND `expiration_date` IS NOT NULL AND `credit_type` = ?",
			array($userId, $creditType)
		);

		if ( $result ) {
			return $result;
		} else {
			return '';
		}
	}

	function getLatestPlanBegin( $userId, $creditType )
	{
		$result = $this->getOne(
			"SELECT MAX(`creation_date`) FROM `tiki_credits`" .
			" WHERE `userId` = ? AND `expiration_date` IS NOT NULL AND `expiration_date` > NOW()" .
			" AND `credit_type` = ? AND `creation_date` < NOW()",
			array($userId, $creditType)
		);

		if ( $result ) {
			return $result;
		} else {
			return '';
		}
	}

	function getNextPlanBegin( $userId, $creditType )
	{
		$result = $this->getOne(
			"SELECT MIN(`creation_date`)" .
			" FROM `tiki_credits`" .
			" WHERE `userId` = ? AND `expiration_date` IS NOT NULL AND `credit_type` = ? AND `creation_date` > NOW()",
			array($userId, $creditType)
		);

		if ( $result ) {
			return $result;
		} else {
			return '';
		}
	}

	function getCreditsUsage( $target_user_id, $req_type, $start_date, $end_date )
	{
		if ($req_type) {
			$results = $this->query(
				"SELECT * FROM tiki_credits_usage" .
				" WHERE userId = ? AND credit_type = ? AND usage_date > ? AND usage_date <= ?" .
				" ORDER BY `usage_date` desc",
				array($target_user_id, $req_type, $start_date, $end_date)
			);
		} else {
			$results = $this->query(
				"SELECT * FROM tiki_credits_usage WHERE userId = ? AND usage_date > ? AND usage_date <= ? ORDER BY `usage_date` desc",
				array($target_user_id, $start_date, $end_date)
			);
		}
		$consumption_data = array();
		while ( $row = $results->fetchRow() ) {
			$consumption_data[] = $row;
		}

		return $consumption_data;
	}

}
