contract Bet0xgame is usingOraclize {
  address public owner;
  string public oraclizeSource;

  mapping(string => mapping(address => uint)) book;
  mapping(string => uint) oddsMapping;
  mapping(bytes32 => bool) validQueryIds;

  mapping(string => uint) teamMapping;
  mapping(bool => uint) boolMapping;

  mapping(address => mapping(string => Player)) playerData;
  struct Player {
    uint betAmount;
    string team;
    bool withdrawn;
  }

  string public WINNER;
  string public loserOne;
  string public loserTwo;

  string public teamOne;
  string public teamTwo;
  string public draw;

  uint public betsCloseAt;
  uint public endsAt;

  event PlayerJoined(
    address indexed playerAddress,
    uint indexed betAmount,
    string indexed team
  );

  event RewardWithdrawn(
    address indexed withdrawer,
    uint indexed withdrawnAmount
  );

  event WinningTeamSet(string indexed team);
  event OraclizeQuery(string indexed description);

  modifier onlyOwner {
    require(msg.sender == address(owner));
    _;
  }

  modifier onlyValidTeam(string _team) {
    require(
      keccak256(bytes(_team)) == keccak256(bytes(teamOne)) ||
        keccak256(bytes(_team)) == keccak256(bytes(teamTwo)) ||
          keccak256(bytes(_team)) == keccak256(bytes(draw))
    );
    _;
  }

  modifier onlyAfterEndTime() {
    require(now >= endsAt);
    _;
  }

  modifier onlyIfWinnerIsMissing() {
    bytes memory winnerBytes = bytes(WINNER);
    require(winnerBytes.length == 0);
    _;
  }

  modifier onlyIfWinnerIsSet() {
    bytes memory winnerBytes = bytes(WINNER);
    require(winnerBytes.length != 0);
    _;
  }

  modifier endsAtAfterBetsCloseAt(uint _betsCloseAt, uint _endsAt) {
    require(_betsCloseAt < _endsAt);
    _;
  }

  modifier onlyBeforeBetsCloseAt() {
    require(now < betsCloseAt);
    _;
  }

  function Bet0xgame(string _teamOne, string _teamTwo, uint _endsAt, uint _betsCloseAt, string _oraclizeSource) public
    endsAtAfterBetsCloseAt(_betsCloseAt, _endsAt)
  {
    oraclize_setProof(proofType_TLSNotary);
    owner = msg.sender;

    teamOne = _teamOne;
    teamTwo = _teamTwo;
    draw = "draw";
    oraclizeSource = _oraclizeSource;

    buildTeamMapping(teamOne, teamTwo, draw);
    buildBoolMapping();

    betsCloseAt = _betsCloseAt;
    endsAt = _endsAt;
  }

  function bet(string _team) public payable
    onlyValidTeam(_team)
    onlyBeforeBetsCloseAt()
  {
    book[_team][msg.sender] += msg.value;
    oddsMapping[_team] += msg.value;

    if (playerData[msg.sender][_team].betAmount == 0) {
      playerData[msg.sender][_team] = Player(msg.value, _team, false);
    } else {
      playerData[msg.sender][_team].betAmount += msg.value;
    }

    PlayerJoined(msg.sender, msg.value, _team);
  }

  function withdrawReward() public onlyAfterEndTime() onlyIfWinnerIsSet() {
    uint betAmount = book[WINNER][msg.sender];
    uint reward = betAmount + (betAmount * (oddsMapping[loserOne] + oddsMapping[loserTwo]) / oddsMapping[WINNER]);

    address(msg.sender).transfer(reward);

    playerData[msg.sender][WINNER].withdrawn = true;
    book[WINNER][msg.sender] = 0;

    RewardWithdrawn(msg.sender, reward);
  }

  function __callback(bytes32 _queryId, string _result, bytes _proof) public
    onlyValidTeam(_result)
  {
    if (!validQueryIds[_queryId]) revert();
    if (msg.sender != oraclize_cbAddress()) revert();

    WINNER = _result;
    delete validQueryIds[_queryId];

    WinningTeamSet(_result);
    setLosers();
  }

  function oraclizeSetWinner(uint _callback_wei, uint _callback_gas_limit) public payable
    onlyAfterEndTime
    onlyIfWinnerIsMissing
  {
    require(oraclize_getPrice("URL", _callback_gas_limit) < msg.value);

    oraclize_setCustomGasPrice(_callback_wei);

    if (oraclize_getPrice("URL") > address(this).balance) {
      OraclizeQuery("Oraclize query not sent, balance too low");
    } else {
      bytes32 queryId = oraclize_query("URL", oraclizeSource, _callback_gas_limit);
      validQueryIds[queryId] = true;

      OraclizeQuery("Oraclize query sent!");
    }
  }

  //  see private method buildTeamMapping, buildBoolMapping
  //  first element in the nested array represents the team user betted on:
  //    (teamOne -> 0, teamTwo -> 1, draw -> 2)
  //  second element in nested array is the bet amount
  //  third element in nested array represents withdrawal status:
  //    (false -> 0, true -> 1)
  //  additionally (applies to first level elements):
  //    first array holds player data for teamOne
  //    second array holds player data for teamTwo
  //    third array holds pleyer data for draw
  function getPlayerData(address _playerAddress) public view returns(uint[3][3]) {
    return [
      [
        teamMapping[playerData[_playerAddress][teamOne].team],
        playerData[_playerAddress][teamOne].betAmount,
        boolMapping[playerData[_playerAddress][teamOne].withdrawn]
      ],
      [
        teamMapping[playerData[_playerAddress][teamTwo].team],
        playerData[_playerAddress][teamTwo].betAmount,
        boolMapping[playerData[_playerAddress][teamTwo].withdrawn]
      ],
      [
        teamMapping[playerData[_playerAddress][draw].team],
        playerData[_playerAddress][draw].betAmount,
        boolMapping[playerData[_playerAddress][draw].withdrawn]
      ]
    ];
  }

  function getPoolAmounts() public view returns(uint[3]) {
    return [oddsMapping[teamOne], oddsMapping[teamTwo], oddsMapping[draw]];
  }

  function withdrawRemainingRewards() public
    onlyOwner
    onlyAfterEndTime
    onlyIfWinnerIsSet
  {
    address(owner).transfer(address(this).balance);
  }

  function setLosers() private returns(string) {
    if (keccak256(bytes(WINNER)) == keccak256(bytes(teamOne))) {
      loserOne = teamTwo;
      loserTwo = draw;
    } else if (keccak256(bytes(WINNER)) == keccak256(bytes(teamTwo))) {
      loserOne = teamOne;
      loserTwo = draw;
    } else if (keccak256(bytes(WINNER)) == keccak256(bytes(draw))) {
      loserOne = teamOne;
      loserTwo = teamTwo;
    }
  }

  function buildTeamMapping(string _teamOne, string _teamTwo, string _draw) private {
    teamMapping[_teamOne] = 0;
    teamMapping[_teamTwo] = 1;
    teamMapping[_draw] = 2;
  }

  function buildBoolMapping() private {
    boolMapping[false] = 0;
    boolMapping[true] = 1;
  }

  function () public payable {}
}
// </ORACLIZE_API>
function getPlayerData(address _playerAddress) public view returns(uint[3][3]) {
  return [
    [
      teamMapping[playerData[_playerAddress][teamOne].team],
      playerData[_playerAddress][teamOne].betAmount,
      boolMapping[playerData[_playerAddress][teamOne].withdrawn]
    ],
    [
      teamMapping[playerData[_playerAddress][teamTwo].team],
      playerData[_playerAddress][teamTwo].betAmount,
      boolMapping[playerData[_playerAddress][teamTwo].withdrawn]
    ],
    [
      teamMapping[playerData[_playerAddress][draw].team],
      playerData[_playerAddress][draw].betAmount,
      boolMapping[playerData[_playerAddress][draw].withdrawn]
    ]
  ];
}

//  see private method buildTeamMapping, buildBoolMapping
//  first element in the nested array represents the team user betted on:
//    (teamOne -> 0, teamTwo -> 1, draw -> 2)
//  second element in nested array is the bet amount
//  third element in nested array represents withdrawal status:
//    (false -> 0, true -> 1)
//  additionally (applies to first level elements):
//    first array holds player data for teamOne
//    second array holds player data for teamTwo
//    third array holds pleyer data for draw