pragma solidity ^0.4.24;

/**
 * @title SpinWinInterface
 */
interface SpinWinInterface {
	function refundPendingBets() external returns (bool);
}


/**
 * @title AdvertisingInterface
 */
interface AdvertisingInterface {
	function incrementBetCounter() external returns (bool);
}


contract SpinWinLibraryInterface {
	function calculateWinningReward(uint256 betValue, uint256 playerNumber, uint256 houseEdge) external pure returns (uint256);
	function calculateTokenReward(address settingAddress, uint256 betValue, uint256 playerNumber, uint256 houseEdge) external constant returns (uint256);
	function generateRandomNumber(address settingAddress, uint256 betBlockNumber, uint256 extraData, uint256 divisor) external constant returns (uint256);
	function calculateClearBetBlocksReward(address settingAddress, address lotteryAddress) external constant returns (uint256);
	function calculateLotteryContribution(address settingAddress, address lotteryAddress, uint256 betValue) external constant returns (uint256);
	function calculateExchangeTokenValue(address settingAddress, uint256 tokenAmount) external constant returns (uint256, uint256, uint256, uint256);
}


/**
 * @title LotteryInterface
 */
interface LotteryInterface {
	function claimReward(address playerAddress, uint256 tokenAmount) external returns (bool);
	function calculateLotteryContributionPercentage() external constant returns (uint256);
	function getNumLottery() external constant returns (uint256);
	function isActive() external constant returns (bool);
	function getCurrentTicketMultiplierHonor() external constant returns (uint256);
	function getCurrentLotteryTargetBalance() external constant returns (uint256, uint256);
}


/**
 * @title SettingInterface
 */
interface SettingInterface {
	function uintSettings(bytes32 name) external constant returns (uint256);
	function boolSettings(bytes32 name) external constant returns (bool);
	function isActive() external constant returns (bool);
	function canBet(uint256 rewardValue, uint256 betValue, uint256 playerNumber, uint256 houseEdge) external constant returns (bool);
	function isExchangeAllowed(address playerAddress, uint256 tokenAmount) external constant returns (bool);

	/******************************************/
	/*          SPINWIN ONLY METHODS          */
	/******************************************/
	function spinwinSetUintSetting(bytes32 name, uint256 value) external;
	function spinwinIncrementUintSetting(bytes32 name) external;
	function spinwinSetBoolSetting(bytes32 name, bool value) external;
	function spinwinAddFunds(uint256 amount) external;
	function spinwinUpdateTokenToWeiExchangeRate() external;
	function spinwinRollDice(uint256 betValue) external;
	function spinwinUpdateWinMetric(uint256 playerProfit) external;
	function spinwinUpdateLoseMetric(uint256 betValue, uint256 tokenRewardValue) external;
	function spinwinUpdateLotteryContributionMetric(uint256 lotteryContribution) external;
	function spinwinUpdateExchangeMetric(uint256 exchangeAmount) external;

	/******************************************/
	/*      SPINLOTTERY ONLY METHODS          */
	/******************************************/
	function spinlotterySetUintSetting(bytes32 name, uint256 value) external;
	function spinlotteryIncrementUintSetting(bytes32 name) external;
	function spinlotterySetBoolSetting(bytes32 name, bool value) external;
	function spinlotteryUpdateTokenToWeiExchangeRate() external;
	function spinlotterySetMinBankroll(uint256 _minBankroll) external returns (bool);
}


/**
 * @title TokenInterface
 */
interface TokenInterface {
	function getTotalSupply() external constant returns (uint256);
	function getBalanceOf(address account) external constant returns (uint256);
	function transfer(address _to, uint256 _value) external returns (bool);
	function transferFrom(address _from, address _to, uint256 _value) external returns (bool);
	function approve(address _spender, uint256 _value) external returns (bool success);
	function approveAndCall(address _spender, uint256 _value, bytes _extraData) external returns (bool success);
	function burn(uint256 _value) external returns (bool success);
	function burnFrom(address _from, uint256 _value) external returns (bool success);
	function mintTransfer(address _to, uint _value) external returns (bool);
	function burnAt(address _at, uint _value) external returns (bool);
}




// https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/math/SafeMath.sol

/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeMath {
	function mul(uint256 a, uint256 b) internal pure returns (uint256) {
		if (a == 0) {
			return 0;
		}
		uint256 c = a * b;
		assert(c / a == b);
		return c;
	}

	function div(uint256 a, uint256 b) internal pure returns (uint256) {
		// assert(b > 0); // Solidity automatically throws when dividing by 0
		uint256 c = a / b;
		// assert(a == b * c + a % b); // There is no case in which this doesn't hold
		return c;
	}

	function sub(uint256 a, uint256 b) internal pure returns (uint256) {
		assert(b <= a);
		return a - b;
	}

	function add(uint256 a, uint256 b) internal pure returns (uint256) {
		uint256 c = a + b;
		assert(c >= a);
		return c;
	}
}



interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) external; }

contract TokenERC20 {
	// Public variables of the token
	string public name;
	string public symbol;
	uint8 public decimals = 18;
	// 18 decimals is the strongly suggested default, avoid changing it
	uint256 public totalSupply;

	// This creates an array with all balances
	mapping (address => uint256) public balanceOf;
	mapping (address => mapping (address => uint256)) public allowance;

	// This generates a public event on the blockchain that will notify clients
	event Transfer(address indexed from, address indexed to, uint256 value);

	// This generates a public event on the blockchain that will notify clients
	event Approval(address indexed _owner, address indexed _spender, uint256 _value);

	// This notifies clients about the amount burnt
	event Burn(address indexed from, uint256 value);

	/**
	 * Constructor function
	 *
	 * Initializes contract with initial supply tokens to the creator of the contract
	 */
	constructor(
		uint256 initialSupply,
		string tokenName,
		string tokenSymbol
	) public {
		totalSupply = initialSupply * 10 ** uint256(decimals);  // Update total supply with the decimal amount
		balanceOf[msg.sender] = totalSupply;                // Give the creator all initial tokens
		name = tokenName;                                   // Set the name for display purposes
		symbol = tokenSymbol;                               // Set the symbol for display purposes
	}

	/**
	 * Internal transfer, only can be called by this contract
	 */
	function _transfer(address _from, address _to, uint _value) internal {
		// Prevent transfer to 0x0 address. Use burn() instead
		require(_to != 0x0);
		// Check if the sender has enough
		require(balanceOf[_from] >= _value);
		// Check for overflows
		require(balanceOf[_to] + _value > balanceOf[_to]);
		// Save this for an assertion in the future
		uint previousBalances = balanceOf[_from] + balanceOf[_to];
		// Subtract from the sender
		balanceOf[_from] -= _value;
		// Add the same to the recipient
		balanceOf[_to] += _value;
		emit Transfer(_from, _to, _value);
		// Asserts are used to use static analysis to find bugs in your code. They should never fail
		assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
	}

	/**
	 * Transfer tokens
	 *
	 * Send `_value` tokens to `_to` from your account
	 *
	 * @param _to The address of the recipient
	 * @param _value the amount to send
	 */
	function transfer(address _to, uint256 _value) public returns (bool success) {
		_transfer(msg.sender, _to, _value);
		return true;
	}

	/**
	 * Transfer tokens from other address
	 *
	 * Send `_value` tokens to `_to` in behalf of `_from`
	 *
	 * @param _from The address of the sender
	 * @param _to The address of the recipient
	 * @param _value the amount to send
	 */
	function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
		require(_value <= allowance[_from][msg.sender]);     // Check allowance
		allowance[_from][msg.sender] -= _value;
		_transfer(_from, _to, _value);
		return true;
	}

	/**
	 * Set allowance for other address
	 *
	 * Allows `_spender` to spend no more than `_value` tokens in your behalf
	 *
	 * @param _spender The address authorized to spend
	 * @param _value the max amount they can spend
	 */
	function approve(address _spender, uint256 _value) public returns (bool success) {
		allowance[msg.sender][_spender] = _value;
		emit Approval(msg.sender, _spender, _value);
		return true;
	}

	/**
	 * Set allowance for other address and notify
	 *
	 * Allows `_spender` to spend no more than `_value` tokens in your behalf, and then ping the contract about it
	 *
	 * @param _spender The address authorized to spend
	 * @param _value the max amount they can spend
	 * @param _extraData some extra information to send to the approved contract
	 */
	function approveAndCall(address _spender, uint256 _value, bytes _extraData)
		public
		returns (bool success) {
		tokenRecipient spender = tokenRecipient(_spender);
		if (approve(_spender, _value)) {
			spender.receiveApproval(msg.sender, _value, this, _extraData);
			return true;
		}
	}

	/**
	 * Destroy tokens
	 *
	 * Remove `_value` tokens from the system irreversibly
	 *
	 * @param _value the amount of money to burn
	 */
	function burn(uint256 _value) public returns (bool success) {
		require(balanceOf[msg.sender] >= _value);   // Check if the sender has enough
		balanceOf[msg.sender] -= _value;            // Subtract from the sender
		totalSupply -= _value;                      // Updates totalSupply
		emit Burn(msg.sender, _value);
		return true;
	}

	/**
	 * Destroy tokens from other account
	 *
	 * Remove `_value` tokens from the system irreversibly on behalf of `_from`.
	 *
	 * @param _from the address of the sender
	 * @param _value the amount of money to burn
	 */
	function burnFrom(address _from, uint256 _value) public returns (bool success) {
		require(balanceOf[_from] >= _value);                // Check if the targeted balance is enough
		require(_value <= allowance[_from][msg.sender]);    // Check allowance
		balanceOf[_from] -= _value;                         // Subtract from the targeted balance
		allowance[_from][msg.sender] -= _value;             // Subtract from the sender's allowance
		totalSupply -= _value;                              // Update totalSupply
		emit Burn(_from, _value);
		return true;
	}
}

contract developed {
	address public developer;

	/**
	 * Constructor
	 */
	constructor() public {
		developer = msg.sender;
	}

	/**
	 * @dev Checks only developer address is calling
	 */
	modifier onlyDeveloper {
		require(msg.sender == developer);
		_;
	}

	/**
	 * @dev Allows developer to switch developer address
	 * @param _developer The new developer address to be set
	 */
	function changeDeveloper(address _developer) public onlyDeveloper {
		developer = _developer;
	}

	/**
	 * @dev Allows developer to withdraw ERC20 Token
	 */
	function withdrawToken(address tokenContractAddress) public onlyDeveloper {
		TokenERC20 _token = TokenERC20(tokenContractAddress);
		if (_token.balanceOf(this) > 0) {
			_token.transfer(developer, _token.balanceOf(this));
		}
	}
}








/**
 * @title SpinWin
 */
contract SpinWin is developed, SpinWinInterface {
	using SafeMath for uint256;

	address public tokenAddress;
	address public settingAddress;
	address public lotteryAddress;

	TokenInterface internal _spintoken;
	SettingInterface internal _setting;
	LotteryInterface internal _lottery;
	SpinWinLibraryInterface internal _lib;
	AdvertisingInterface internal _advertising;

	/**
	 * @dev Player variables
	 */
	struct Bet {
		address playerAddress;
		bytes32 betId;
		uint256 betValue;
		uint256 diceResult;
		uint256 playerNumber;
		uint256 houseEdge;
		uint256 rewardValue;
		uint256 tokenRewardValue;
		uint256 blockNumber;
		bool processed;
	}
	struct TokenExchange {
		address playerAddress;
		bytes32 exchangeId;
		bool processed;
	}

	mapping (uint256 => Bet) internal bets;
	mapping (bytes32 => uint256) internal betIdLookup;
	mapping (address => uint256) public playerPendingWithdrawals;
	mapping (address => uint256) public playerPendingTokenWithdrawals;
	mapping (address => address) public referees;
	mapping (bytes32 => TokenExchange) public tokenExchanges;
	mapping (address => uint256) public lotteryBlocksAmount;

	uint256 constant public TWO_DECIMALS = 100;
	uint256 constant public PERCENTAGE_DIVISOR = 10 ** 6;   // 1000000 = 100%
	uint256 constant public CURRENCY_DIVISOR = 10**18;

	uint256 public totalPendingBets;

	/**
	 * @dev Log when bet is placed
	 */
	event LogBet(bytes32 indexed betId, address indexed playerAddress, uint256 playerNumber, uint256 betValue, uint256 houseEdge, uint256 rewardValue, uint256 tokenRewardValue);

	/**
	 * @dev Log when bet is cleared
	 *
	 * Status:
	 * -2 = lose + failed mint and transfer
	 * -1 = lose + failed send
	 * 0 = lose
	 * 1 = win
	 * 2 = win + failed send
	 * 3 = refund
	 * 4 = refund + failed send
	 * 5 = owner cancel + refund
	 * 6 = owner cancel + refund + failed send
	 */
	event LogResult(bytes32 indexed betId, address indexed playerAddress, uint256 playerNumber, uint256 diceResult, uint256 betValue, uint256 houseEdge, uint256 rewardValue, uint256 tokenRewardValue, int256 status);

	/**
	 * @dev Log when spinwin contributes some ETH to the lottery contract address
	 */
	event LogLotteryContribution(bytes32 indexed betId, address indexed playerAddress, uint256 weiValue);

	/**
	 * @dev Log when spinwin rewards the referee of a bet or the person clears bet
	 * rewardType
	 * 1 = referral
	 * 2 = clearBet
	 */
	event LogRewardLotteryBlocks(address indexed receiver, bytes32 indexed betId, uint256 lottoBlocksAmount, uint256 rewardType, uint256 status);

	/**
	 * @dev Log when player clears bets
	 */
	event LogClearBets(address indexed playerAddress);

	/**
	 * @dev Log when player claims the lottery blocks reward
	 *
	 * Status:
	 * 0 = failed
	 * 1 = success
	 */
	event LogClaimLotteryBlocks(address indexed playerAddress, uint256 numLottery, uint256 claimAmount, uint256 claimStatus);

	/**
	 * @dev Log when player exchanges token to Wei
	 *
	 * Status:
	 * 0 = failed send
	 * 1 = success
	 * 2 = failed destroy token
	 */
	event LogTokenExchange(bytes32 indexed exchangeId, address indexed playerAddress, uint256 tokenValue, uint256 tokenToWeiExchangeRate, uint256 weiValue, uint256 receivedWeiValue, uint256 remainderTokenValue, uint256 status);

	/**
	 * @dev Log when player withdraws balance from failed transfer
	 *
	 * Status:
	 * 0 = failed
	 * 1 = success
	 */
	event LogPlayerWithdrawBalance(address indexed playerAddress, uint256 withdrawAmount, uint256 status);

	/**
	 * @dev Log when player withdraw token balance from failed token transfer
	 *
	 * Status:
	 * 0 = failed
	 * 1 = success
	 */
	event LogPlayerWithdrawTokenBalance(address indexed playerAddress, uint256 withdrawAmount, uint256 status);

	/**
	 * @dev Log when a bet ID is not found during clear bet
	 */
	event LogBetNotFound(bytes32 indexed betId);

	/**
	 * @dev Log when developer cancel existing active bet
	 */
	event LogDeveloperCancelBet(bytes32 indexed betId, address indexed playerAddress);

	/**
	 * Constructor
	 * @param _tokenAddress SpinToken contract address
	 * @param _settingAddress GameSetting contract address
	 * @param _libraryAddress SpinWinLibrary contract address
	 */
	constructor(address _tokenAddress, address _settingAddress, address _libraryAddress) public {
		tokenAddress = _tokenAddress;
		settingAddress = _settingAddress;
		_spintoken = TokenInterface(_tokenAddress);
		_setting = SettingInterface(_settingAddress);
		_lib = SpinWinLibraryInterface(_libraryAddress);
	}

	/**
	 * @dev Checks if contract is active
	 */
	modifier isActive {
		require(_setting.isActive() == true);
		_;
	}

	/**
	 * @dev Checks whether a bet is allowed, and player profit, bet value, house edge and player number are within range
	 */
	modifier canBet(uint256 _betValue, uint256 _playerNumber, uint256 _houseEdge) {
		require(_setting.canBet(_lib.calculateWinningReward(_betValue, _playerNumber, _houseEdge), _betValue, _playerNumber, _houseEdge) == true);
		_;
	}

	/**
	 * @dev Checks if bet exist
	 */
	modifier betExist(bytes32 betId, address playerAddress) {
		require(betIdLookup[betId] > 0 && bets[betIdLookup[betId]].betId == betId && bets[betIdLookup[betId]].playerAddress == playerAddress);
		_;
	}

	/**
	 * @dev Checks if token exchange is allowed
	 */
	modifier isExchangeAllowed(address playerAddress, uint256 tokenAmount) {
		require(_setting.isExchangeAllowed(playerAddress, tokenAmount) == true);
		_;
	}

	/******************************************/
	/*       DEVELOPER ONLY METHODS           */
	/******************************************/
	/**
	 * @dev Allows developer to set lottery contract address
	 * @param _lotteryAddress The new lottery contract address to be set
	 */
	function devSetLotteryAddress(address _lotteryAddress) public onlyDeveloper {
		require (_lotteryAddress != address(0));
		lotteryAddress = _lotteryAddress;
		_lottery = LotteryInterface(_lotteryAddress);
	}

	/**
	 * @dev Allows developer to set advertising contract address
	 * @param _advertisingAddress The new advertising contract address to be set
	 */
	function devSetAdvertisingAddress(address _advertisingAddress) public onlyDeveloper {
		require (_advertisingAddress != address(0));
		_advertising = AdvertisingInterface(_advertisingAddress);
	}

	/**
	 * @dev Allows developer to get bet internal ID based on public betId
	 * @param betId The public betId
	 * @return The bet internal ID
	 */
	function devGetBetInternalId(bytes32 betId) public onlyDeveloper constant returns (uint256) {
		return (betIdLookup[betId]);
	}

	/**
	 * @dev Allows developer to get bet info based on `betInternalId`
	 * @param betInternalId The bet internal ID to be queried
	 * @return The bet information
	 */
	function devGetBet(uint256 betInternalId) public
		onlyDeveloper
		constant returns (address, uint256, uint256, uint256, uint256, uint256, uint256, uint256, bool) {
		Bet memory _bet = bets[betInternalId];
		return (_bet.playerAddress, _bet.betValue, _bet.diceResult, _bet.playerNumber, _bet.houseEdge, _bet.rewardValue, _bet.tokenRewardValue, _bet.blockNumber, _bet.processed);
	}

	/**
	 * @dev Allows developer to manually refund existing active bet.
	 * @param betId The ID of the bet to be cancelled
	 * @return Return true if success
	 */
	function devRefundBet(bytes32 betId) public onlyDeveloper returns (bool) {
		require (betIdLookup[betId] > 0);

		Bet storage _bet = bets[betIdLookup[betId]];

		require(_bet.processed == false);

		_bet.processed = true;
		uint256 betValue = _bet.betValue;
		_bet.betValue = 0;
		_bet.rewardValue = 0;
		_bet.tokenRewardValue = 0;

		_refundPlayer(betIdLookup[betId], betValue);
		return true;
	}

	/**
	 * @dev Add funds to the contract
	 */
	function () public payable isActive {
		_setting.spinwinAddFunds(msg.value);
	}

	/******************************************/
	/*           SETTING METHODS              */
	/******************************************/
	/**
	 * @dev Triggered during escape hatch. Go through each pending bets
	 * and move the bet value to playerPendingWithdrawals so that player
	 * can withdraw later
	 */
	function refundPendingBets() public returns (bool) {
		require (msg.sender == settingAddress);
		uint256 totalBets = _setting.uintSettings('totalBets');
		if (totalBets > 0) {
			for (uint256 i = 1; i <= totalBets; i++) {
				Bet storage _bet = bets[i];
				if (_bet.processed == false) {
					uint256 _betValue = _bet.betValue;
					_bet.processed = true;
					_bet.betValue = 0;
					playerPendingWithdrawals[_bet.playerAddress] = playerPendingWithdrawals[_bet.playerAddress].add(_betValue);
					emit LogResult(_bet.betId, _bet.playerAddress, _bet.playerNumber, 0, _betValue, _bet.houseEdge, 0, 0, 4);
				}
			}
		}
		return true;
	}

	/******************************************/
	/*            PUBLIC METHODS              */
	/******************************************/
	/**
	 * @dev Player places a bet. If it has a `referrerAddress`, we want to give reward to the referrer accordingly.
	 * @dev If there is a bet that needs to be cleared, we will do it here too.
	 * @param playerNumber The number that the player chose
	 * @param houseEdge The house edge percentage that the player chose
	 * @param clearBetId The bet ID to be cleared
	 * @param referreeAddress The referree address if exist
	 * @return Return true if success
	 */
	function rollDice(uint256 playerNumber, uint256 houseEdge, bytes32 clearBetId, address referreeAddress) public
		payable
		canBet(msg.value, playerNumber, houseEdge)
		returns (bool) {
		uint256 betInternalId = _storeBet(msg.value, msg.sender, playerNumber, houseEdge);

		// Check if we need to clear a pending bet
		if (clearBetId != '') {
			_clearSingleBet(msg.sender, clearBetId, _setting.uintSettings('blockSecurityCount'));
		}

		// Check if we need to reward the referree
		_rewardReferree(referreeAddress, betInternalId);

		_advertising.incrementBetCounter();

		return true;
	}

	/**
	 * @dev Player can clear multiple bets
	 * @param betIds The bet ids to be cleared
	 */
	function clearBets(bytes32[] betIds) public isActive {
		require (betIds.length > 0 && betIds.length <= _setting.uintSettings('maxNumClearBets'));
		bool canClear = false;
		uint256 blockSecurityCount = _setting.uintSettings('blockSecurityCount');
		for (uint256 i = 0; i < betIds.length; i++) {
			Bet memory _bet = bets[betIdLookup[betIds[i]]];
			if (_bet.processed == false && _setting.uintSettings('contractBalance') >= _bet.rewardValue && (block.number.sub(_bet.blockNumber)) >= blockSecurityCount) {
				canClear = true;
				break;
			}
		}
		require(canClear == true);

		// Loop through each bets and clear it if possible
		for (i = 0; i < betIds.length; i++) {
			_clearSingleBet(msg.sender, betIds[i], blockSecurityCount);
		}
		emit LogClearBets(msg.sender);
	}

	/**
	 * @dev Allow player to claim lottery blocks reward
	 * and spend it on lottery blocks
	 */
	function claimLotteryBlocks() public isActive {
		require (_lottery.isActive() == true);
		require (lotteryBlocksAmount[msg.sender] > 0);
		uint256 claimAmount = lotteryBlocksAmount[msg.sender];
		lotteryBlocksAmount[msg.sender] = 0;
		uint256 claimStatus = 1;
		if (!_lottery.claimReward(msg.sender, claimAmount)) {
			claimStatus = 0;
			lotteryBlocksAmount[msg.sender] = claimAmount;
		}
		emit LogClaimLotteryBlocks(msg.sender, _lottery.getNumLottery(), claimAmount, claimStatus);
	}

	/**
	 * @dev Player exchanges token for Wei
	 * @param tokenAmount The amount of token to be exchanged
	 * @return Return true if success
	 */
	function exchangeToken(uint256 tokenAmount) public
		isExchangeAllowed(msg.sender, tokenAmount) {
		(uint256 weiValue, uint256 sendWei, uint256 tokenRemainder, uint256 burnToken) = _lib.calculateExchangeTokenValue(settingAddress, tokenAmount);

		_setting.spinwinIncrementUintSetting('totalTokenExchanges');

		// Generate exchangeId
		bytes32 _exchangeId = keccak256(abi.encodePacked(this, msg.sender, _setting.uintSettings('totalTokenExchanges')));
		TokenExchange storage _tokenExchange = tokenExchanges[_exchangeId];

		// Make sure we don't process the exchange bet twice
		require (_tokenExchange.processed == false);

		// Update exchange metric
		_setting.spinwinUpdateExchangeMetric(sendWei);

		/*
		 * Store the info about this exchange
		 */
		_tokenExchange.playerAddress = msg.sender;
		_tokenExchange.exchangeId = _exchangeId;
		_tokenExchange.processed = true;

		/*
		 * Burn token at this address
		 */
		if (!_spintoken.burnAt(_tokenExchange.playerAddress, burnToken)) {
			uint256 exchangeStatus = 2; // status = failed destroy token

		} else {
			if (!_tokenExchange.playerAddress.send(sendWei)) {
				exchangeStatus = 0; // status = failed send

				// If send failed, let player withdraw via playerWithdrawPendingTransactions
				playerPendingWithdrawals[_tokenExchange.playerAddress] = playerPendingWithdrawals[_tokenExchange.playerAddress].add(sendWei);
			} else {
				exchangeStatus = 1; // status = success
			}
		}
		// Update the token to wei exchange rate
		_setting.spinwinUpdateTokenToWeiExchangeRate();

		emit LogTokenExchange(_tokenExchange.exchangeId, _tokenExchange.playerAddress, tokenAmount, _setting.uintSettings('tokenToWeiExchangeRateHonor'), weiValue, sendWei, tokenRemainder, exchangeStatus);
	}

	/**
	 * @dev Calculate winning ETH when player wins
	 * @param betValue The amount of ETH for this bet
	 * @param playerNumber The number that player chose
	 * @param houseEdge The house edge for this bet
	 * @return The amount of ETH to be sent to player if he/she wins
	 */
	function calculateWinningReward(uint256 betValue, uint256 playerNumber, uint256 houseEdge) public view returns (uint256) {
		return _lib.calculateWinningReward(betValue, playerNumber, houseEdge);
	}

	/**
	 * @dev Calculates token reward amount when player loses
	 * @param betValue The amount of ETH for this bet
	 * @param playerNumber The number that player chose
	 * @param houseEdge The house edge for this bet
	 * @return The amount of token to be sent to player if he/she loses
	 */
	function calculateTokenReward(uint256 betValue, uint256 playerNumber, uint256 houseEdge) public constant returns (uint256) {
		return _lib.calculateTokenReward(settingAddress, betValue, playerNumber, houseEdge);
	}

	/**
	 * @dev Player withdraws balance in case of a failed refund or failed win send
	 */
	function playerWithdrawPendingTransactions() public {
		require(playerPendingWithdrawals[msg.sender] > 0);
		uint256 withdrawAmount = playerPendingWithdrawals[msg.sender];
		playerPendingWithdrawals[msg.sender] = 0;

		// External call to untrusted contract
		uint256 status = 1; // status = success
		if (!msg.sender.send(withdrawAmount)) {
			status = 0; // status = failed

			/*
			 * If send failed, revert playerPendingWithdrawals[msg.sender] = 0
			 * so that player can try to withdraw again later
			 */
			playerPendingWithdrawals[msg.sender] = withdrawAmount;
		}
		emit LogPlayerWithdrawBalance(msg.sender, withdrawAmount, status);
	}

	/**
	 * @dev Players withdraws SPIN token balance in case of a failed token transfer
	 */
	function playerWithdrawPendingTokenTransactions() public {
		require(playerPendingTokenWithdrawals[msg.sender] > 0);
		uint256 withdrawAmount = playerPendingTokenWithdrawals[msg.sender];
		playerPendingTokenWithdrawals[msg.sender] = 0;

		// Mint and transfer token to msg.sender
		uint256 status = 1; // status = success
		if (!_spintoken.mintTransfer(msg.sender, withdrawAmount)) {
			status = 0; // status = failed
			/*
			 * If transfer failed, revert playerPendingTokenWithdrawals[msg.sender] = 0
			 * so that player can try to withdraw again later
			 */
			playerPendingTokenWithdrawals[msg.sender] = withdrawAmount;
		}
		emit LogPlayerWithdrawTokenBalance(msg.sender, withdrawAmount, status);
	}

	/**
	 * @dev Player gets bet information based on betId
	 * @return The bet information
	 */
	function playerGetBet(bytes32 betId) public
		constant returns (uint256, uint256, uint256, uint256, uint256, uint256, bool) {
		require(betIdLookup[betId] > 0 && bets[betIdLookup[betId]].betId == betId);
		Bet memory _bet = bets[betIdLookup[betId]];
		return (_bet.betValue, _bet.diceResult, _bet.playerNumber, _bet.houseEdge, _bet.rewardValue, _bet.tokenRewardValue, _bet.processed);
	}

	/**
	 * @dev Player gets pending bet IDs
	 * @return The pending bet IDs
	 */
	function playerGetPendingBetIds() public constant returns (bytes32[]) {
		bytes32[] memory pendingBetIds = new bytes32[](totalPendingBets);
		if (totalPendingBets > 0) {
			uint256 counter = 0;
			for (uint256 i = 1; i <= _setting.uintSettings('totalBets'); i++) {
				Bet memory _bet = bets[i];
				if (_bet.processed == false) {
					pendingBetIds[counter] = _bet.betId;
					counter++;
				}
				if (counter == totalPendingBets) {
					break;
				}
			}
		}
		return pendingBetIds;
	}

	/**
	 * @dev Player gets pending bet information based on betId
	 * @return The bet information
	 */
	function playerGetPendingBet(bytes32 betId) public
		constant returns (address, uint256, uint256, uint256, uint256) {
		require(betIdLookup[betId] > 0 && bets[betIdLookup[betId]].betId == betId);
		Bet memory _bet = bets[betIdLookup[betId]];
		return (_bet.playerAddress, _bet.playerNumber, _bet.betValue, _bet.houseEdge, _bet.blockNumber);
	}

	/**
	 * @dev Calculates lottery block rewards when player clears a bet
	 * @return The amount of lottery blocks to be rewarded when player clears bet
	 */
	function calculateClearBetBlocksReward() public constant returns (uint256) {
		return _lib.calculateClearBetBlocksReward(settingAddress, lotteryAddress);
	}


	/******************************************/
	/*           INTERNAL METHODS             */
	/******************************************/

	/**
	 * @dev Stores bet information.
	 * @param betValue The value of the bet
	 * @param playerAddress The player address
	 * @param playerNumber The number that player chose
	 * @param houseEdge The house edge for this bet
	 * @return The internal  bet ID of this bet
	 */
	function _storeBet (uint256 betValue, address playerAddress, uint256 playerNumber, uint256 houseEdge) internal returns (uint256) {
		// Update the setting metric
		_setting.spinwinRollDice(betValue);

		uint256 betInternalId = _setting.uintSettings('totalBets');

		// Generate betId
		bytes32 betId = keccak256(abi.encodePacked(this, playerAddress, betInternalId));

		Bet storage _bet = bets[betInternalId];

		// Make sure we don't process the same bet twice
		require (_bet.processed == false);

		// Store the info about this bet
		betIdLookup[betId] = betInternalId;
		_bet.playerAddress = playerAddress;
		_bet.betId = betId;
		_bet.betValue = betValue;
		_bet.playerNumber = playerNumber;
		_bet.houseEdge = houseEdge;

		// Safely calculate winning reward
		_bet.rewardValue = calculateWinningReward(betValue, playerNumber, houseEdge);

		// Safely calculate token payout
		_bet.tokenRewardValue = calculateTokenReward(betValue, playerNumber, houseEdge);
		_bet.blockNumber = block.number;

		// Update the pendingBets counter
		totalPendingBets++;

		emit LogBet(_bet.betId, _bet.playerAddress, _bet.playerNumber, _bet.betValue, _bet.houseEdge, _bet.rewardValue, _bet.tokenRewardValue);
		return betInternalId;
	}

	/**
	 * @dev Internal function to clear single bet
	 * @param playerAddress The player who clears this bet
	 * @param betId The bet ID to be cleared
	 * @param blockSecurityCount The block security count to be checked
	 * @return true if success, false otherwise
	 */
	function _clearSingleBet(address playerAddress, bytes32 betId, uint256 blockSecurityCount) internal returns (bool) {
		if (betIdLookup[betId] > 0) {
			Bet memory _bet = bets[betIdLookup[betId]];

			/* Check if we can clear this bet
			 * - Make sure we don't process the same bet twice
			 * - Check if contract can payout on win
			 * - block number difference >= blockSecurityCount
			 */
			if (_bet.processed == false && _setting.uintSettings('contractBalance') >= _bet.rewardValue && (block.number.sub(_bet.blockNumber)) >= blockSecurityCount) {
				_processBet(playerAddress, betIdLookup[betId], true);
			} else {
				emit LogRewardLotteryBlocks(playerAddress, _bet.betId, 0, 2, 0);
			}
			return true;
		} else {
			emit LogBetNotFound(betId);
			return false;
		}
	}

	/**
	 * @dev Internal function to process existing bet.
	 * If no dice result, then we initiate a refund.
	 * If player wins (dice result < player number), we send player winning ETH.
	 * If player loses (dice result >= player number), we send player some SPIN token.
	 * If player loses and bankroll goal is reached, spinwin will contribute some ETH to lottery contract address.
	 *
	 * @param triggerAddress The player who clears this bet
	 * @param betInternalId The bet internal ID to be processed
	 * @param isClearMultiple Whether or not this is part of clear multiple bets transaction
	 * @return Return true if success
	 */
	function _processBet(address triggerAddress, uint256 betInternalId, bool isClearMultiple) internal returns (bool) {
		Bet storage _bet =  bets[betInternalId];
		uint256 _betValue = _bet.betValue;
		uint256 _rewardValue = _bet.rewardValue;
		uint256 _tokenRewardValue = _bet.tokenRewardValue;

		// Prevent re-entrancy
		_bet.processed = true;
		_bet.betValue = 0;
		_bet.rewardValue = 0;
		_bet.tokenRewardValue = 0;

		// Generate the result
		_bet.diceResult = _lib.generateRandomNumber(settingAddress, _bet.blockNumber, _setting.uintSettings('totalBets').add(_setting.uintSettings('totalWeiWagered')), 100);

		if (_bet.diceResult == 0) {
			/*
			 * Invalid random number. Refund the player
			 */
			_refundPlayer(betInternalId, _betValue);
		} else if (_bet.diceResult < _bet.playerNumber) {
			/*
			 * Player wins. Send the player the winning eth amount
			 */
			_payWinner(betInternalId, _betValue, _rewardValue);
		} else {
			/*
			 * Player loses. Send the player 1 wei and the spintoken amount
			 */
			_payLoser(betInternalId, _betValue, _tokenRewardValue);
		}
		// Update the pendingBets counter
		totalPendingBets--;

		// Update the token to wei exchange rate
		_setting.spinwinUpdateTokenToWeiExchangeRate();

		// Calculate the lottery blocks reward for this transaction
		uint256 lotteryBlocksReward = calculateClearBetBlocksReward();

		// If this is a single clear (from placing bet), we want to multiply this with clearSingleBetMultiplier
		if (isClearMultiple == false) {
			uint256 multiplier = _setting.uintSettings('clearSingleBetMultiplier');
		} else {
			multiplier = _setting.uintSettings('clearMultipleBetsMultiplier');
		}
		lotteryBlocksReward = (lotteryBlocksReward.mul(multiplier)).div(TWO_DECIMALS);

		lotteryBlocksAmount[triggerAddress] = lotteryBlocksAmount[triggerAddress].add(lotteryBlocksReward);
		emit LogRewardLotteryBlocks(triggerAddress, _bet.betId, lotteryBlocksReward, 2, 1);
		return true;
	}

	/**
	 * @dev Refund the player when we are unable to determine the dice result
	 * @param betInternalId The bet internal ID
	 * @param refundAmount The amount to be refunded
	 */
	function _refundPlayer(uint256 betInternalId, uint256 refundAmount) internal {
		Bet memory _bet =  bets[betInternalId];
		/*
		 * Send refund - external call to an untrusted contract
		 * If send fails, map refund value to playerPendingWithdrawals[address]
		 * for withdrawal later via playerWithdrawPendingTransactions
		 */
		int256 betStatus = 3; // status = refund
		if (!_bet.playerAddress.send(refundAmount)) {
			betStatus = 4; // status = refund + failed send

			// If send failed, let player withdraw via playerWithdrawPendingTransactions
			playerPendingWithdrawals[_bet.playerAddress] = playerPendingWithdrawals[_bet.playerAddress].add(refundAmount);
		}
		emit LogResult(_bet.betId, _bet.playerAddress, _bet.playerNumber, _bet.diceResult, refundAmount, _bet.houseEdge, 0, 0, betStatus);
	}

	/**
	 * @dev Pays the player the winning eth amount
	 * @param betInternalId The bet internal ID
	 * @param betValue The original wager
	 * @param playerProfit The player profit
	 */
	function _payWinner(uint256 betInternalId, uint256 betValue, uint256 playerProfit) internal {
		Bet memory _bet =  bets[betInternalId];
		// Update setting's contract balance and total wei won
		_setting.spinwinUpdateWinMetric(playerProfit);

		// Safely calculate payout via profit plus original wager
		playerProfit = playerProfit.add(betValue);

		/*
		 * Send win - external call to an untrusted contract
		 * If send fails, map reward value to playerPendingWithdrawals[address]
		 * for withdrawal later via playerWithdrawPendingTransactions
		 */
		int256 betStatus = 1; // status = win
		if (!_bet.playerAddress.send(playerProfit)) {
			betStatus = 2; // status = win + failed send

			// If send failed, let player withdraw via playerWithdrawPendingTransactions
			playerPendingWithdrawals[_bet.playerAddress] = playerPendingWithdrawals[_bet.playerAddress].add(playerProfit);
		}
		emit LogResult(_bet.betId, _bet.playerAddress, _bet.playerNumber, _bet.diceResult, betValue, _bet.houseEdge, playerProfit, 0, betStatus);
	}

	/**
	 * @dev Pays the player 1 wei and the spintoken amount
	 * @param betInternalId The bet internal ID
	 * @param betValue The original wager
	 * @param tokenRewardValue The token reward for this bet
	 */
	function _payLoser(uint256 betInternalId, uint256 betValue, uint256 tokenRewardValue) internal {
		Bet memory _bet =  bets[betInternalId];
		/*
		 * Update the game setting metric when player loses
		 */
		_setting.spinwinUpdateLoseMetric(betValue, tokenRewardValue);

		int256 betStatus; // status = lose

		/*
		 * Send 1 Wei to losing bet - external call to an untrusted contract
		 */
		if (!_bet.playerAddress.send(1)) {
			betStatus = -1; // status = lose + failed send

			// If send failed, let player withdraw via playerWithdrawPendingTransactions
			playerPendingWithdrawals[_bet.playerAddress] = playerPendingWithdrawals[_bet.playerAddress].add(1);
		}

		/*
		 * Mint and transfer token reward to this player
		 */
		if (tokenRewardValue > 0) {
			if (!_spintoken.mintTransfer(_bet.playerAddress, tokenRewardValue)) {
				betStatus = -2; // status = lose + failed mint and transfer

				// If transfer token failed, let player withdraw via playerWithdrawPendingTokenTransactions
				playerPendingTokenWithdrawals[_bet.playerAddress] = playerPendingTokenWithdrawals[_bet.playerAddress].add(tokenRewardValue);
			}
		}
		emit LogResult(_bet.betId, _bet.playerAddress, _bet.playerNumber, _bet.diceResult, betValue, _bet.houseEdge, 1, tokenRewardValue, betStatus);
		_sendLotteryContribution(betInternalId, betValue);
	}

	/**
	 * @dev Contribute the house win to lottery address
	 * @param betInternalId The bet internal ID
	 * @param betValue The original wager
	 * @return Return true if success
	 */
	function _sendLotteryContribution(uint256 betInternalId, uint256 betValue) internal returns (bool) {
		/*
		 * If contractBalance >= minBankroll, contribute the a percentage of the winning to lottery
		 */
		uint256 contractBalance = _setting.uintSettings('contractBalance');
		if (contractBalance >= _setting.uintSettings('minBankroll')) {
			Bet memory _bet =  bets[betInternalId];
			uint256 lotteryContribution = _lib.calculateLotteryContribution(settingAddress, lotteryAddress, betValue);

			if (lotteryContribution > 0 && contractBalance >= lotteryContribution) {
				// Safely adjust contractBalance
				_setting.spinwinUpdateLotteryContributionMetric(lotteryContribution);

				emit LogLotteryContribution(_bet.betId, _bet.playerAddress, lotteryContribution);

				// Contribute to the lottery
				if (!lotteryAddress.call.gas(_setting.uintSettings('gasForLottery')).value(lotteryContribution)()) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * @dev Reward the referree if necessary.
	 * @param referreeAddress The address of the referree
	 * @param betInternalId The internal bet ID
	 */
	function _rewardReferree(address referreeAddress, uint256 betInternalId) internal {
		Bet memory _bet = bets[betInternalId];

		// If the player already has a referee, use that address
		if (referees[_bet.playerAddress] != address(0)) {
			referreeAddress = referees[_bet.playerAddress];
		}
		if (referreeAddress != address(0) && referreeAddress != _bet.playerAddress) {
			referees[_bet.playerAddress] = referreeAddress;
			uint256 _tokenForLotto = _bet.tokenRewardValue.mul(_setting.uintSettings('referralPercent')).div(PERCENTAGE_DIVISOR);
			lotteryBlocksAmount[referreeAddress] = lotteryBlocksAmount[referreeAddress].add(_tokenForLotto);
			emit LogRewardLotteryBlocks(referreeAddress, _bet.betId, _tokenForLotto, 1, 1);
		}
	}
}