contract Raffle is Ownable, Priceable, SafeWinner, Cascading {
  using SafeMath for uint256;
  using OraclizeLib for OraclizeLib.OraclizeData;

  enum RaffleState { Active, InActive, PendingInActive }
  enum RandomSource { RandomDS, Qrng }

  struct Jackpot {
    uint absoluteTotal;
    uint feeTotal;
    uint cascadeTotal;
    uint winnerTotal;
  }

  struct TicketHolder {
    address purchaser;
    uint16 count;
    uint80 runningTotal;
  }
  
  // public
  RaffleState public raffleState;
  RandomSource public randomSource;
  uint public ticketPrice;
  uint public gameId;
  uint public fee;
  

  // internal
  TicketHolder[] internal ticketHolders;
  uint internal randomBytes;
  uint internal randomQueried;
  uint internal callbackGas;
  RewardDistributable internal rewardDistributor;

  // oraclize
  OraclizeLib.OraclizeData oraclizeData;

  // events
  event TicketPurchased(address indexed ticketPurchaser, uint indexed id, uint numTickets, uint totalCost, uint block);
  event WinnerSelected(address indexed winner, uint indexed id, uint winnings, uint block);
  event RandomProofFailed(bytes32 queryId, uint indexed id, uint block);

  function Raffle(uint _ticketPrice, address _rewardDistributor) public {
    ticketPrice = _ticketPrice;
    raffleState = RaffleState.Active;
    callbackGas = 200000;
    randomBytes = 8;
    fee = 5 finney;
    rewardDistributor = RewardDistributable(_rewardDistributor);
    oraclizeData.initializeOraclize();
    randomSource = RandomSource.Qrng;
    resetRaffle();
  }

  /// @dev Returns whether the game is active.
  function isActive() public constant returns (bool) {
    return raffleState == RaffleState.Active || raffleState == RaffleState.PendingInActive;
  }
  
  /// @dev Fallback function to purchase a single ticket.
  function () public payable {
  }
   
  /// @dev Gets the projected jackpot.
  /// @return The projected jackpot amount.
  function getProjectedJackpot() public constant returns (uint) {
    uint jackpot = getAbsoluteProjectedJackpot();
    Jackpot memory totals = getJackpotTotals(jackpot);
    return totals.winnerTotal;
  }

  /// @dev Gets the actual jackpot
  /// @return The actual jackpot amount.
  function getJackpot() public constant returns (uint) {
    uint jackpot = getAbsoluteJackpot();
    Jackpot memory totals = getJackpotTotals(jackpot);
    return totals.winnerTotal;
  }

  /// @dev Gets the ticket holder count
  /// @return The total ticket holder count
  function getTicketHolderCount() public constant returns (uint) {
    return getTotalTickets();
  }

  /// @dev Updates the ticket price.
  function updateTicketPrice(uint updatedPrice) public onlyOwner {
    require(raffleState == RaffleState.InActive);
    require(updatedPrice > 0);
    ticketPrice = updatedPrice;
  }

  /// @dev Updates the ticket price.
  function updateFee(uint updatedFee) public onlyOwner {
    require(updatedFee > 0);
    fee = updatedFee;
  }

  /// @dev Deactivates the raffle after the next game.
  function deactivate() public onlyOwner {
    require(raffleState == RaffleState.Active);
    raffleState = ticketHolders.length == 0 ? RaffleState.InActive : RaffleState.PendingInActive;
  }

  /// @dev Activates the raffle, if inactivated.
  function activate() public onlyOwner {
    require(raffleState == RaffleState.InActive);
    raffleState = RaffleState.Active;
  }

  /// The oraclize callback function.
  function __callback(bytes32 queryId, string result, bytes proof) public {
    require(msg.sender == oraclizeData.oraclize_cbAddress());
    
    // We only expect this for this callback
    if (oraclizeData.oraclize_randomDS_proofVerify__returnCode(queryId, result, proof) != 0) {
      RandomProofFailed(queryId, gameId, now);
      randomQueried = 0;
      return;
    }

    __callback(queryId, result);
  }

  /// The oraclize callback function.
  function __callback(bytes32 queryId, string result) public {
    require(msg.sender == oraclizeData.oraclize_cbAddress());
    
    // Guard against the case where oraclize is triggered, or calls back multiple times.
    if (!shouldChooseWinner()) {
      return;
    }

    uint maxRange = 2**(8*randomBytes); 
    uint randomNumber = uint(keccak256(result)) % maxRange; 
    winnerSelected(randomNumber);
  }

  /// @dev An administrative function to allow in case the proof fails or 
  /// a random winner needs to be chosen again.
  function forceChooseRandomWinner() public onlyOwner {
    require(raffleState != RaffleState.InActive);
    executeRandomQuery();
  }

  /// @dev Forces a refund for all participants and deactivates the contract
  /// This offers a full refund, so it will be up to the owner to ensure a full balance.
  function forceRefund() public onlyOwner {
    raffleState = RaffleState.PendingInActive;

    uint total = getTotalTickets() * ticketPrice;
    require(this.balance > total);

    for (uint i = 0; i < ticketHolders.length; i++) {
      TicketHolder storage holder = ticketHolders[i];
      holder.purchaser.transfer(uint256(holder.count).mul(ticketPrice));
    }

    resetRaffle();
  }

  /// @dev Destroys the current contract and moves all ETH back to  
  function updateRewardDistributor(address newRewardDistributor) public onlyOwner {
    rewardDistributor = RewardDistributable(newRewardDistributor);
  }

  /// @dev Destroys the current contract and moves all ETH back to
  /// owner. Only can occur after state has been set to inactive.
  function destroy() public onlyOwner {
    require(raffleState == RaffleState.InActive);
    selfdestruct(owner);
  }

  /// Gets the projected jackpot prior to any fees
  /// @return The projected jackpot prior to any fees
  function getAbsoluteProjectedJackpot() internal constant returns (uint);

  /// Gets the actual jackpot prior to any fees
  /// @return The actual jackpot amount prior to any fees.
  function getAbsoluteJackpot() internal constant returns (uint);
  
  /// An abstract function which determines whether a it is appropriate to choose a winner.
  /// @return True if it is appropriate to choose the winner, false otherwise.
  function shouldChooseWinner() internal returns (bool);

  function executeRandomQuery() internal {
    if (randomSource == RandomSource.RandomDS) {
      oraclizeData.oraclize_newRandomDSQuery(0, randomBytes, callbackGas);
    }
    else {
      oraclizeData.oraclize_query("URL","json(https://qrng.anu.edu.au/API/jsonI.php?length=1&type=hex16&size=32).data[0]", callbackGas);
    }
  }

  /// Chooses the winner at random.
  function chooseWinner() internal {
    // We build in a buffer of 20 blocks.  Approx 1 block per 15 secs ~ 5 mins
    // the last time random was queried, we'll execute again.
    if (randomQueried < (block.number.sub(20))) {
      executeRandomQuery();
      randomQueried = block.number;
    }
  }

  /// Internal function for when a winner is chosen.
  function winnerSelected(uint randomNumber) internal {
    TicketHolder memory winner = getWinningTicketHolder(randomNumber);
    uint jackpot = getAbsoluteJackpot();
    Jackpot memory jackpotTotals = getJackpotTotals(jackpot);

    WinnerSelected(winner.purchaser, gameId, jackpotTotals.winnerTotal, now);    
    transferJackpot(winner.purchaser, jackpotTotals.winnerTotal);
    transferCascades(jackpotTotals.absoluteTotal);
    resetRaffle();
  }

  function getWinningTicketHolder(uint randomNumber) internal view returns(TicketHolder) {
    assert(ticketHolders.length > 0);
    uint totalTickets = getTotalTickets();
    uint winner = (randomNumber % totalTickets) + 1;

    uint min = 0;
    uint max = ticketHolders.length-1;
    while (max > min) {
        uint mid = (max + min + 1) / 2;
        if (ticketHolders[mid].runningTotal >= winner &&
         (ticketHolders[mid].runningTotal-ticketHolders[mid].count) < winner) {
           return ticketHolders[mid];
        }

        if (ticketHolders[mid].runningTotal <= winner) {
            min = mid;
        } else {
            max = mid-1;
        }
    }

    return ticketHolders[min];
  }

  /// Transfers the jackpot to the winner triggering the event
  function transferJackpot(address winner, uint jackpot) internal returns(uint) {
    // We explicitly do not use transfer here because if the 
    // the call fails, the oraclize contract will not retry.
    bool sendSuccessful = winner.send(jackpot);
    if (!sendSuccessful) {
      addPendingWinner(winner, jackpot);
    }

    return jackpot;
  }

  /// Resets the raffle game state.
  function resetRaffle() internal {
    if (raffleState == RaffleState.PendingInActive) {
      raffleState = RaffleState.InActive;
    }
    ticketHolders.length = 0;
    gameId = block.number;
    randomQueried = 0;
  }

  /// Gets the jackpot after fees
  function getJackpotTotals(uint jackpot) internal constant returns(Jackpot) {
    if (jackpot < fee) {
      return Jackpot(0, 0, 0, 0);
    }

    uint cascadeTotal = getCascadeTotal(totalCascadingPercentage, jackpot);
    return Jackpot(jackpot, fee, cascadeTotal, jackpot.sub(fee).sub(cascadeTotal));
  }

  function updateRandomSource(uint newRandomSource) public onlyOwner {
    if (newRandomSource == 1) {
      randomSource = RandomSource.RandomDS;
    } else {
      randomSource = RandomSource.Qrng;
    }

    setProof();
  }


  function setProof() internal {
      if (randomSource == RandomSource.RandomDS) {
        // proofType_Ledger = 0x30;
        oraclizeData.oraclize_setProof(0x30);
      }
      else {
        oraclizeData.oraclize_setProof(0x00);
      }
  }

  function getTotalTickets() internal view returns(uint) {
    return ticketHolders.length == 0 ? 0 : ticketHolders[ticketHolders.length-1].runningTotal;
  }

  function updateOraclizeGas(uint newCallbackGas, uint customGasPrice) public onlyOwner {
    callbackGas = newCallbackGas;
    updateCustomGasPrice(customGasPrice);
  }

  function updateCustomGasPrice(uint customGasPrice) internal {
    oraclizeData.oraclize_setCustomGasPrice(customGasPrice);
  }
}
function shouldChooseWinner() internal returns (bool);

/// An abstract function which determines whether a it is appropriate to choose a winner.
/// @return True if it is appropriate to choose the winner, false otherwise.