contract Dice is usingOraclize {

    uint constant edge = 190; //edge percentage (10000 = 100%)
    uint constant maxWin = 100; //max win (before edge is taken) as percentage of bankroll (10000 = 100%)
    uint constant minBet = 10 finney;

    uint safeGas = 2300;
    uint constant ORACLIZE_GAS_LIMIT = 175000;
    uint constant INVALID_BET_MARKER = 99999;

    struct Investor {
        address investorAddress;
        uint amountInvested;
        bool votedForEmergencyWithdrawal;
    }

    struct Bet {
        address playerAddress;
        uint amountBet;
        uint numberRolled;
        uint pwin;
    }

    struct WithdrawalProposal {
        address toAddress;
        uint atTime;
    }

    address public owner;

    mapping (bytes32 => Bet) public bets;
    bytes32[] public betsKeys;

    uint public investorsProfit = 0;
    uint public investorsLosses = 0;
    uint public amountBets = 0;
    uint public amountWon = 0;
    bool public isStopped = false;

    event LOG_NewBet(address playerAddress, uint amount, uint pwin, bytes32 id);
    event LOG_BetWon(address playerAddress, uint numberRolled, uint amountWon, bytes32 id);
    event LOG_BetLost(address playerAddress, uint numberRolled, uint amountLost, bytes32 id);
    event LOG_FailedSend(address receiver, uint amount);
    event LOG_ZeroSend();
    event LOG_OwnerAddressChanged(address oldowner, address newowner);
    event LOG_GasLimitChanged(uint oldGasLimit, uint newGasLimit);
    event LOG_ValueIsTooBig();
    event LOG_SuccessfulSend(address addr, uint amount);
    event LOG_IncreaseInvestment(uint amount);
    event LOG_Payout(address addr, uint amount);

    event Log(uint text);

    function Dice() {
        // OAR = OraclizeAddrResolverI(0x6f485C8BF6fc43eA212E93BBF8ce046C7f1cb475);
        owner = msg.sender;
        oraclize_setNetwork(networkID_auto);
        // oraclize_setProof(proofType_TLSNotary | proofStorage_IPFS);
    }

    //SECTION I: MODIFIERS AND HELPER FUNCTIONS

    //MODIFIERS

    modifier onlyOwner {
        if (owner != msg.sender) throw;
        _;
    }

    modifier onlyIfNotStopped {
        if (isStopped) throw;
        _;
    }

    modifier onlyOraclize {
        if (msg.sender != oraclize_cbAddress()) throw;
        _;
    }

    modifier onlyIfBetExist(bytes32 myid) {
        if(bets[myid].playerAddress == address(0x0)) throw;
        _;
    }

    modifier onlyIfBetSizeIsStillCorrect(bytes32 myid, uint pwin) {
        if ((((bets[myid].amountBet * ((10000 - edge) - pwin)) / pwin ) <= getBankroll())  && (bets[myid].amountBet >= minBet)) {
             _;
        }
        else {
            bets[myid].numberRolled = INVALID_BET_MARKER;
            safeSend(bets[myid].playerAddress, bets[myid].amountBet);
            return;
        }
    }

    modifier onlyIfValidRoll(bytes32 myid, string result) {
        uint numberRolled = parseInt(result);
        if ((numberRolled < 1 || numberRolled > 10000) && bets[myid].numberRolled == 0) {
            bets[myid].numberRolled = INVALID_BET_MARKER;
            safeSend(bets[myid].playerAddress, bets[myid].amountBet);
            return;
        }
        _;
    }

    modifier onlyWinningBets(uint numberRolled, uint pwin) {
        if (numberRolled - 1 < pwin) {
            _;
        }
    }

    modifier onlyLosingBets(uint numberRolled, uint pwin) {
        if (numberRolled - 1 >= pwin) {
            _;
        }
    }


    modifier onlyIfValidGas(uint newGasLimit) {
        if (ORACLIZE_GAS_LIMIT + newGasLimit < ORACLIZE_GAS_LIMIT) throw;
        if (newGasLimit < 25000) throw;
        _;
    }

    modifier onlyIfNotProcessed(bytes32 myid) {
        if (bets[myid].numberRolled > 0) throw;
        _;
    }

    //CONSTANT HELPER FUNCTIONS

    function getBankroll() constant returns(uint) {
      return this.balance;
    }

    function getBet(uint id)
        constant
        returns(address, uint, uint) {

        if (id < betsKeys.length) {
            bytes32 betKey = betsKeys[id];
            return (bets[betKey].playerAddress, bets[betKey].amountBet, bets[betKey].numberRolled);
        }
    }

    function numBets()
        constant
        returns(uint) {

        return betsKeys.length;
    }

    function totalWon() constant returns (uint) {
        return amountWon;
    }

    // PRIVATE HELPERS FUNCTION

    function safeSend(address addr, uint value)
        private {

        if (value == 0) {
            LOG_ZeroSend();
            return;
        }

        if (this.balance < value) {
            LOG_ValueIsTooBig();
            return;
        }

        if (!(addr.call.gas(safeGas).value(value)())) {
            LOG_FailedSend(addr, value);
            if (addr != owner) {
                //Forward to house address all change
                if (!(owner.call.gas(safeGas).value(value)())) LOG_FailedSend(owner, value);
            }
        }

        LOG_SuccessfulSend(addr,value);
    }

    // SECTION II: BET & BET PROCESSING

    function() payable {
        bet(5000);
    }

    function bet(uint pwin) payable public onlyIfNotStopped {
        uint oraclizeFee = OraclizeI(OAR.getAddress()).getPrice("URL", ORACLIZE_GAS_LIMIT + safeGas);
        if (oraclizeFee >= msg.value) throw;
        uint betValue = msg.value - oraclizeFee;

     if ((((betValue * (10000 - edge)) / pwin) <= (getBankroll()) / 30) && (betValue >= minBet)) {        
           bytes32 myid =
                 oraclize_query("nested", 
        "[URL] ['json(https://api.random.org/json-rpc/1/invoke).result.random.data.0', '\\n{\"jsonrpc\":\"2.0\",\"method\":\"generateIntegers\",\"params\":{\"apiKey\":\"${[decrypt] BHvgt4Bhec2IJyErKzdCVu3FSzo6mKW10ZDb38Ruyj6bOCyzY5738oLb8a5fGGfMhjcVjabuo9YOJ+f83/lXZELZQSmMAwbhg+thjUJZMoBLsFmAnD17UjEPPr1ffUNfAn7AI74sB3AwVh3296JCtcEVWn66}\",\"n\":1,\"min\":1,\"max\":10000,\"replacement\":true${[identity] \"}\"},\"id\":1${[identity] \"}\"}']");    
                
            LOG_NewBet(msg.sender, betValue, pwin, myid);
            bets[myid] = Bet(msg.sender, betValue, 0, pwin);
            betsKeys.push(myid);
            amountBets += 1;
            
        }
        else {
            throw;
        }    
        
        
    }

    function __callback(bytes32 myid, string result, bytes proof)
        onlyOraclize
        onlyIfBetExist(myid)
        onlyIfNotProcessed(myid)
        onlyIfValidRoll(myid, result)
        onlyIfBetSizeIsStillCorrect(myid, bets[myid].pwin)  {

        uint numberRolled = parseInt(result);
        bets[myid].numberRolled = numberRolled;
        isWinningBet(bets[myid], numberRolled, myid);
        isLosingBet(bets[myid], numberRolled, myid);
    }

    function isWinningBet(Bet thisBet, uint numberRolled, bytes32 myid) private onlyWinningBets(numberRolled, thisBet.pwin) {
        uint winAmount = (thisBet.amountBet * (10000 - edge)) / thisBet.pwin;
        LOG_BetWon(thisBet.playerAddress, numberRolled, winAmount, myid );
        safeSend(thisBet.playerAddress, winAmount);

        //Check for overflow and underflow
        if ((investorsLosses + winAmount < investorsLosses) ||
            (investorsLosses + winAmount < thisBet.amountBet)) {
                throw;
            }

        amountWon += winAmount;
        investorsLosses += winAmount - thisBet.amountBet;
    }

    function isLosingBet(Bet thisBet, uint numberRolled, bytes32 myid) private onlyLosingBets(numberRolled, thisBet.pwin) {
        LOG_BetLost(thisBet.playerAddress, numberRolled, thisBet.amountBet, myid);
        safeSend(thisBet.playerAddress, 1);

        //Check for overflow and underflow
        if ((investorsProfit + thisBet.amountBet < investorsProfit) ||
            (investorsProfit + thisBet.amountBet < thisBet.amountBet) ||
            (thisBet.amountBet == 1)) {
                throw;
            }

        investorsProfit += (thisBet.amountBet - 1);
    }

    //SECTION III: INVEST & DIVEST

    function invest() payable public 
    onlyOwner
    onlyIfNotStopped {
        LOG_IncreaseInvestment(msg.value);
    }

    function divest(uint value) public onlyOwner {
        safeSend(owner, value);
        LOG_Payout(owner, value);
    }

    function getProfits() public onlyOwner {
     uint value = investorsProfit - investorsLosses;
     safeSend(owner, value);
     investorsProfit = 0;
     investorsLosses = 0;
    }



    //SECTION IV: CONTRACT MANAGEMENT

    function changeOwnerAddress(address newOwner) onlyOwner {
        if (newOwner == address(0x0)) throw;
        owner = newOwner;
        LOG_OwnerAddressChanged(owner, newOwner);
    }

    function changeGasLimitOfSafeSend(uint newGasLimit) onlyOwner onlyIfValidGas(newGasLimit) {
        safeGas = newGasLimit;
        LOG_GasLimitChanged(safeGas, newGasLimit);
    }

    function kill() public onlyOwner {
        isStopped = true;
        selfdestruct(owner);
    }
}
// </ORACLIZE_API>
function getBankroll() constant returns(uint) {
  return this.balance;
}

//CONSTANT HELPER FUNCTIONS