<?php
/**
 * WebEngine Lottery Plugin
 * https://webenginecms.org/
 * 
 * @version 2.2.0
 * @author Lautaro Angelico <http://lautaroangelico.com/>
 * @copyright (c) 2013-2019 Lautaro Angelico, All Rights Reserved
 * @build 3a0d6d119a70a51f6c8dc25e5f3d37b5
 */

namespace Plugin\Lottery;

class Lottery {
	
	private $_active = false;
	
	private $_activeTicketLimit = 5;
	
	private $_defaultInitialJackpot = 5000;
	private $_initialJackpot = 0;
	private $_minNumber = 0;
	private $_maxNumber = 20;
	public $_ticketCost = 0;
	public $_ticketProfit = 20; // percent
	private $_ticketHistoryLimit = 20;
	
	private $_matches4 = 0;
	private $_matches3 = 0;
	private $_matches2 = 0;
	private $_matches1 = 0;
	
	private $_startingDay = 7;
	private $_numberRevealDay = 5;
	private $_endingDay = 6;
	
	private $_startingTime = '15:00:00';
	private $_numberRevealTime = '15:00:00';
	private $_endingTime = '15:00:00';
	
	private $_creditConfig = 0;
	
	private $_weekDays = array(
		1 => "Monday",
		2 => "Tuesday",
		3 => "Wednesday",
		4 => "Thursday",
		5 => "Friday",
		6 => "Saturday",
		7 => "Sunday",
	);
	
	private $_userid;
	private $_username;
	private $_luckyNumbers = array();
	
	private $_configXml = 'config.xml';
	private $_lotteryData = array();
	private $_totalTickets;
	private $_accumulatedJackpot;
	
	private $_modulesPath = 'modules';
	private $_sqlPath = 'sql';
	private $_sqlList = array(
		'WEBENGINE_LOTTERY',
		'WEBENGINE_LOTTERY_STASH',
		'WEBENGINE_LOTTERY_TICKETS',
	);
	
	// CONSTRUCTOR
	
	function __construct($lotteryId='') {
		
		$this->configFilePath = __PATH_LOTTERY_ROOT__.$this->_configXml;
		$this->sqlFilePath = __PATH_LOTTERY_ROOT__.$this->_sqlPath.'/';
		
		if(!file_exists($this->configFilePath)) throw new \Exception(lang('lottery_error_13', true));
		
		$xml = simplexml_load_file($this->configFilePath);
		if(!$xml) throw new \Exception(lang('lottery_error_13', true));
		
		$this->_lotteryConfig = convertXML($xml->children());
		if(!is_array($this->_lotteryConfig)) throw new \Exception(lang('lottery_error_13', true));
		
		$this->_active = $this->_lotteryConfig['active'];
		if(!defined('admincp') || admincp != 1) {
			if(!$this->_active) throw new \Exception(lang('lottery_error_1', true));
		}
		
		$this->_defaultInitialJackpot = $this->_lotteryConfig['initial_jackpot'];
		$this->_minNumber = $this->_lotteryConfig['min_number'];
		$this->_maxNumber = $this->_lotteryConfig['max_number'];
		$this->_ticketCost = $this->_lotteryConfig['ticket_cost'];
		$this->_ticketProfit = $this->_lotteryConfig['ticket_profit'];
		$this->_matches4 = $this->_lotteryConfig['matches_4'];
		$this->_matches3 = $this->_lotteryConfig['matches_3'];
		$this->_matches2 = $this->_lotteryConfig['matches_2'];
		$this->_matches1 = $this->_lotteryConfig['matches_1'];
		$this->_startingDay = $this->_lotteryConfig['schedule_start'];
		$this->_numberRevealDay = $this->_lotteryConfig['schedule_reveal'];
		$this->_endingDay = $this->_lotteryConfig['schedule_end'];
		$this->_startingTime = $this->_lotteryConfig['schedule_start_time'];
		$this->_numberRevealTime = $this->_lotteryConfig['schedule_reveal_time'];
		$this->_endingTime = $this->_lotteryConfig['schedule_end_time'];
		$this->_creditConfig = $this->_lotteryConfig['credit_config'];
		$this->_ticketHistoryLimit = $this->_lotteryConfig['history_limit'];
		
		$this->common = new \common();
		$this->db = \Connection::Database('Me_MuOnline');
		
		$this->_checkTables();
		$this->_checkCron();
		
		if(check_value($lotteryId)) {
			$lotteryData = $this->db->query_fetch_single("SELECT * FROM WEBENGINE_LOTTERY WHERE id = ?", array($lotteryId));
		} else {
			$lotteryData = $this->db->query_fetch_single("SELECT * FROM WEBENGINE_LOTTERY WHERE finished IS NULL");
		}
		
		if(!is_array($lotteryData)) {
			$this->_scheduleLottery();
			$lotteryData = $this->db->query_fetch_single("SELECT * FROM WEBENGINE_LOTTERY WHERE finished IS NULL");
		}
		
		$this->_lotteryData = $lotteryData;
		$this->_initialJackpot = $this->_lotteryData['current_jackpot'];
		
		$this->_calculateAccumulatedJackpot();
	}
	
	public function getWeekDays() {
		return $this->_weekDays;
	}
	
	// PUBLIC FUNCTIONS
	
	public function loadModule($module) {
		if(!\Validator::Alpha($module)) throw new \Exception(lang('lottery_error_15', true));
		if(!$this->_moduleExists($module)) throw new \Exception(lang('lottery_error_15', true));
		if(!@include_once(__PATH_LOTTERY_ROOT__ . $this->_modulesPath . '/' . $module . '.php')) throw new \Exception(lang('lottery_error_15', true));
	}
	
	public function setUserid($input) {
		
		$this->_userid = $input;
		$this->_loadUserData();
	}
	
	public function setLuckyNumber($input) {
		if(!\Validator::UnsignedNumber($input)) throw new \Exception(lang('lottery_error_6',true));
		if(!\Validator::Number($input, $this->_maxNumber+1, $this->_minNumber-1)) throw new \Exception(lang('lottery_error_6',true));
		if(in_array($input, $this->_luckyNumbers)) throw new \Exception(lang('lottery_error_7',true));
		
		$this->_luckyNumbers[] = $input;
	}
	
	public function getTicketHistory() {
		if(!$this->_userid) throw new \Exception(lang('lottery_error_2',true));
		
		$query = "SELECT TOP ".$this->_ticketHistoryLimit." WEBENGINE_LOTTERY_TICKETS.*, WEBENGINE_LOTTERY.finished FROM WEBENGINE_LOTTERY_TICKETS INNER JOIN WEBENGINE_LOTTERY ON WEBENGINE_LOTTERY.id = WEBENGINE_LOTTERY_TICKETS.lottery_id WHERE WEBENGINE_LOTTERY_TICKETS.memb___id = ? ORDER BY WEBENGINE_LOTTERY_TICKETS.id DESC";
		$result = $this->db->query_fetch($query, array($this->_username));
		if(!is_array($result)) return;
		return $result;
	}
	
	public function buyTicket() {
		if(!$this->_userid) throw new \Exception(lang('lottery_error_2',true));
		if(count($this->_luckyNumbers) != 4) throw new \Exception(lang('lottery_error_8',true));
		
		$this->_checkActiveTicketLimit();
		
		// subtract credits
		$creditSystem = new \CreditSystem();
		$creditSystem->setConfigId($this->_creditConfig);
		$configSettings = $creditSystem->showConfigs(true);
		switch($configSettings['config_user_col_id']) {
			case 'userid':
				$creditSystem->setIdentifier($this->_userid);
				break;
			case 'username':
				$creditSystem->setIdentifier($this->_username);
				break;
			default:
				throw new \Exception(lang('lottery_error_16', true));
		}
		$subtractCredits = $creditSystem->subtractCredits($this->_ticketCost);
		
		// ticket data
		$ticketData = array(
			'lotteryid' => $this->_lotteryData['id'],
			'membid' => $this->_username,
			'n1' => $this->_luckyNumbers[0],
			'n2' => $this->_luckyNumbers[1],
			'n3' => $this->_luckyNumbers[2],
			'n4' => $this->_luckyNumbers[3]
		);
		
		// add to ticket list
		$query = "INSERT INTO WEBENGINE_LOTTERY_TICKETS (lottery_id,memb___id,buydate,number1,number2,number3,number4) VALUES (:lotteryid, :membid, CURRENT_TIMESTAMP, :n1, :n2, :n3, :n4)";
		$addTicket = $this->db->query($query, $ticketData);
		if(!$addTicket) throw new \Exception(lang('lottery_error_9',true));
	}
	
	/**
	 * Stages:
	 * 1	scheduled, waiting to start
	 * 2	started, in progress
	 * 3	numbers revealed
	 * 4	finished, re-schedule
	 */
	public function cronJob() {
		if(!$this->_active) return;
		switch($this->_lotteryData['current_stage']) {
			case 1:
				// Check Start Date and update current status
				if(time() >= strtotime($this->_lotteryData['start_timestamp'])) {
					$this->_updateCurrentStatus(2);
				}
				break;
			case 2:
				// Check if its time to reveal the numbers
				if(time() >= $this->_generateTimestamp($this->_numberRevealDay, $this->_numberRevealTime, strtotime($this->_lotteryData['start_timestamp']))) {
					$this->_updateCurrentStatus(3);
				}
				break;
			case 3:
				// get winners
				$winners = $this->getWinners();
				
				if(is_array($winners['matches'])) {
					// get results
					$match[1] = (array_key_exists(1, $winners['matches']) ? count($winners['matches'][1]) : 0);
					$match[2] = (array_key_exists(2, $winners['matches']) ? count($winners['matches'][2]) : 0);
					$match[3] = (array_key_exists(3, $winners['matches']) ? count($winners['matches'][3]) : 0);
					$match[4] = (array_key_exists(4, $winners['matches']) ? count($winners['matches'][4]) : 0);
					
					// update lottery results
					if($this->_lotteryData['winners_1'] == NULL && $this->_lotteryData['winners_2'] == NULL && $this->_lotteryData['winners_3'] == NULL && $this->_lotteryData['winners_4'] == NULL) {
						$this->db->query("UPDATE WEBENGINE_LOTTERY SET winners_1 = ?, winners_2 = ?, winners_3 = ?, winners_4 = ? WHERE id = ?", array($match[1], $match[2], $match[3], $match[4], $this->_lotteryData['id']));
					}
				}
				
				// Check if lottery ended
				if(time() >= strtotime($this->_lotteryData['end_timestamp'])) {
					
					// reward winners
					if(is_array($winners['matches'])) {
						foreach($winners['matches'] as $matches => $userindex) {
							
							$reward = $this->_accumulatedJackpot*($this->getMatchWinPercent($matches))/100;
							$reward = floor($reward/$match[$matches]);
							
							foreach($userindex as $index) {
								
								$this->_rewardUser($winners['users'][$index], $reward);
							}
						}
					}
					
					$this->_updateCurrentStatus(4);
				}
				break;
			case 4:
				// Schedule New Lottery
				$this->_clearTickets();
				$this->_scheduleLottery();
				break;
			default:
				return;
		}
	}
	
	public function getJackpot() {
		return $this->_accumulatedJackpot;
	}
	
	public function showLuckyNumber($number) {
		switch($number) {
			case 1:
				if($this->_lotteryData['current_stage'] >= 3) return $this->_lotteryData['number1'];
				return "?";
			case 2:
				if($this->_lotteryData['current_stage'] >= 3) return $this->_lotteryData['number2'];
				return "?";
			case 3:
				if($this->_lotteryData['current_stage'] >= 3) return $this->_lotteryData['number3'];
				return "?";
			case 4:
				if($this->_lotteryData['current_stage'] >= 3) return $this->_lotteryData['number4'];
				return "?";
			default:
				return "?";
		}
	}
	
	public function getStartDate() {
		return $this->_lotteryData['start_timestamp'];
	}
	
	public function getEndDate() {
		return $this->_lotteryData['end_timestamp'];
	}
	
	public function getTicketCost() {
		return $this->_ticketCost;
	}
	
	public function getMinNumber() {
		return $this->_minNumber;
	}
	
	public function getMaxNumber() {
		return $this->_maxNumber;
	}
	
	public function canBuyTicket() {
		if($this->_lotteryData['current_stage'] == 2) return true;
		return false;
	}
	
	public function getStashCredits() {
		if(!$this->_userid) throw new \Exception(lang('lottery_error_2',true));
		$result = $this->db->query_fetch_single("SELECT * FROM WEBENGINE_LOTTERY_STASH WHERE memb___id = ?", array($this->_username));
		if(is_array($result)) return $result['credits'];
		return 0;
	}
	
	public function transferCredits() {
		if(!$this->_userid) throw new \Exception(lang('lottery_error_2',true));
		$userCredits = $this->getStashCredits();
		if($userCredits >= 1) {
			
			// subtract credits
			$creditSystem = new \CreditSystem();
			$creditSystem->setConfigId($this->_creditConfig);
			$configSettings = $creditSystem->showConfigs(true);
			switch($configSettings['config_user_col_id']) {
				case 'userid':
					$creditSystem->setIdentifier($this->_userid);
					break;
				case 'username':
					$creditSystem->setIdentifier($this->_username);
					break;
				default:
					throw new \Exception(lang('lottery_error_16', true));
			}
			$addCredits = $creditSystem->addCredits($userCredits);
			
			$this->db->query("DELETE FROM WEBENGINE_LOTTERY_STASH WHERE memb___id = ?", array($this->_username));
		}
	}
	
	public function getWinners() {
		$tickets = $this->_loadTickets();
		$luckyNumbers = array(
			$this->_lotteryData['number1'],
			$this->_lotteryData['number2'],
			$this->_lotteryData['number3'],
			$this->_lotteryData['number4']
		);
		if(is_array($tickets)) {
			$ticketsArray = array();
			$usersArray = array();
			foreach($tickets as $ticket) {
				$usersArray[] = $ticket['memb___id'];
				$ticketsArray[] = array(
					$ticket['number1'],
					$ticket['number2'],
					$ticket['number3'],
					$ticket['number4']
				);
			}
			
			foreach($ticketsArray as $key => $ticket) {
				$matches = array_intersect($luckyNumbers, $ticket);
				$count = count($matches);
				if($count >= 1) {
					$results[$count][] = $key;
				}
			}
			
			$returnResults = array();
			$returnResults['users'] = $usersArray;
			$returnResults['matches'] = $results;
			return $returnResults;
		}
	}
	
	public function getMatchWinPercent($matches) {
		switch($matches) {
			case 1:
				return $this->_matches1;
			case 2:
				return $this->_matches2;
			case 3:
				return $this->_matches3;
			case 4:
				return $this->_matches4;
			default:
				return 0;
		}
	}
	
	public function getLastLotteryData() {
		$result = $this->db->query_fetch_single("SELECT * FROM WEBENGINE_LOTTERY WHERE finished = ? ORDER BY id DESC", array(1));
		if(!is_array($result)) return;
		return $result;
	}
	
	public function getLotteryList() {
		$result = $this->db->query_fetch("SELECT * FROM WEBENGINE_LOTTERY ORDER BY id DESC");
		if(!is_array($result)) return;
		return $result;
	}
	
	public function getStashList() {
		$result = $this->db->query_fetch("SELECT * FROM WEBENGINE_LOTTERY_STASH ORDER BY credits DESC");
		if(!is_array($result)) return;
		return $result;
	}
	
	public function getLotteryData($key) {
		if(!array_key_exists($key, $this->_lotteryData)) return;
		return $this->_lotteryData[$key];
	}
	
	// PRIVATE FUNCTIONS
	
	private function _moduleExists($module) {
		if(!check_value($module)) return;
		if(!file_exists(__PATH_LOTTERY_ROOT__ . $this->_modulesPath . '/' . $module . '.php')) return;
		return true;
	}
	
	private function _loadUserData() {
		if(!$this->_userid) throw new \Exception(lang('lottery_error_2',true));
		
		$accountInfo = $this->common->accountInformation($this->_userid);
		if(!is_array($accountInfo)) throw new \Exception(lang('lottery_error_3',true));
		
		$this->_username = $accountInfo[_CLMN_USERNM_];
		
		if($this->_checkOnline) {
			if($this->_checkOnlineStatus()) throw new \Exception(lang('lottery_error_4',true));
		}
	}
	
	private function _checkOnlineStatus() {
		if(!$this->_username) throw new \Exception(lang('lottery_error_5',true));
		$accountOnline = $this->common->accountOnline($this->_username);
		if($accountOnline) return true;
		return false;
	}
	
	private function _getActiveTicketCount() {
		if(!$this->_userid) throw new \Exception(lang('lottery_error_2',true));
		
		$ticketHistory = $this->getTicketHistory();
		if(!is_array($ticketHistory)) return 0;
		
		$count = 0;
		foreach($ticketHistory as $ticket) {
			if($ticket['finished'] == 0) $count++;
		}
		
		return $count;
	}
	
	private function _checkActiveTicketLimit() {
		if(!$this->_userid) throw new \Exception(lang('lottery_error_2',true));
		if($this->_getActiveTicketCount() >= $this->_activeTicketLimit) throw new \Exception(lang('lottery_error_12',true));
	}
	
	private function _scheduleLottery() {
		$numbers = $this->_generateRandomNumbers();
		$start = $this->_generateTimestamp($this->_startingDay, $this->_startingTime);
		//$end = $this->_generateTimestamp($this->_endingDay, $this->_endingTime, $start);
		$end = $start+518400;
		
		$start = date("Y-m-d H:i:s", $start);
		$end = date("Y-m-d H:i:s", $end);
		
		$insertData = array(
			$this->_defaultInitialJackpot,
			1,
			$start,
			$end,
			$numbers[0],
			$numbers[1],
			$numbers[2],
			$numbers[3],
		);
		
		$query = "INSERT INTO WEBENGINE_LOTTERY (current_jackpot, current_stage, start_timestamp, end_timestamp, number1, number2, number3, number4) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
		
		$schedule = $this->db->query($query, $insertData);
		if(!$schedule) throw new \Exception(lang('lottery_error_17', true));
		
	}
	
	private function _generateTimestamp($day, $time, $timestamp="") {
		$dowText = date('D', strtotime("Sunday +$day days"));
		if($day == date("N")) {
			$scheduleDay = "today";
		} else {
			$scheduleDay = "next " . $dowText;
		}
		if($timestamp) {
			return strtotime("$scheduleDay $time", $timestamp);
		} else {
			return strtotime("$scheduleDay $time");
		}
	}
	
	private function _generateRandomNumbers() {
		$numbers = array();
		
		while(count($numbers) < 4) {
			$randomNumber = mt_rand($this->_minNumber, $this->_maxNumber);
			if(!in_array($randomNumber, $numbers)) {
				$numbers[] = $randomNumber;
			}
		}
		
		return $numbers;
	}
	
	private function _updateCurrentStatus($status) {
		$update = $this->db->query("UPDATE WEBENGINE_LOTTERY SET current_stage = ? WHERE id = ?", array($status, $this->_lotteryData['id']));
		if(!$update) throw new \Exception(lang('lottery_error_18', true));
	}
	
	private function _calculateAccumulatedJackpot() {
		$result = $this->db->query_fetch_single("SELECT COUNT(*) as totalTickets FROM WEBENGINE_LOTTERY_TICKETS WHERE lottery_id = ?", array($this->_lotteryData['id']));
		$this->_totalTickets = $result['totalTickets'];
		
		// Calculate accumulated jackpot
		if($this->_ticketProfit < 100) {
			$ticketMultiplier = (100-$this->_ticketProfit)/100;
			$this->_accumulatedJackpot = floor((($this->_totalTickets*$this->_ticketCost)*$ticketMultiplier)+$this->_initialJackpot);
		}
	}
	
	private function _rewardUser($username, $credits) {
		$check = $this->db->query_fetch_single("SELECT * FROM WEBENGINE_LOTTERY_STASH WHERE memb___id = ?", array($username));
		if(is_array($check)) {
			// update
			$this->db->query("UPDATE WEBENGINE_LOTTERY_STASH SET credits = ? WHERE memb___id = ?", array($credits, $username));
		} else {
			// insert
			$this->db->query("INSERT INTO WEBENGINE_LOTTERY_STASH (memb___id, credits) VALUES (?, ?)", array($username, $credits));
		}
	}
	
	private function _loadTickets() {
		return $this->db->query_fetch("SELECT * FROM WEBENGINE_LOTTERY_TICKETS WHERE lottery_id = ?", array($this->_lotteryData['id']));
	}
	
	private function _clearTickets() {
		$this->db->query("UPDATE WEBENGINE_LOTTERY SET finished = 1 WHERE id = ?", array($this->_lotteryData['id']));
	}
	
	private function _checkTables() {
		if(!is_array($this->_sqlList)) throw new \Exception(lang('lottery_error_19', true));
		foreach($this->_sqlList as $tableName) {
			$tableExists = $this->db->query_fetch_single("SELECT * FROM sysobjects WHERE xtype = 'U' AND name = ?", array($tableName));
			if($tableExists) continue;
			if(!$this->_createTable($tableName)) throw new \Exception(lang('lottery_error_20', true));
		}
	}
	
	private function _tableFileExists($name) {
		if(!file_exists($this->sqlFilePath.$name.'.txt')) return;
		return true;
	}
	
	private function _createTable($name) {
		if(!in_array($name, $this->_sqlList)) return;
		if(!$this->_tableFileExists($name)) return;
		$query = file_get_contents($this->sqlFilePath.$name.'.txt');
		if(!check_value($query)) return;
		if(!$this->db->query($query)) return;
		return true;
	}
	
	private function _checkCron() {
		$result = $this->db->query_fetch_single("SELECT * FROM ".WEBENGINE_CRON." WHERE cron_file_run = ?", array('lottery.php'));
		if(is_array($result)) return;
		$this->_createCron();
	}
	
	private function _createCron() {
		if(!file_exists(__PATH_CRON__ . 'lottery.php')) throw new \Exception(lang('lottery_error_21', true));
		$cronMd5 = md5_file(__PATH_CRON__ . 'lottery.php');
		if(!check_value($cronMd5)) throw new \Exception(lang('lottery_error_21', true));
		$insertData = array(
			'WebEngine Lottery',
			'lottery.php',
			300,
			1,
			0,
			$cronMd5
		);
		$result = $this->db->query("INSERT INTO ".WEBENGINE_CRON." (cron_name, cron_file_run, cron_run_time, cron_status, cron_protected, cron_file_md5) VALUES (?, ?, ?, ?, ?, ?)", $insertData);
		if(!$result) throw new \Exception(lang('lottery_error_22', true));
	}
	
}