contract ArsenalvsManCity is usingOraclize {

  /* Declaration */

  address public OWNERS = 0xC3eD2d481B9d75835EC04174b019A7eAF2Faf78A;

  uint public constant COMMISSION = 0; // Commission for the owner
  uint public constant MIN_BET = 0.01 ether;

  uint public EXPECTED_START = 1534086000; // When the bet's event is expected to start
  uint public EXPECTED_END = 1534096800; // When the bet's event is expected to end

  uint public constant BETTING_OPENS = 1533851317;
  uint public BETTING_CLOSES = EXPECTED_START - 60; // Betting closes a minute before the bet event starts

  uint public constant PING_ORACLE_INTERVAL = 60 * 60 * 24; // Ping oracle every 24 hours until completion (or cancelation)
  uint public ORACLIZE_GAS = 200000;
  uint public CANCELATION_DATE = EXPECTED_END + 60 * 60 * 24; // Cancelation date is 24 hours after the expected end
  uint public RETURN_DATE = EXPECTED_END + 60 * 60 * 24 * 30; // Any leftover money is returned to owners 1 month after bet ends

  bool public completed;
  bool public canceled;
  bool public ownersPayed;
  uint public ownerPayout;
  bool public returnedToOwners;
  uint public winnerDeterminedDate;
  uint public numCollected = 0;

  bytes32 public nextScheduledQuery;
  uint public oraclizeFees;
  uint public collectionFees;

  struct Better {
    uint betAmount;
    uint betOption;
    bool withdrawn;
  }
  mapping(address => Better) betterInfo;

  address[] public betters;

  uint[2] public totalAmountsBet;
  uint[2] public numberOfBets;
  uint public totalBetAmount;

  uint public winningOption = 2;

  /* Events */

  event BetMade();

  /* Modifiers */

  // Modifier to only allow the
  // determination of the winner
  modifier canDetermineWinner() {
    require (winningOption == 2 && !completed && !canceled && now > BETTING_CLOSES && now >= EXPECTED_END);
    _;
  }

  // Modifier to only allow emptying
  // the remaining value of the contract
  // to owners.
  modifier canEmptyRemainings() {
    require(canceled || completed);
    uint numRequiredToCollect = canceled ? (numberOfBets[0] + numberOfBets[1]) : numberOfBets[winningOption];
    require ((now >= RETURN_DATE && !canceled) || (numCollected == numRequiredToCollect));
    _;
  }

  // Modifier to only allow the collection
  // of bet payouts when winner is determined,
  // (or withdrawals if the bet is canceled)
  modifier collectionsEnabled() {
    require (canceled || (winningOption != 2 && completed && now > BETTING_CLOSES));
    _;
  }

  // Modifier to only allow the execution of
  // owner payout when winner is determined
  modifier canPayOwners() {
    require (!canceled && winningOption != 2 && completed && !ownersPayed && now > BETTING_CLOSES);
    _;
  }

  // Modifier to only allow the execution of
  // certain functions when betting is closed
  modifier bettingIsClosed() {
    require (now >= BETTING_CLOSES);
    _;
  }

  // Modifier to only allow the execution of
  // certain functions restricted to the owners
  modifier onlyOwnerLevel() {
    require(
      OWNERS == msg.sender
    );
    _;
  }

  /* Functions */
  
  // Constructor
  function ArsenalvsManCity() public payable {
    oraclize_setCustomGasPrice(1000000000);
    callOracle(EXPECTED_END, ORACLIZE_GAS); // Kickoff Oracle checking for outcome
  }

  function changeGasLimitAndPrice(uint gas, uint price) public onlyOwnerLevel {
    ORACLIZE_GAS = gas;
    oraclize_setCustomGasPrice(price);
  }

  // Change bet expected times
  function setExpectedTimes(uint _EXPECTED_START, uint _EXPECTED_END) public onlyOwnerLevel {
    setExpectedStart(_EXPECTED_START);
    setExpectedEnd(_EXPECTED_END);
  }

  // Change bet expected start time
  function setExpectedStart(uint _EXPECTED_START) public onlyOwnerLevel {
    EXPECTED_START = _EXPECTED_START;
    BETTING_CLOSES = EXPECTED_START - 60;
  }

  // Change bet expected end time
  function setExpectedEnd(uint _EXPECTED_END) payable public onlyOwnerLevel {
    require(_EXPECTED_END > EXPECTED_START);

    EXPECTED_END = _EXPECTED_END;
    CANCELATION_DATE = EXPECTED_END + 60 * 60 * 24;
    RETURN_DATE = EXPECTED_END + 60 * 60 * 24 * 30;

    callOracle(EXPECTED_END, ORACLIZE_GAS); // Kickoff Oracle checking for winner
  }

  function callOracle(uint timeOrDelay, uint gas) private {
    require(canceled != true && completed != true);

    // Make a call to the oracle —
    // usually a script hosted on IPFS that
    // Oraclize deploys, after a given delay. We
    // leave nested query as default to maximize
    // optionality for queries.

    // To readers of the code (aka prospective betters)
    // if this is a computation query, you can view the
    // script we use to compute the winner, as it is hosted
    // on IPFS. The first argument in the computation query
    // is the IPFS hash (script would be located at
    // ipfs.io/ipfs/<HASH>). The file hosted at this hash
    // is actually a zipped folder that contains a Dockerfile and
    // the script. So, if you download the file at the hash provided,
    // ensure to convert it to a .zip, unzip it, and read the code.

    // Oraclize uses the Dockerfile to deploy this script.
    // Look over the Oraclize documentation to verify this
    // for yourself.
    nextScheduledQuery = makeOraclizeQuery(timeOrDelay, "nested", "[computation] ['QmRQAbvyJacfnNVyf4f3SWs1kjKdJf36eaXRvEwA8Wzq6i', '233027', '${[decrypt] BM/Sk9ifIw/U3W7+wt+ZN45oFLQSuPNY8SXs9LG3MbvbD5+J2pdPkbpGJzcL4GTmZ+Gti1Rnqviolxc8LZluklWPBwKUP8jsOGtthw3fxNDwtwxpNuj8xplBo5n1uvK7ItzGZ8aAB+vb0drf5XcN3vk=}']", gas);
  }

  function makeOraclizeQuery(uint timeOrDelay, string datasource, string query, uint gas) private returns(bytes32) {
    oraclizeFees += oraclize_getPrice(datasource, gas);
    return oraclize_query(timeOrDelay, datasource, query, gas);
  }

  // Determine the outcome manually,
  // immediately
  function determineWinner(uint gas, uint gasPrice) payable public onlyOwnerLevel canDetermineWinner {
    ORACLIZE_GAS = gas;
    oraclize_setCustomGasPrice(gasPrice);

    callOracle(0, ORACLIZE_GAS);
  }

  // Callback from Oraclize
  function __callback(bytes32 queryId, string result, bytes proof) public canDetermineWinner {
    require(msg.sender == oraclize_cbAddress());

    // The Oracle must always return
    // an integer (either 0 or 1, or if not then)
    // it should be 2
    if (keccak256(result) != keccak256("0") && keccak256(result) != keccak256("1")) {
      // Reschedule winner determination,
      // unless we're past the point of
      // cancelation. If nextScheduledQuery is
      // not the current query, it means that
      // there's a scheduled future query, so
      // we can wait for that instead of scheduling
      // another one now (otherwise this would cause
      // dupe queries).

      if (now >= CANCELATION_DATE) {
        cancel();
      }
      else if (nextScheduledQuery == queryId) {
        callOracle(PING_ORACLE_INTERVAL, ORACLIZE_GAS);
      }
    }
    else {
      setWinner(parseInt(result));
    }
  }

  function setWinner(uint winner) private {
    completed = true;
    canceled = false;
    winningOption = winner;
    winnerDeterminedDate = now;

    payOwners();
  }

  // Returns the total amounts betted
  // for the sender
  function getUserBet(address addr) public constant returns(uint[]) {
    uint[] memory bets = new uint[](2);
    bets[betterInfo[addr].betOption] = betterInfo[addr].betAmount;

    return bets;
  }

  // Returns whether a user has withdrawn
  // money or not.
  function userHasWithdrawn(address addr) public constant returns(bool) {
    return betterInfo[addr].withdrawn;
  }

  // Returns whether winning collections are
  // now available, or not.
  function collectionsAvailable() public constant returns(bool) {
    return (completed && winningOption != 2 && now >= (winnerDeterminedDate + 600)); // At least 10 mins has to pass between determining winner and enabling payout, so that we have time to revert the bet in case we detect suspicious betting activty (eg. a hacker bets a lot to steal the entire losing pot, and hacks the oracle)
  }
  
  // Returns true if we can bet (in betting window)
  function canBet() public constant returns(bool) {
    return (now >= BETTING_OPENS && now < BETTING_CLOSES && !canceled && !completed);
  }

  // Function for user to bet on launch
  // outcome
  function bet(uint option) public payable {
    require(canBet() == true);
    require(msg.value >= MIN_BET);
    require(betterInfo[msg.sender].betAmount == 0 || betterInfo[msg.sender].betOption == option);

    // Add better to better list if they
    // aren't already in it
    if (betterInfo[msg.sender].betAmount == 0) {
      betterInfo[msg.sender].betOption = option;
      numberOfBets[option]++;

      betters.push(msg.sender);
    }

    // Perform bet
    betterInfo[msg.sender].betAmount += msg.value;
    totalBetAmount += msg.value;
    totalAmountsBet[option] += msg.value;

    BetMade(); // Trigger event
  }

  // Empty remainder of the value in the
  // contract to the owners.
  function emptyRemainingsToOwners() private canEmptyRemainings {
    OWNERS.transfer(this.balance);
    returnedToOwners = true;
  }

  function returnToOwners() public onlyOwnerLevel canEmptyRemainings {
    emptyRemainingsToOwners();
  }

  // Performs payout to owners
  function payOwners() private canPayOwners {
    if (COMMISSION == 0) {
      ownersPayed = true;
      ownerPayout = 0;
      if (numberOfBets[winningOption] > 0) {
        collectionFees = ((oraclizeFees != 0) ? (oraclizeFees / numberOfBets[winningOption] + 1) : 0); // We add 1 wei to act as a ceil for the integer div -- important because the contract cannot afford to lose that spare change, as it will gaurantee that the final payout collection will fail.
      }

      return;
    }

    // Calculate total pool of ETH
    // betted for the two outcomes.    
    uint losingChunk = totalAmountsBet[1 - winningOption];
    ownerPayout = (losingChunk - oraclizeFees) / COMMISSION; // Payout to the owner; commission of losing pot, minus the same % of the fees
    if (numberOfBets[winningOption] > 0) {
      collectionFees = ((oraclizeFees != 0) ? ((oraclizeFees - oraclizeFees / COMMISSION) / numberOfBets[winningOption] + 1) : 0); // The fees to be distributed to the collectors, after owner payout. See reasoning above for adding the 1 wei.
    }

    // Equal weight payout to the owners
    OWNERS.transfer(ownerPayout);
    ownersPayed = true;
  }

  function cancelBet() payable public onlyOwnerLevel {
    cancel();
  }

  // Cancel bet and relase all the bets back to
  // the betters if, for any reason, payouts cannot be
  // completed. (For example Oracle fails.) Triggered by owners.
  function cancel() private {
    canceled = true;
    completed = false;
  }

  // Fallback function in case someone sends
  // ether to the contract so it doesn't get
  // lost. Could be used by us owners as buffer
  // value in case payouts fail.
  function() payable public {
  }

  // Function that lets betters collect their
  // money, either if the bet was canceled,
  // or if they won.
  function collect() public collectionsEnabled {
    address better = msg.sender;

    require(betterInfo[better].betAmount > 0);
    require(!betterInfo[better].withdrawn);
    require(canceled != completed);
    require(canceled || (completed && betterInfo[better].betOption == winningOption));
    require(now >= (winnerDeterminedDate + 600));

    uint payout = 0;
    if (!canceled) {
      // On top of their original bet,
      // add in profit, which is a weighted
      // proportion of the losing pot, relative
      // to their contribution to the winning pot,
      // minus owner commission.
      uint losingChunk = totalAmountsBet[1 - winningOption];
      payout = betterInfo[better].betAmount + (betterInfo[better].betAmount * (losingChunk - ownerPayout) / totalAmountsBet[winningOption]) - collectionFees;
    }
    else {
      payout = betterInfo[better].betAmount;
    }

    if (payout > 0) {
      better.transfer(payout);
      betterInfo[better].withdrawn = true;

      numCollected++;
    }
  }

}
// </ORACLIZE_API>
function determineWinner(uint gas, uint gasPrice) payable public onlyOwnerLevel canDetermineWinner {
  ORACLIZE_GAS = gas;
  oraclize_setCustomGasPrice(gasPrice);

  callOracle(0, ORACLIZE_GAS);
}

// Determine the outcome manually,
// immediately