contract Etherwow is usingOraclize, SafeMath {
    
     using strings for *;

    /*
     * checks user profit, bet size and user number is within range
    */
    modifier betIsValid(uint _betSize, uint _userNumber) {
        require(((((_betSize * (100-(safeSub(_userNumber,1)))) / (safeSub(_userNumber,1))+_betSize))*houseEdge/houseEdgeDivisor)-_betSize <= maxProfit && _betSize >= minBet && _userNumber >= minNumber && _userNumber <= maxNumber); 
        _;
    }

    /*
     * checks game is currently active
    */
    modifier gameIsActive {
        require(gamePaused == false);
        _;
    }     

    /*
     * checks only Oraclize address is calling
    */
    modifier onlyOraclize {
        require(msg.sender == oraclize_cbAddress());
        _;
    }

    /*
     * checks only owner address is calling
    */
    modifier onlyOwner {
         require(msg.sender == owner);
         _;
    }

    /*
     * checks only owner address is calling
    */
    modifier onlyOwnerOrOperator {
         require((msg.sender == owner || msg.sender == operator.addr) && msg.sender != 0x0);
         _;
    }
    
    /*
     * token vars
    */
    token public jackpotToken;
    uint public jackpotTokenEthRate;
    uint public jackpotTokenWinRewardRate;
    uint public jackpotTokenLoseRewardRate;
    uint constant rewardRateDivisor = 1000;
    uint jackpotTokenReward;

    /*
     * game vars
    */ 
    uint constant public maxProfitDivisor = 1000000;
    uint constant public houseEdgeDivisor = 1000;    
    uint constant public maxNumber = 99; 
    uint constant public minNumber = 2;
    
    bool public gamePaused;
    uint32 public gasForOraclize;
    address public owner;
    uint public contractBalance;
    uint public houseEdge;     
    uint public maxProfit;   
    uint public maxProfitAsPercentOfHouse;                    
    uint public minBet; 
    uint public maxPendingPayouts;
    uint public randomQueryID;
    uint public randomGenerateMethod;
    string private randomApiKey;
    
    /* init discontinued contract data */     
    uint public totalBets = 0;
    uint public totalWeiWon = 0;
    uint public totalWeiWagered = 0; 

    /* access control */
    Operator public operator;

    struct Operator{
        address addr;
        bool refundPermission;  /* true - have permission, false - don't have permission */
        uint refundAmtApprove;  /* when refundAmtApprove not enough, use ownerModOperator() to set again */
    }

 

    /*
     * user vars
    */
    mapping (bytes32 => address) public userAddress;
    mapping (bytes32 => address) public userTempAddress;
    mapping (bytes32 => bytes32) public userBetId;
    mapping (bytes32 => uint) public  userBetValue;
    mapping (bytes32 => uint) public  userTempBetValue;               
    mapping (bytes32 => uint) public  userDieResult;
    mapping (bytes32 => uint) public  userNumber;
    mapping (address => uint) public  userPendingWithdrawals;      
    mapping (bytes32 => uint) public  userProfit;
    mapping (bytes32 => uint) public  userTempReward;           
    /* Status: 0=lose, 1=win, 2=win + failed send, 3=refund, 4=refund + failed send, 5=pending, 6=manual refund */
    mapping (bytes32 => uint8) public  betStatus;           

    /*
     * events
    */
    /* log bets + output to web3 for precise 'payout on win' field in UI */
    event LogBet(bytes32 indexed BetID, address indexed UserAddress, uint indexed RewardValue, uint ProfitValue, uint BetValue, uint UserNumber, uint RandomQueryID);      
    /* output to web3 UI on bet result*/
    event LogResult(bytes32 indexed BetID, address indexed UserAddress, uint UserNumber, uint DiceResult, uint Value, uint tokenReward, uint8 Status, uint RandomGenerateMethod, bytes Proof, uint indexed SerialNumberOfResult);   
    /* log manual refunds */
    event LogRefund(bytes32 indexed BetID, address indexed UserAddress, uint indexed RefundValue);
    /* log owner transfers */
    event LogOwnerTransfer(address indexed SentToAddress, uint indexed AmountTransferred);


    /*
     * init
    */
    function Etherwow() {

        owner = msg.sender;
        oraclize_setNetwork(networkID_auto);        
        /* init 990 = 99% (1% houseEdge)*/
        ownerSetHouseEdge(990);
        /* init 10,000 = 1%  */
        ownerSetMaxProfitAsPercentOfHouse(10000);
        /* init min bet (0.1 ether) */
        ownerSetMinBet(100000000000000000);        
        /* init gas for oraclize */        
        gasForOraclize = 300000;  
        /* init gas price for callback (default 20 gwei)*/
        oraclize_setCustomGasPrice(20000000000 wei);
        /* defult random num generation method 0-random.org */
        randomGenerateMethod = 0; 

    }

    /*
     * @dev generate a true random num, two methods are provide, to avoid single point failure
     *      randomGenerateMethod = 0 - random num generate from random.org
     *      randomGenerateMethod = 1 - random num generate from oraclize service
     * @return oraclize queryId
     */
    function generateRandomNum() internal returns(bytes32){
        /* random num solution from random.org */
        if (randomGenerateMethod == 0){
                randomQueryID += 1;
                string memory queryString1 = "[URL] ['json(https://api.random.org/json-rpc/1/invoke).result.random[\"serialNumber\",\"data\"]', '\\n{\"jsonrpc\":\"2.0\",\"method\":\"generateSignedIntegers\",\"params\":{\"apiKey\":";
                string memory queryString1_1 = ",\"n\":1,\"min\":1,\"max\":100,\"replacement\":true,\"base\":10${[identity] \"}\"},\"id\":";
                queryString1 = queryString1.toSlice().concat(randomApiKey.toSlice());
                queryString1 = queryString1.toSlice().concat(queryString1_1.toSlice());

                string memory queryString2 = uint2str(randomQueryID);
                string memory queryString3 = "${[identity] \"}\"}']";

                string memory queryString1_2 = queryString1.toSlice().concat(queryString2.toSlice());

                string memory queryString1_2_3 = queryString1_2.toSlice().concat(queryString3.toSlice()); 

                oraclize_setProof(proofType_TLSNotary | proofStorage_IPFS);
                return oraclize_query("nested", queryString1_2_3, gasForOraclize);               
        }

        // /* random num solution from oraclize(by default), prove fair paper: http://www.oraclize.it/papers/random_datasource-rev1.pdf */
        // if (randomGenerateMethod == 1){
        //         randomQueryID += 1;
        //         uint N = 8; /* number of random bytes we want the datasource to return */
        //         uint delay = 0; /* number of seconds to wait before the execution takes place */
        //         oraclize_setProof(proofType_Ledger);
        //         return oraclize_newRandomDSQuery(delay, N, gasForOraclize); /* this function internally generates the correct oraclize_query and returns its queryId */
        // }
        

    }

    /*
     * @dev validate roll dice request, and log the bet info
     * @param number player choosen, from [2,99]
     */
    function userRollDice(uint rollUnder) public 
        payable
        gameIsActive
        betIsValid(msg.value, rollUnder)
    {       

        bytes32 rngId = generateRandomNum(); 
       
        /* map bet id to this oraclize query */
        userBetId[rngId] = rngId;
        /* map user lucky number to this oraclize query */
        userNumber[rngId] = rollUnder;
        /* map value of wager to this oraclize query */
        userBetValue[rngId] = msg.value;
        /* map user address to this oraclize query */
        userAddress[rngId] = msg.sender;
        /* safely map user profit to this oraclize query */                     
        userProfit[rngId] = ((((msg.value * (100-(safeSub(rollUnder,1)))) / (safeSub(rollUnder,1))+msg.value))*houseEdge/houseEdgeDivisor)-msg.value;        
        /* safely increase maxPendingPayouts liability - calc all pending payouts under assumption they win */
        maxPendingPayouts = safeAdd(maxPendingPayouts, userProfit[rngId]);
        /* check contract can payout on win */
        require(maxPendingPayouts < contractBalance);
        /* bet status = 5-pending */
        betStatus[rngId] = 5;
        /* provides accurate numbers for web3 and allows for manual refunds in case of no oraclize __callback */
        emit LogBet(userBetId[rngId], userAddress[rngId], safeAdd(userBetValue[rngId], userProfit[rngId]), userProfit[rngId], userBetValue[rngId], userNumber[rngId], randomQueryID);          

    }   
             

    /*
     * @dev oraclize callback, only oraclize can call, payout should in active status
     * @param queryId
     * @param query result
     * @param query proof
     */
    function __callback(bytes32 myid, string result, bytes proof) public   
        onlyOraclize
    {  
        /* user address mapped to query id does not exist */
        require(userAddress[myid]!=0x0);

        /* random num solution from random.org(by default) */
        if (randomGenerateMethod == 0){
                /* keep oraclize honest by retrieving the serialNumber from random.org result */
                var sl_result = result.toSlice();
                sl_result.beyond("[".toSlice()).until("]".toSlice());
                uint serialNumberOfResult = parseInt(sl_result.split(', '.toSlice()).toString());          

                /* map random result to user */
                userDieResult[myid] = parseInt(sl_result.beyond("[".toSlice()).until("]".toSlice()).toString());                 
        } 

        // /* random num solution from oraclize */        
        // if (randomGenerateMethod == 1){
        //         uint maxRange = 100; this is the highest uint we want to get. It should never be greater than 2^(8*N), where N is the number of random bytes we had asked the datasource to return
        //         userDieResult[myid] = uint(sha3(result)) % maxRange + 1; /* this is an efficient way to get the uint out in the [0, maxRange] range */
        // }
      
        /* get the userAddress for this query id */
        userTempAddress[myid] = userAddress[myid];
        /* delete userAddress for this query id */
        delete userAddress[myid];

        /* map the userProfit for this query id */
        userTempReward[myid] = userProfit[myid];
        /* set  userProfit for this query id to 0 */
        userProfit[myid] = 0; 

        /* safely reduce maxPendingPayouts liability */
        maxPendingPayouts = safeSub(maxPendingPayouts, userTempReward[myid]);         

        /* map the userBetValue for this query id */
        userTempBetValue[myid] = userBetValue[myid];
        /* set  userBetValue for this query id to 0 */
        userBetValue[myid] = 0; 

        /* total number of bets */
        totalBets += 1;

        /* total wagered */
        totalWeiWagered += userTempBetValue[myid];                                                           

        /*
        * refund
        * if result is 0 result is empty or no proof refund original bet value
        * if refund fails save refund value to userPendingWithdrawals
        */
        if(userDieResult[myid] == 0 || bytes(result).length == 0 || bytes(proof).length == 0){ 
             /* Status: 0=lose, 1=win, 2=win + failed send, 3=refund, 4=refund + failed send*/ 
             /* 3 = refund */
             betStatus[myid] = 3;
            /*
            * send refund - external call to an untrusted contract
            * if send fails map refund value to userPendingWithdrawals[address]
            * for withdrawal later via userWithdrawPendingTransactions
            */
            if(!userTempAddress[myid].send(userTempBetValue[myid])){
                /* 4 = refund + failed send */
                betStatus[myid] = 4;              
                /* if send failed let user withdraw via userWithdrawPendingTransactions */
                userPendingWithdrawals[userTempAddress[myid]] = safeAdd(userPendingWithdrawals[userTempAddress[myid]], userTempBetValue[myid]);                        
            }
            jackpotTokenReward = 0;
            emit LogResult(userBetId[myid], userTempAddress[myid], userNumber[myid], userDieResult[myid], userTempBetValue[myid], jackpotTokenReward, betStatus[myid], randomGenerateMethod, proof, serialNumberOfResult);
            return;
        }

        /*
        * pay winner
        * update contract balance to calculate new max bet
        * send reward
        * if send of reward fails save value to userPendingWithdrawals        
        */
        if(userDieResult[myid] < userNumber[myid]){ 

            /* safely reduce contract balance by user profit */
            contractBalance = safeSub(contractBalance, userTempReward[myid]); 

            /* update total wei won */
            totalWeiWon = safeAdd(totalWeiWon, userTempReward[myid]);              

            /* safely calculate payout via profit plus original wager */
            userTempReward[myid] = safeAdd(userTempReward[myid], userTempBetValue[myid]); 

            /* 1 = win */
            betStatus[myid] = 1;                           

            /* update maximum profit */
            setMaxProfit();

            if (jackpotTokenWinRewardRate > 0) {
                /* calculate win token return */
                jackpotTokenReward = userTempBetValue[myid]*jackpotTokenEthRate*jackpotTokenWinRewardRate/rewardRateDivisor;
            
                /* transfer token to user */
                jackpotToken.transfer(userTempAddress[myid], jackpotTokenReward);                  
            }

            /*
            * send win - external call to an untrusted contract
            * if send fails map reward value to userPendingWithdrawals[address]
            * for withdrawal later via userWithdrawPendingTransactions
            */
            if(!userTempAddress[myid].send(userTempReward[myid])){
                /* 2 = win + failed send */
                betStatus[myid] = 2;                   
                /* if send failed let user withdraw via userWithdrawPendingTransactions */
                userPendingWithdrawals[userTempAddress[myid]] = safeAdd(userPendingWithdrawals[userTempAddress[myid]], userTempReward[myid]);                               
            }
            
            emit LogResult(userBetId[myid], userTempAddress[myid], userNumber[myid], userDieResult[myid], userTempBetValue[myid], jackpotTokenReward, betStatus[myid], randomGenerateMethod, proof, serialNumberOfResult);
            return;

        }

        /*
        * no win
        * send 1 wei to a losing bet
        * update contract balance to calculate new max bet
        */
        if(userDieResult[myid] >= userNumber[myid]){

            /* 0 = lose */
            betStatus[myid] = 0;                          

            /*  
            *  safe adjust contractBalance
            *  setMaxProfit
            *  send 1 wei to losing bet
            */
            contractBalance = safeAdd(contractBalance, (userTempBetValue[myid]-1));                                                                         

            /* update maximum profit */
            setMaxProfit(); 
 
            if (jackpotTokenLoseRewardRate > 0){
                /* calculate token reward */
                jackpotTokenReward = userTempBetValue[myid]*jackpotTokenEthRate*safeSub(100,userNumber[myid])*jackpotTokenLoseRewardRate/(rewardRateDivisor*100);

                /* transfer token to user */
                jackpotToken.transfer(userTempAddress[myid], jackpotTokenReward);                
            }

            /*
            * send 1 wei - external call to an untrusted contract                  
            */
            if(!userTempAddress[myid].send(1)){
                /* if send failed let user withdraw via userWithdrawPendingTransactions */                
               userPendingWithdrawals[userTempAddress[myid]] = safeAdd(userPendingWithdrawals[userTempAddress[myid]], 1);                                
            }                                   
            
            emit LogResult(userBetId[myid], userTempAddress[myid], userNumber[myid], userDieResult[myid], userTempBetValue[myid], jackpotTokenReward, betStatus[myid], randomGenerateMethod, proof, serialNumberOfResult);  
            return;

        }

    }
    
    /*
     * @dev in case of a failed refund or win send, user can withdraw later
     * @return true - withdraw success, false - withdraw failed
     */
    function userWithdrawPendingTransactions() public 
        gameIsActive
        returns (bool)
     {
        uint withdrawAmount = userPendingWithdrawals[msg.sender];
        userPendingWithdrawals[msg.sender] = 0;
        /* external call to untrusted contract */
        if (msg.sender.call.value(withdrawAmount)()) {
            return true;
        } else {
            /* if send failed revert userPendingWithdrawals[msg.sender] = 0; */
            /* user can try to withdraw again later */
            userPendingWithdrawals[msg.sender] = withdrawAmount;
            return false;
        }
    }

    /*
     * @dev in case of a failed refund or win send, user can check pending withdraw
     * @param address to check
     * @return pending withdraw amount
     */
    function userGetPendingTxByAddress(address addressToCheck) public constant returns (uint) {
        return userPendingWithdrawals[addressToCheck];
    }

    /*
     * @dev sets max profit
     */
    function setMaxProfit() internal {
        maxProfit = (contractBalance*maxProfitAsPercentOfHouse)/maxProfitDivisor;  
    }      

    /*
     * @dev fallback method
     */
    function ()
        payable
        onlyOwnerOrOperator
    {
        /* safely update contract balance */
        contractBalance = safeAdd(contractBalance, msg.value);        
        /* update the maximum profit */
        setMaxProfit();
    } 
     
     /*
     * @dev owner can set operator & permission
     * if want to revoke a permission, just set address to "0x0"
     * @param operator address
     * @param operator transfer permission
     * @param operator transfer approve amt
     * @param operator refund permission
     * @param operator refund approve amt
     */  
    function ownerModOperator(address newAddress, bool newRefundPermission, uint newRefundAmtApprove) public 
        onlyOwner
    {
        operator.addr = newAddress;
        operator.refundPermission = newRefundPermission;
        operator.refundAmtApprove = newRefundAmtApprove;            
    }

    /*
     * @dev onlyOwnerOrOperator set gas price for oraclize callback
     */
    function ownerSetCallbackGasPrice(uint newCallbackGasPrice) public 
        onlyOwnerOrOperator
    {
        oraclize_setCustomGasPrice(newCallbackGasPrice);
    }     

    /*
     * @dev onlyOwnerOrOperator set gas limit for oraclize query
     */
    function ownerSetOraclizeSafeGas(uint32 newSafeGasToOraclize) public 
        onlyOwnerOrOperator
    {
        gasForOraclize = newSafeGasToOraclize;
    }

    /*
     * @dev onlyOwnerOrOperator adjust contract balance variable (only used for max profit calc)
     */
    function ownerUpdateContractBalance(uint newContractBalanceInWei) public 
        onlyOwnerOrOperator
    {        
       contractBalance = newContractBalanceInWei;
    }    

    /*
     * @dev owner set houseEdge
     */    
    function ownerSetHouseEdge(uint newHouseEdge) public 
        onlyOwnerOrOperator
    {
        if(msg.sender == operator.addr && newHouseEdge > 990) throw;
        houseEdge = newHouseEdge;
    }

    /*
     * @dev onlyOwnerOrOperator set maxProfitAsPercentOfHouse
     */    
    function ownerSetMaxProfitAsPercentOfHouse(uint newMaxProfitAsPercent) public 
        onlyOwnerOrOperator
    {
        /* restrict each bet to a maximum profit of 5% contractBalance */
        require(newMaxProfitAsPercent <= 50000);

        maxProfitAsPercentOfHouse = newMaxProfitAsPercent;
        setMaxProfit();
    }

    /*
     * @dev onlyOwnerOrOperator set minBet
     */        
    function ownerSetMinBet(uint newMinimumBet) public 
        onlyOwnerOrOperator
    {
        minBet = newMinimumBet;
    }       

    /*
     * @dev owner transfer ether
     */  
    function ownerTransferEther(address sendTo, uint amount) public 
        onlyOwner
    {        
        /* safely update contract balance when sending out funds*/
        contractBalance = safeSub(contractBalance, amount);     
        /* update max profit */
        setMaxProfit();
        sendTo.transfer(amount);
        emit LogOwnerTransfer(sendTo, amount); 
    }

    /*
     * @dev manual refund
     * only onlyOwnerOrOperator address can do manual refund
     * used only if bet placed but not execute payout method after stock market close
     * filter LogBet by address and/or userBetId, do manual refund only when meet below conditions:
     * 1. bet status should be 5-pending;
     * 2. record should in logBet;
     * 3. record should not in logResult;
     * 4. record should not in logRefund;
     * if LogResult exists user should use the withdraw pattern userWithdrawPendingTransactions
     * if LogRefund exists means manual refund has been done before
     * @param betId
     * @param address sendTo
     * @param original user profit
     * @param original bet value
    */
    function ownerRefundUser(bytes32 originalUserBetId, address sendTo, uint originalUserProfit, uint originalUserBetValue) public 
        onlyOwnerOrOperator
    {        
        /* check operator permission */
        require(msg.sender == owner || (msg.sender == operator.addr && operator.refundPermission == true && safeToSubtract(operator.refundAmtApprove, originalUserBetValue)));
        /* status should be 5-pending */
        require(betStatus[originalUserBetId] == 5);
        /* safely reduce pendingPayouts by userProfit[rngId] */
        maxPendingPayouts = safeSub(maxPendingPayouts, originalUserProfit);
        /* send refund */
        sendTo.transfer(originalUserBetValue);
        /* decrease approve amt if it's triggered by operator(no need to use safesub here, since passed above require() statement) */
        if(msg.sender == operator.addr){
            operator.refundAmtApprove = operator.refundAmtApprove -  originalUserBetValue;
        }
        /* update betStatus = 6-manual refund */
        betStatus[originalUserBetId] = 6;
        /* log refunds */
        emit LogRefund(originalUserBetId, sendTo, originalUserBetValue);        
    }    

    /*
     * @dev onlyOwnerOrOperator set system emergency pause
     * @param true: pause, false: not pause
     */ 
    function ownerPauseGame(bool newStatus) public 
        onlyOwnerOrOperator
    {
        gamePaused = newStatus;
    }


    /*
     * @dev owner set new owner
     * @param new owner address
     */ 
    function ownerChangeOwner(address newOwner) public 
        onlyOwner
    {
        owner = newOwner;
    }

    /*
     * @dev onlyOwnerOrOperator set random.org api key
     * @param new api key
     */ 
    function ownerSetRandomApiKey(string newApiKey) public 
        onlyOwnerOrOperator
    {
        randomApiKey = newApiKey;
    } 

    /*
     * @dev onlyOwnerOrOperator can set randomGenerateMethod
     * @param 0-random num solution from random.org, 1-random num solution from oraclize
     */  
    function ownerSetRandomGenerateMethod(uint newRandomGenerateMethod) public 
        onlyOwnerOrOperator
    {
        randomGenerateMethod = newRandomGenerateMethod;
    } 

    /*
     * @dev onlyOwnerOrOperator can set jackpotToken
     * @param jackpotToken address
     * @param ether to token rate
     * @param win reward rate, defult 10(divisor 1000)
     * @param lose reward rate, defult 1000(divisor 1000)
     */  
    function ownerSetJackpotToken(address newTokenAddr, uint newTokenEthRate, uint newWinRewardRate, uint newLoseRewardRate) public onlyOwnerOrOperator{
        jackpotToken = token(newTokenAddr);
        jackpotTokenEthRate = newTokenEthRate;
        jackpotTokenWinRewardRate = newWinRewardRate;
        jackpotTokenLoseRewardRate = newLoseRewardRate;
    }

    /*
     * @dev transfer token
     * @param to address
     * @param token amount
     */  
    function ownerTransferJackpotToken(address toAddress, uint amount ) public onlyOwner{
        jackpotToken.transfer(toAddress, amount); 
    }

    /*
     * @dev owner selfdestruct contract ***BE CAREFUL! EMERGENCY ONLY / CONTRACT UPGRADE***
     */ 
    function ownerkill() public 
        onlyOwner
    {
        selfdestruct(owner);
    }    


}
/**
 * @title Etherwow
 * @dev user choose a num in [2,99] system generate a num in [1,100], if num user choosed  > num system generated, user win, otherwise user lose
 */
function ownerRefundUser(bytes32 originalUserBetId, address sendTo, uint originalUserProfit, uint originalUserBetValue) public 
    onlyOwnerOrOperator
{        
    /* check operator permission */
    require(msg.sender == owner || (msg.sender == operator.addr && operator.refundPermission == true && safeToSubtract(operator.refundAmtApprove, originalUserBetValue)));
    /* status should be 5-pending */
    require(betStatus[originalUserBetId] == 5);
    /* safely reduce pendingPayouts by userProfit[rngId] */
    maxPendingPayouts = safeSub(maxPendingPayouts, originalUserProfit);
    /* send refund */
    sendTo.transfer(originalUserBetValue);
    /* decrease approve amt if it's triggered by operator(no need to use safesub here, since passed above require() statement) */
    if(msg.sender == operator.addr){
        operator.refundAmtApprove = operator.refundAmtApprove -  originalUserBetValue;
    }
    /* update betStatus = 6-manual refund */
    betStatus[originalUserBetId] = 6;
    /* log refunds */
    emit LogRefund(originalUserBetId, sendTo, originalUserBetValue);        
}    

/*
 * @dev manual refund
 * only onlyOwnerOrOperator address can do manual refund
 * used only if bet placed but not execute payout method after stock market close
 * filter LogBet by address and/or userBetId, do manual refund only when meet below conditions:
 * 1. bet status should be 5-pending;
 * 2. record should in logBet;
 * 3. record should not in logResult;
 * 4. record should not in logRefund;
 * if LogResult exists user should use the withdraw pattern userWithdrawPendingTransactions
 * if LogRefund exists means manual refund has been done before
 * @param betId
 * @param address sendTo
 * @param original user profit
 * @param original bet value
 */