contract Bet100 is usingOraclize {

    using NameFilter for string;

    uint constant edge = 100; 
    uint constant maxWin = 1000; 
    uint constant maxWinCheck = maxWin * 2;  
    uint constant minBet = 10 finney;
    uint constant emergencyWithdrawalRatio = 10; 

    uint safeGas = 400000;
    uint constant ORACLIZE_GAS_LIMIT = 175000;
    uint constant INVALID_BET_MARKER = 99999;
    uint constant INVALID_BET_MARKER_1 = 99998;
    uint constant EMERGENCY_TIMEOUT = 30 seconds;

    string public randomOrgAPIKey = "e1de2fda-77b3-4fa5-bdec-cd09c82bcff7";

    PlayerBookInterface constant private PlayerBook = PlayerBookInterface(0x8c7f8865acdf45ce86adac2939a72658d12272e3);


    struct Bet {
        address playerAddress;      
        uint betNumber;             
        uint amountBet;             
        uint numberRolled;          
        uint laff;                  
        uint betTime;               
        uint winAmount;             
        bytes32 myid;                  
    }

    struct WithdrawalProposal {
        address toAddress;
        uint atTime;
    }

    uint public _bankRoll = 0;

    address public owner;
    address public houseAddress;
    bool public isStopped;


    mapping (bytes32 => Bet) public bets;
    bytes32[] public betsKeys;
    uint256 betsCount;
    mapping(uint => Bet) public betsInfo;


    uint public investorsProfit = 0;

    uint public investorsLosses = 0;


    uint constant dealerMinReward = 100 szabo; 
    uint constant dealer_level_1_reward = 20; 
    uint constant dealer_level_2_reward = 10; 
    uint constant dealer_level_3_reward = 5; 
    uint public dealer_reward_total = 0; 

    uint public draw_amount = 0;
    uint public invest_amount = 0;

    event LOG_NewBet(address playerAddress, uint amount, bytes32 myid);
    event LOG_BetWon(address playerAddress, uint numberRolled, uint amountWon, uint betId);
    event LOG_BetLost(address playerAddress, uint numberRolled, uint betId);
    event LOG_EmergencyWithdrawalProposed();
    event LOG_EmergencyWithdrawalFailed(address withdrawalAddress);
    event LOG_EmergencyWithdrawalSucceeded(address withdrawalAddress, uint amountWithdrawn);
    event LOG_FailedSend(address receiver, uint amount);
    event LOG_ZeroSend();
    event LOG_InvestorEntrance(address investor, uint amount);
    event LOG_InvestorCapitalUpdate(address investor, int amount);
    event LOG_InvestorExit(address investor, uint amount);
    event LOG_ContractStopped();
    event LOG_ContractResumed();
    event LOG_OwnerAddressChanged(address oldAddr, address newOwnerAddress);
    event LOG_HouseAddressChanged(address oldAddr, address newHouseAddress);
    event LOG_RandomOrgAPIKeyChanged(string oldKey, string newKey);
    event LOG_GasLimitChanged(uint oldGasLimit, uint newGasLimit);
    event LOG_EmergencyAutoStop();
    event LOG_EmergencyWithdrawalVote(address investor, bool vote);
    event LOG_ValueIsTooBig();
    event LOG_SuccessfulSend(address addr, uint amount);

    constructor() public {
        oraclize_setProof(proofType_TLSNotary | proofStorage_IPFS);
        owner = msg.sender;
        houseAddress = msg.sender;
        betsCount = 0;
    }

    //SECTION I: MODIFIERS AND HELPER FUNCTIONS

    //MODIFIERS

    modifier onlyIfNotStopped {
        require(isStopped == false, "Game is stop!!!");
        //if (isStopped) throw;
        _;
    }

    modifier onlyIfStopped {
        require(isStopped == true, "Game is not stop!!!");
        // if (!isStopped) throw;
        _;
    }

    modifier onlyOwner {
        require(owner == msg.sender, "Only owner can operator !!!");
        // if (owner != msg.sender) throw;
        _;
    }

    modifier onlyOraclize {
        require(msg.sender == oraclize_cbAddress(), "Only Oraclize address can operator !!!");
        //if (msg.sender != oraclize_cbAddress()) throw;
        _;
    }

    modifier onlyMoreThanZero {
        require(msg.value != 0, "onlyMoreThanZero !!!");
        //if (msg.value == 0) throw;
        _;
    }

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

    modifier onlyIfBetSizeIsStillCorrect(bytes32 myid) {
        if ((((bets[myid].amountBet * ((10000 - edge) - bets[myid].betNumber)) / bets[myid].betNumber ) <= (maxWinCheck * getBankroll()) / 10000)  && (bets[myid].amountBet >= minBet)) {
             _;
        }
        else {
            bets[myid].numberRolled = INVALID_BET_MARKER_1;
            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 betNumber) {
        if (numberRolled - 1 < betNumber) {
            _;
        }
    }

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

    modifier onlyIfValidGas(uint newGasLimit) {
        require(ORACLIZE_GAS_LIMIT + newGasLimit >= ORACLIZE_GAS_LIMIT, "gas is low");
        // if (ORACLIZE_GAS_LIMIT + newGasLimit < ORACLIZE_GAS_LIMIT) throw;
        require(newGasLimit >= 25000, "gas is low");
        // if (newGasLimit < 25000) throw;
        _;
    }

    modifier onlyIfNotProcessed(bytes32 myid) {
        require(bets[myid].numberRolled <= 0, "onlyIfNotProcessed");
        // if (bets[myid].numberRolled > 0) throw;
        _;
    }
    //检测下注点数是否合法
    modifier onlyRollNumberValid(uint rollNumber) {
        require(rollNumber < 100, "roll number invalid");
        require(rollNumber > 0, "roll number invalid");
        _;
    }

    //CONSTANT HELPER FUNCTIONS

    function getBankroll()
        view
        public
        returns(uint) {

        if (_bankRoll + investorsProfit <= investorsLosses) {
            return 0;
        }
        else {
            return _bankRoll + investorsProfit - investorsLosses;
        }
        //return _bankRoll;
    }

    function getStatus()
        view
        external
        returns(uint, uint, uint, uint, uint, uint) {

        uint bankroll = getBankroll();
        //uint minInvestment = getMinInvestment();
        return (bankroll, edge, maxWin, minBet, (investorsProfit - investorsLosses), betsCount);
    }

    function getBet(uint id)
        public
        view
        returns(address, uint, uint, uint, uint, uint) {

        if (id < betsCount) {
            return (betsInfo[id].playerAddress, betsInfo[id].amountBet, betsInfo[id].betNumber,  betsInfo[id].numberRolled, betsInfo[id].winAmount,  betsInfo[id].betTime);
        }
    }

    function getBetKey(uint id)
        public
        view
        returns(bytes32) {

        if (id < betsCount) {
            return (betsInfo[id].myid);
        }
    }

    function numBets()
        view
        public
        returns(uint) {

        return betsCount;
    }

    function getBetReward(uint betNumber, uint betAmount) 
        view
        public
        returns(uint, uint, uint) 
    {
        uint oraclizeFee = OraclizeI(OAR.getAddress()).getPrice("URL", ORACLIZE_GAS_LIMIT + safeGas);
        uint realBet = betAmount;
        uint winAmount = ((realBet * (10000 - edge)) / (betNumber * 100)) - oraclizeFee ;
        return (winAmount, realBet, oraclizeFee);
    }

    function OraclizeIFee() 
        view
        public
        returns(uint) 
    {
        return OraclizeI(OAR.getAddress()).getPrice("URL", ORACLIZE_GAS_LIMIT + safeGas);
    }

    function getMinBetAmount()
        view
        public
        returns(uint) {

        uint oraclizeFee = OraclizeI(OAR.getAddress()).getPrice("URL", ORACLIZE_GAS_LIMIT + safeGas);
        return oraclizeFee + minBet;
    }

    function getMaxBetAmount(uint256 withNumber)
        view
        public
        returns(uint) {
        uint betNumber = withNumber * 100;
        uint oraclizeFee = OraclizeI(OAR.getAddress()).getPrice("URL", ORACLIZE_GAS_LIMIT + safeGas);
        uint betValue =  (maxWin * getBankroll()) * betNumber / (10000 * (10000 - edge - betNumber));
        return betValue + oraclizeFee;
    }


    function changeOraclizeProofType(byte _proofType)
        onlyOwner 
        public {

        require(_proofType != 0x00, "_proofType is 0x00");
        //if (_proofType == 0x00) throw;
        oraclize_setProof( _proofType |  proofStorage_IPFS );
    }

    function changeOraclizeConfig(bytes32 _config)
        onlyOwner 
        public {

        oraclize_setConfig(_config);
    }

    // PRIVATE HELPERS FUNCTION

    function safeSend(address addr, uint value)
        private {

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

        if (this.balance < value) {
            emit LOG_ValueIsTooBig();
            return;
        }
        //发送资金
        if (!(addr.call.gas(safeGas).value(value)())) {
            emit LOG_FailedSend(addr, value);
            if (addr != houseAddress) {
                
                if (!(houseAddress.call.gas(safeGas).value(value)())) LOG_FailedSend(houseAddress, value);
            }
        }

        emit LOG_SuccessfulSend(addr,value);
    }

    // SECTION II: BET & BET PROCESSING

    function()
        payable 
        public {

        bet(5, 0);
    }

    function bet(uint256 betNumber, uint affCode)
        payable
        onlyIfNotStopped 
        onlyRollNumberValid(betNumber) {

        uint256 betNumberHigh = betNumber * 100;
        uint oraclizeFee = OraclizeI(OAR.getAddress()).getPrice("URL", ORACLIZE_GAS_LIMIT + safeGas);
        require(oraclizeFee < msg.value, "msg.value can not pay for oraclizeFee");
        //if (oraclizeFee >= msg.value) throw;
        uint betValue = msg.value;

        uint pID = PlayerBook.getPlayerID(msg.sender, affCode);
        if ((((betValue * ((10000 - edge) - betNumberHigh)) / betNumberHigh ) <= (maxWin * getBankroll()) / 10000) && (betValue >= minBet)) {

            string memory str1 = "[URL] ['json(https://api.random.org/json-rpc/1/invoke).result.random.data.0', '\\n{\"jsonrpc\":\"2.0\",\"method\":\"generateSignedIntegers\",\"params\":{\"apiKey\":\"";
            string memory str2 = randomOrgAPIKey;
            string memory str3 = "\",\"n\":1,\"min\":1,\"max\":10000${[identity] \"}\"},\"id\":1${[identity] \"}\"}']";
            string memory query = strConcat(str1, str2, str3);

            bytes32 myid =
                oraclize_query(
                    "nested",
                    query,
                    ORACLIZE_GAS_LIMIT + safeGas
                );
            uint laff = PlayerBook.getPlayerLAff(pID);
            bets[myid] = Bet(msg.sender, betNumberHigh, betValue, 0, laff, now, 0, myid);
            betsKeys.push(myid);
            emit LOG_NewBet(msg.sender, betValue, myid);
        }
        else {
            revert("out of bank roll");
        }
    }

    function __callback(bytes32 myid, string result, bytes proof)
        onlyOraclize
        onlyIfBetExist(myid)
        onlyIfNotProcessed(myid)
        onlyIfValidRoll(myid, result)
        onlyIfBetSizeIsStillCorrect(myid) {

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

    function isWinningBet(Bet thisBet, uint numberRolled, uint betNumber)
        private
        onlyWinningBets(numberRolled, betNumber) {

        uint winAmount = (thisBet.amountBet * (10000 - edge)) / betNumber;

        uint oraclizeFee = OraclizeI(OAR.getAddress()).getPrice("URL", ORACLIZE_GAS_LIMIT + safeGas);
        winAmount = winAmount - oraclizeFee;

        emit LOG_BetWon(thisBet.playerAddress, numberRolled, winAmount, betsCount);

        safeSend(thisBet.playerAddress, winAmount);
        
        //统计
        thisBet.winAmount = winAmount;
        betsInfo[betsCount] = thisBet;
        PlayerBook.betXaddr(thisBet.playerAddress, thisBet.amountBet, true, betsCount, winAmount);

        //计算返现
        affReward(thisBet, betsCount);

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

        uint256 rLosses = winAmount - thisBet.amountBet;
        if(winAmount < thisBet.amountBet)
            rLosses = 0;

        investorsLosses += rLosses;
    }

    function isLosingBet(Bet thisBet, uint numberRolled, uint betNumber)
        private
        onlyLosingBets(numberRolled, betNumber) {

        emit LOG_BetLost(thisBet.playerAddress, numberRolled, betsCount);
        safeSend(thisBet.playerAddress, 1);


        betsInfo[betsCount] = thisBet;
        PlayerBook.betXaddr(thisBet.playerAddress, thisBet.amountBet, false, betsCount, 0);
        

        affReward(thisBet, betsCount);

        betsCount++;

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

    function affReward(Bet thisBet, uint256 betID)
        private {
        
        if(thisBet.laff > 0)
        {
            uint laff_1_reward_max = thisBet.amountBet * edge / 10000;
            uint laff_1_reward = thisBet.amountBet * edge * dealer_level_1_reward / (10000 * 100);
            if(laff_1_reward >= dealerMinReward && laff_1_reward < laff_1_reward_max)
            {

                address laff_1_address = PlayerBook.getPlayerAddr(thisBet.laff);
                dealer_reward_total += laff_1_reward;
                safeSend(laff_1_address, laff_1_reward);
                PlayerBook.rewardXID(thisBet.laff, laff_1_reward, betID, 1);


                uint laff_2_pid = PlayerBook.getPlayerLAff(thisBet.laff);
                uint laff_2_reward = thisBet.amountBet * edge * dealer_level_2_reward / (10000 * 100);
                if(laff_2_pid>0 && laff_2_reward >= dealerMinReward && laff_2_reward < laff_1_reward_max)
                {

                    address laff_2_address = PlayerBook.getPlayerAddr(laff_2_pid);
                    dealer_reward_total += laff_2_reward;
                    safeSend(laff_2_address, laff_2_reward);
                    PlayerBook.rewardXID(laff_2_pid, laff_2_reward, betID, 2);
                }
                
            }
            
        }
    }

    //SECTION III: INVEST & DIVEST

    function increaseInvestment()
        external
        payable
        onlyIfNotStopped
        onlyMoreThanZero
        onlyOwner  {

        _bankRoll += msg.value;
        invest_amount += msg.value;
    }



    function divest(uint amount)
        external
        onlyOwner {

        //divest(msg.sender);
        require(address(this).balance >= amount, "Don't have enough balance");
        //require(_bankRoll > amount, "Don't have enough _bankRoll");

        _bankRoll = address(this).balance - amount;
        draw_amount += amount;
        safeSend(owner, amount);

        investorsProfit = 0;
        investorsLosses = 0;
        
    }

    //SECTION IV: CONTRACT MANAGEMENT

    function stopContract()
        external
        onlyOwner {

        isStopped = true;
        emit LOG_ContractStopped();
    }

    function resumeContract()
        external
        onlyOwner {

        isStopped = false;
        emit LOG_ContractResumed();
    }

    function changeHouseAddress(address newHouse)
        external
        onlyOwner {

        require(newHouse != address(0x0), "new Houst is 0x0");
        //if (newHouse == address(0x0)) throw; //changed based on audit feedback
        houseAddress = newHouse;
        emit LOG_HouseAddressChanged(houseAddress, newHouse);
    }

    function changeOwnerAddress(address newOwner)
        external
        onlyOwner {

        require(newOwner != address(0x0), "new owner is 0x0");
        // if (newOwner == address(0x0)) throw;
        owner = newOwner;
        emit LOG_OwnerAddressChanged(owner, newOwner);
    }

    /**
     * @dev prevents contracts from interacting with fomo3d 
     */
    modifier isHuman() {
        address _addr = msg.sender;
        uint256 _codeLength;
        
        assembly {_codeLength := extcodesize(_addr)}
        require(_codeLength == 0, "sorry humans only");
        _;
    }
    
    function regName(string name) 
        isHuman()
        public
        payable
    {
        bytes32 _name = name.nameFilter();
        address _addr = msg.sender;
        uint256 _paid = msg.value;
        PlayerBook.registerNameFromDapp.value(_paid)(_addr, _name);
    }

    function changeRandomOrgAPIKey(string newKey) 
        onlyOwner {

        string oldKey = randomOrgAPIKey;
        randomOrgAPIKey = newKey;
        LOG_RandomOrgAPIKeyChanged(oldKey, newKey);
    }

    function changeGasLimitOfSafeSend(uint newGasLimit)
        external
        onlyOwner
        onlyIfValidGas(newGasLimit) {

        safeGas = newGasLimit;
        emit LOG_GasLimitChanged(safeGas, newGasLimit);
    }

    //SECTION V: EMERGENCY WITHDRAWAL
    function receivePlayerInfo(uint256 _pID, address _addr, bytes32 _name, uint256 _laff) 
        external
    {
        require (msg.sender == address(PlayerBook), "your not playerNames contract... hmmm..");
    }
}
// </ORACLIZE_API>
function stopContract()
        external
        onlyOwner {

        isStopped = true;
        emit LOG_ContractStopped();
    }
//SECTION IV: CONTRACT MANAGEMENT