contract EdgelessCasino is CasinoBank{
	/** the most recent known state of a state channel */
	mapping(address => State) public lastState;
	/** fired when the state is updated */
	event StateUpdate(address player, uint128 count, int128 winBalance, int difference, uint gasCost);
  /** fired if one of the parties chooses to log the seeds and results */
  event GameData(address player, bytes32[] serverSeeds, bytes32[] clientSeeds, int[] results, uint gasCost);
  
	struct State{
		uint128 count;
		int128 winBalance;
	}


  /**
  * creates a new edgeless casino contract.
  * @param predecessorAddress the address of the predecessing contract
	*				 tokenContract      the address of the Edgeless token contract
	* 			 depositLimit       the maximum deposit allowed
	* 			 kGasPrice				  the price per kGas in WEI
  **/
  function EdgelessCasino(address predecessorAddress, address tokenContract, uint depositLimit, uint kGasPrice) CasinoBank(depositLimit, predecessorAddress) Mortal(tokenContract) ChargingGas(kGasPrice) public{

  }
  
  /**
   * updates several state channels at once. can be called by authorized wallets only.
   * 1. determines the player address from the signature.
   * 2. verifies if the signed game-count is higher than the last known game-count of this channel.
   * 3. updates the balances accordingly. This means: It checks the already performed updates for this channel and computes
   *    the new balance difference to add or subtract from the player‘s balance.
   * @param winBalances array of the current wins or losses
   *				gameCounts  array of the numbers of signed game moves
   *				v,r,s       array of the players's signatures
   *        chargeGas   indicates if the gas costs should be subtracted from the players's balances
   * */
  function updateBatch(int128[] winBalances,  uint128[] gameCounts, uint8[] v, bytes32[] r, bytes32[] s, bool chargeGas) public onlyAuthorized{
    require(winBalances.length == gameCounts.length);
    require(winBalances.length == v.length);
    require(winBalances.length == r.length);
    require(winBalances.length == s.length);
    require(winBalances.length <= 50);
    address player;
    uint gasCost = 0;
    if(chargeGas) 
      gasCost = getGasCost();
    gasPayback = safeAdd(gasPayback, safeMul(gasCost, winBalances.length));
    for(uint8 i = 0; i < winBalances.length; i++){
      player = ecrecover(keccak256(winBalances[i], gameCounts[i]), v[i], r[i], s[i]);
      _updateState(player, winBalances[i], gameCounts[i], gasCost);
    }
  }

  /**
   * updates a state channel. can be called by both parties.
   * 1. verifies the signature.
   * 2. verifies if the signed game-count is higher than the last known game-count of this channel.
   * 3. updates the balances accordingly. This means: It checks the already performed updates for this channel and computes
   *    the new balance difference to add or subtract from the player‘s balance.
   * @param winBalance the current win or loss
   *				gameCount  the number of signed game moves
   *				v,r,s      the signature of either the casino or the player
   *        chargeGas  indicates if the gas costs should be subtracted from the player's balance
   * */
  function updateState(int128 winBalance,  uint128 gameCount, uint8 v, bytes32 r, bytes32 s, bool chargeGas) public{
  	address player = determinePlayer(winBalance, gameCount, v, r, s);
  	uint gasCost = 0;
  	if(player == msg.sender)//if the player closes the state channel himself, make sure the signer is a casino wallet
  		require(authorized[ecrecover(keccak256(player, winBalance, gameCount), v, r, s)]);
  	else if (chargeGas){//subtract the gas costs from the player balance only if the casino wallet is the sender
  		gasCost = getGasCost();
  		gasPayback = safeAdd(gasPayback, gasCost);
  	}
  	_updateState(player, winBalance, gameCount, gasCost);
  }
  
  /**
   * internal method to perform the actual state update.
   * @param player the player address
   *        winBalance the player's win balance
   *        gameCount  the player's game count
   * */
  function _updateState(address player, int128 winBalance,  uint128 gameCount, uint gasCost) internal {
    State storage last = lastState[player];
  	require(gameCount > last.count);
  	int difference = updatePlayerBalance(player, winBalance, last.winBalance, gasCost);
  	lastState[player] = State(gameCount, winBalance);
  	emit StateUpdate(player, gameCount, winBalance, difference, gasCost);
  }

  /**
   * determines if the msg.sender or the signer of the passed signature is the player. returns the player's address
   * @param winBalance the current winBalance, used to calculate the msg hash
   *				gameCount  the current gameCount, used to calculate the msg.hash
   *				v, r, s    the signature of the non-sending party
   * */
  function determinePlayer(int128 winBalance, uint128 gameCount, uint8 v, bytes32 r, bytes32 s) view internal returns(address){
  	if (authorized[msg.sender])//casino is the sender -> player is the signer
  		return ecrecover(keccak256(winBalance, gameCount), v, r, s);
  	else
  		return msg.sender;
  }

	/**
	 * computes the difference of the win balance relative to the last known state and adds it to the player's balance.
	 * in case the casino is the sender, the gas cost in EDG gets subtracted from the player's balance.
	 * @param player the address of the player
	 *				winBalance the current win-balance
	 *				lastWinBalance the win-balance of the last known state
	 *				gasCost the gas cost of the tx
	 * */
  function updatePlayerBalance(address player, int128 winBalance, int128 lastWinBalance, uint gasCost) internal returns(int difference){
  	difference = safeSub(winBalance, lastWinBalance);
  	int outstanding = safeSub(difference, int(gasCost));
  	uint outs;
  	if(outstanding < 0){
  		outs = uint256(outstanding * (-1));
  		playerBalance = safeSub(playerBalance, outs);
  		balanceOf[player] = safeSub(balanceOf[player], outs);
  	}
  	else{
  		outs = uint256(outstanding);
  		assert(bankroll() * oneEDG > outs);
  	  playerBalance = safeAdd(playerBalance, outs);
  	  balanceOf[player] = safeAdd(balanceOf[player], outs);
  	}
  }
  
  /**
   * logs some seeds and game results for players wishing to have their game history logged by the contract
   * @param serverSeeds array containing the server seeds
   *        clientSeeds array containing the client seeds
   *        results     array containing the results
   *        v, r, s     the signature of the non-sending party (to make sure the correct results are logged)
   * */
  function logGameData(bytes32[] serverSeeds, bytes32[] clientSeeds, int[] results, uint8 v, bytes32 r, bytes32 s) public{
    address player = determinePlayer(serverSeeds, clientSeeds, results, v, r, s);
    uint gasCost;
    //charge gas in case the server is logging the results for the player
    if(player != msg.sender){
      gasCost = (57 + 768 * serverSeeds.length / 1000)*gasPrice;
      balanceOf[player] = safeSub(balanceOf[player], gasCost);
      playerBalance = safeSub(playerBalance, gasCost);
      gasPayback = safeAdd(gasPayback, gasCost);
    }
    emit GameData(player, serverSeeds, clientSeeds, results, gasCost);
  }
  
  /**
   * determines if the msg.sender or the signer of the passed signature is the player. returns the player's address
   * @param serverSeeds array containing the server seeds
   *        clientSeeds array containing the client seeds
   *        results     array containing the results
   *				v, r, s    the signature of the non-sending party
   * */
  function determinePlayer(bytes32[] serverSeeds, bytes32[] clientSeeds, int[] results, uint8 v, bytes32 r, bytes32 s) view internal returns(address){
  	address signer = ecrecover(keccak256(serverSeeds, clientSeeds, results), v, r, s);
  	if (authorized[msg.sender])//casino is the sender -> player is the signer
  		return signer;
  	else if (authorized[signer])
  		return msg.sender;
  	else 
  	  revert();
  }

}
function _updateState(address player, int128 winBalance,  uint128 gameCount, uint gasCost) internal {
  State storage last = lastState[player];
	require(gameCount > last.count);
	int difference = updatePlayerBalance(player, winBalance, last.winBalance, gasCost);
	lastState[player] = State(gameCount, winBalance);
	emit StateUpdate(player, gameCount, winBalance, difference, gasCost);
}

/**
 * internal method to perform the actual state update.
 * @param player the player address
 *        winBalance the player's win balance
 *        gameCount  the player's game count
 * */