contract EthFlip is usingOraclize {
    
  // Bet archival
  struct Bet {
    bool win;
    uint betValue;
    uint timestamp;
    address playerAddress;
    uint randomNumber;
  }
  
  // Player archival
  struct Player {
    uint[] betNumbers;
  }
    
  // Oraclize query callback data preservation
  struct QueryMap {
    uint betValue;
    address playerAddress;
  }
  
  // Game parameters
  bool private gamePaused;
  uint private minBet;
  uint private maxBet;
  uint private houseFee;
  uint private oraclizeGas;
  uint private oraclizeGasPrice;
  address private owner;
  
  // Static game information
  uint private currentQueryId;
  uint private currentBetNumber;
  uint private totalPayouts;
  uint private totalWins;
  uint private totalLosses;

  // Epoch variables
  bool private win;
  uint private randomNumber;
  mapping (address => Player) private playerBetNumbers;
  mapping (uint => Bet) private pastBets;
  mapping (uint => QueryMap) private queryIdMap;
  
  // Events
  event BetComplete(bool _win, uint _betNumber, uint _betValue, uint _timestamp, address _playerAddress, uint _randomNumber);
  event GameStatusUpdate(bool _paused);
  event MinBetUpdate(uint _newMin);
  event MaxBetUpdate(uint _newMax);
  event HouseFeeUpdate(uint _newFee);
  event OwnerUpdate(address _newOwner);
  
  // Modifiers
  modifier gameIsActive {
    require(!gamePaused);
    _;
  }
  
  modifier gameIsNotActive {
    require(gamePaused);
    _;
  }
  
  modifier senderIsOwner {
    require(msg.sender == owner);
    _;
  }
  
  modifier senderIsOraclize {
    require(msg.sender == oraclize_cbAddress());
    _;
  }
  
  modifier sentEnoughForBet {
    require(msg.value >= minBet);
    _;
  }
  
  modifier didNotSendOverMaxBet {
    require(msg.value <= maxBet);
    _;
  }

  // Constructor
  function EthFlip() public {
    minBet = 100000000000000000;
    maxBet = 500000000000000000;
    houseFee = 29; // 2.9%
    oraclizeGas = 500000;
    oraclizeGasPrice = 3010000000;
    oraclize_setCustomGasPrice(oraclizeGasPrice);
    oraclize_setProof(proofType_Ledger);
    owner = msg.sender;
    
    // Carry-over from old contract
    totalPayouts = 1728380000000000000;
    totalWins = 10;
    totalLosses = 15;
  }
  
  // Fallback
  function() public payable {}

  // Placing a bet
  function placeBet() public payable gameIsActive sentEnoughForBet didNotSendOverMaxBet {
    secureGenerateNumber(msg.sender, msg.value);
  }
  
  // Securely generate number randomly
  function secureGenerateNumber(address _playerAddress, uint _betValue) private {
    bytes32 queryId = oraclize_newRandomDSQuery(0, 1, oraclizeGas);
    uint convertedId = uint(keccak256(queryId));
    queryIdMap[convertedId].betValue = _betValue;
    queryIdMap[convertedId].playerAddress = _playerAddress;
  }
  
  // Check if the player won or refund if randomness proof failed
  function checkIfWon() private {
    if (randomNumber != 101) {
      if (randomNumber <= 50) {
        win = true;
        sendPayout(subtractHouseFee(queryIdMap[currentQueryId].betValue*2));
      } else {
        win = false;
        sendOneWei();
      }
    } else {
      win = false;
      sendRefund();
    }
    logBet();
  }
  
  // Winner payout
  function sendPayout(uint _amountToPayout) private {
    uint payout = _amountToPayout;
    _amountToPayout = 0;
    queryIdMap[currentQueryId].playerAddress.transfer(payout);
  }
  
  // Loser payout
  function sendOneWei() private {
    queryIdMap[currentQueryId].playerAddress.transfer(1);
  }
  
  // Refund player
  function sendRefund() private {
    queryIdMap[currentQueryId].playerAddress.transfer(queryIdMap[currentQueryId].betValue);
  }
  
  // Helpers
  function subtractHouseFee(uint _amount) view private returns (uint _result) {
    return (_amount*(1000-houseFee))/1000;
  }
  
  function logBet() private {
    // Static updates
    currentBetNumber++;
    if (win) {
      totalWins++;
      totalPayouts += subtractHouseFee(queryIdMap[currentQueryId].betValue*2);
    } else {
      if (randomNumber != 101) {
        totalLosses++;
      }
    }
    
    // Bets updates
    pastBets[currentBetNumber] = Bet({win:win, betValue:queryIdMap[currentQueryId].betValue, timestamp:block.timestamp, playerAddress:queryIdMap[currentQueryId].playerAddress, randomNumber:randomNumber});
    
    // // Player updates
    playerBetNumbers[queryIdMap[currentQueryId].playerAddress].betNumbers.push(currentBetNumber);
    
    // Emit complete event
    BetComplete(win, currentBetNumber, queryIdMap[currentQueryId].betValue, block.timestamp, queryIdMap[currentQueryId].playerAddress, randomNumber);
    queryIdMap[currentQueryId].betValue = 0;
  }
  
  // Static information getters
  function getLastBetNumber() constant public returns (uint) {
    return currentBetNumber;
  }
  
  function getTotalPayouts() constant public returns (uint) {
    return totalPayouts;
  }
  
  function getTotalWins() constant public returns (uint) {
    return totalWins;
  }
  
  function getTotalLosses() constant public returns (uint) {
    return totalLosses;
  }
  
  // Game information getters
  function getBalance() constant public returns (uint) {
    return this.balance;
  }
  
  function getGamePaused() constant public returns (bool) {
      return gamePaused;
  }
  
  function getMinBet() constant public returns (uint) {
      return minBet;
  }
  
  function getMaxBet() constant public returns (uint) {
      return maxBet;
  }
  
  function getHouseFee() constant public returns (uint) {
      return houseFee;
  }
  
  function getOraclizeGas() constant public returns (uint) {
      return oraclizeGas;
  }
  
  function getOraclizeGasPrice() constant public returns (uint) {
      return oraclizeGasPrice;
  }
  
  function getOwnerAddress() constant public returns (address) {
      return owner;
  }
  
  function getPlayerBetNumbers(address _playerAddress) constant public returns (uint[] _betNumbers) {
    return (playerBetNumbers[_playerAddress].betNumbers);
  }
  
  function getPastBet(uint _betNumber) constant public returns (bool _win, uint _betValue, uint _timestamp, address _playerAddress, uint _randomNumber) {
    require(currentBetNumber >= _betNumber);
    return (pastBets[_betNumber].win, pastBets[_betNumber].betValue, pastBets[_betNumber].timestamp, pastBets[_betNumber].playerAddress, pastBets[_betNumber].randomNumber);
  }
  
  // Owner only setters
  // Changes made here only apply while the game is PAUSED (with notification on the website), so all participants
  // may audit the contract on unpause prior to placing bets in the proceeding rounds; in addition, all changes will
  // be updated on the website simultaneously to ensure everyone is informed prior to placing bets
  function pauseGame() public senderIsOwner gameIsActive {
    gamePaused = true;
    GameStatusUpdate(true);
  }
  
  function resumeGame() public senderIsOwner gameIsNotActive {
    gamePaused = false;
    GameStatusUpdate(false);
  }
  
  function setMaxBet(uint _newMax) public senderIsOwner gameIsNotActive {
    require(_newMax >= 100000000000000000);
    maxBet = _newMax;
    MaxBetUpdate(_newMax);
  }
  
  function setMinBet(uint _newMin) public senderIsOwner gameIsNotActive {
    require(_newMin >= 100000000000000000);
    minBet = _newMin;
    MinBetUpdate(_newMin);
  }
  
  function setHouseFee(uint _newFee) public senderIsOwner gameIsNotActive {
    require(_newFee <= 100);
    houseFee = _newFee;
    HouseFeeUpdate(_newFee);
  }
  
  function setOraclizeGas(uint _newGas) public senderIsOwner gameIsNotActive {
    oraclizeGas = _newGas;
  }
  
  function setOraclizeGasPrice(uint _newPrice) public senderIsOwner gameIsNotActive {
    oraclizeGasPrice = _newPrice + 10000000;
    oraclize_setCustomGasPrice(oraclizeGasPrice);
  }
  
  function setOwner(address _newOwner) public senderIsOwner gameIsNotActive {
    owner = _newOwner;
    OwnerUpdate(_newOwner);
  }
  
  function selfDestruct() public senderIsOwner gameIsNotActive {
    selfdestruct(owner);
  }
  
  // Oraclize random number function
  // the callback function is called by Oraclize when the result is ready
  // the oraclize_randomDS_proofVerify modifier prevents an invalid proof to execute this function code:
  // the proof validity is fully verified on-chain
  function __callback(bytes32 _queryId, string _result, bytes _proof) public senderIsOraclize {
     currentQueryId = uint(keccak256(_queryId));
    if (oraclize_randomDS_proofVerify__returnCode(_queryId, _result, _proof) == 0) {
      randomNumber = (uint(keccak256(_result)) % 100) + 1;
    } else {
      randomNumber = 101;
    }
    if (queryIdMap[currentQueryId].betValue != 0) {
      checkIfWon();
    }
  }
}
/*
 EthFlip
 
 Copyright (c) 2018 Maxwell Black
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
 
 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 */
function checkIfWon() private {
  if (randomNumber != 101) {
    if (randomNumber <= 50) {
      win = true;
      sendPayout(subtractHouseFee(queryIdMap[currentQueryId].betValue*2));
    } else {
      win = false;
      sendOneWei();
    }
  } else {
    win = false;
    sendRefund();
  }
  logBet();
}

// Check if the player won or refund if randomness proof failed