contract Jackpot is HouseOwned {
    using SafeMath for uint;

    enum Stages {
        InitialOffer,   // ICO stage: forming the jackpot fund
        GameOn,         // The game is running
        GameOver,       // The jackpot is won, paying out the jackpot
        Aborted         // ICO aborted, refunding investments
    }

    uint256 constant initialIcoTokenPrice = 4 finney;
    uint256 constant initialBetAmount = 10 finney;
    uint constant gameStartJackpotThreshold = 333 ether;
    uint constant icoTerminationTimeout = 48 hours;

    // These variables hold the values needed for minor prize checking:
    //  - when they were last won (once the number reaches the corresponding amount, the
    //    minor prize is won, and it should be reset)
    //  - how much ether was bet since it was last won
    // `etherSince*` variables start with value of 1 and always have +1 in their value
    // so that the variables never go 0, for gas consumption consistency
    uint32 public totalBets = 0;
    uint256 public etherSince20 = 1;
    uint256 public etherSince50 = 1;
    uint256 public etherSince100 = 1;
    uint256 public pendingEtherForCroupier = 0;

    // ICO status
    uint32 public icoSoldTokens;
    uint256 public icoEndTime;

    // Jackpot status
    address public lastBetUser;
    uint256 public terminationTime;
    address public winner;
    uint256 public pendingJackpotForHouse;
    uint256 public pendingJackpotForWinner;

    // General configuration and stage
    address public croupier;
    Token public token;
    Stages public stage = Stages.InitialOffer;

    // Price state
    uint256 public currentIcoTokenPrice = initialIcoTokenPrice;
    uint256 public currentBetAmount = initialBetAmount;

    // Investment tracking for emergency ICO termination
    mapping (address => uint256) public investmentOf;
    uint256 public abortTime;

    //////
    /// @title Modifiers
    //

    /// @dev Only Token
    modifier onlyToken {
        require(msg.sender == address(token));
        _;
    }

    /// @dev Only Croupier
    modifier onlyCroupier {
        require(msg.sender == address(croupier));
        _;
    }

    //////
    /// @title Events
    //

    /// @dev Fired when tokens are sold for Ether in ICO
    event EtherIco(address indexed from, uint256 value, uint256 tokens);

    /// @dev Fired when a bid with Ether is made
    event EtherBet(address indexed from, uint256 value, uint256 dividends);

    /// @dev Fired when a bid with a Token is made
    event TokenBet(address indexed from);

    /// @dev Fired when a bidder wins a minor prize
    ///      Type: 1: 20, 2: 50, 3: 100
    event MinorPrizePayout(address indexed from, uint256 value, uint8 prizeType);

    /// @dev Fired when as a result of ether bid, tokens are sold from the Croupier's pool
    ///      The parameters are who bought them, how many tokens, and for how much Ether they were sold
    event SoldTokensFromCroupier(address indexed from, uint256 value, uint256 tokens);

    /// @dev Fired when the jackpot is won
    event JackpotWon(address indexed from, uint256 value);


    //////
    /// @title Constructor and Initialization
    //

    /// @dev The contract constructor
    /// @param _croupier The address of the trusted Croupier bot's account
    function Jackpot(address _croupier)
        HouseOwned()
        public
    {
        require(_croupier != 0x0);
        croupier = _croupier;

        // There are no bets (it even starts in ICO stage), so initialize
        // lastBetUser, just so that value is not zero and is meaningful
        // The game can't end until at least one bid is made, and once
        // a bid is made, this value is permanently overwritten.
        lastBetUser = _croupier;
    }

    /// @dev Function to set address of Token contract once after creation
    /// @param _token Address of the Token contract (JACK Token)
    function setToken(address _token) onlyHouse public {
        require(address(token) == 0x0);
        require(_token != address(this)); // Protection from admin's mistake

        token = Token(_token);
    }


    //////
    /// @title Default Function
    //

    /// @dev The fallback function for receiving ether (bets)
    ///      Action depends on stages:
    ///       - ICO: just sell the tokens
    ///       - Game: accept bets, award tokens, award minor (20, 50, 100) prizes
    ///       - Game Over: pay out jackpot
    ///       - Aborted: fail
    function() payable public {
        require(croupier != 0x0);
        require(address(token) != 0x0);
        require(stage != Stages.Aborted);

        uint256 tokens;

        if (stage == Stages.InitialOffer) {

            // First, check if the ICO is over. If it is, trigger the events and
            // refund sent ether
            bool started = checkGameStart();
            if (started) {
                // Refund ether without failing the transaction
                // (because side-effect is needed)
                msg.sender.transfer(msg.value);
                return;
            }

            require(msg.value >= currentIcoTokenPrice);
        
            // THE PLAN
            // 1. [CHECK + EFFECT] Calculate how much times price, the investment amount is,
            //    calculate how many tokens the investor is going to get
            // 2. [EFFECT] Log and count
            // 3. [EFFECT] Check game start conditions and maybe start the game
            // 4. [INT] Award the tokens
            // 5. [INT] Transfer 20% to house

            // 1. [CHECK + EFFECT] Checking the amount
            tokens = _icoTokensForEther(msg.value);

            // 2. [EFFECT] Log
            // Log the ICO event and count investment
            EtherIco(msg.sender, msg.value, tokens);

            investmentOf[msg.sender] = investmentOf[msg.sender].add(
                msg.value.sub(msg.value / 5)
            );

            // 3. [EFFECT] Game start
            // Check if we have accumulated the jackpot amount required for game start
            if (icoEndTime == 0 && this.balance >= gameStartJackpotThreshold) {
                icoEndTime = now + icoTerminationTimeout;
            }

            // 4. [INT] Awarding tokens
            // Award the deserved tokens (if any)
            if (tokens > 0) {
                token.transfer(msg.sender, tokens);
            }

            // 5. [INT] House
            // House gets 20% of ICO according to the rules
            house.transfer(msg.value / 5);

        } else if (stage == Stages.GameOn) {

            // First, check if the game is over. If it is, trigger the events and
            // refund sent ether
            bool terminated = checkTermination();
            if (terminated) {
                // Refund ether without failing the transaction
                // (because side-effect is needed)
                msg.sender.transfer(msg.value);
                return;
            }

            // Now processing an Ether bid
            require(msg.value >= currentBetAmount);

            // THE PLAN
            // 1. [CHECK] Calculate how much times min-bet, the bet amount is,
            //    calculate how many tokens the player is going to get
            // 2. [CHECK] Check how much is sold from the Croupier's pool, and how much from Jackpot
            // 3. [EFFECT] Deposit 25% to the Croupier (for dividends and house's benefit)
            // 4. [EFFECT] Log and mark bid
            // 6. [INT] Check and reward (if won) minor (20, 100, 1000) prizes
            // 7. [EFFECT] Update bet amount
            // 8. [INT] Award the tokens


            // 1. [CHECK + EFFECT] Checking the bet amount and token reward
            tokens = _betTokensForEther(msg.value);

            // 2. [CHECK] Check how much is sold from the Croupier's pool, and how much from Jackpot
            //    The priority is (1) Croupier, (2) Jackpot
            uint256 sellingFromJackpot = 0;
            uint256 sellingFromCroupier = 0;
            if (tokens > 0) {
                uint256 croupierPool = token.frozenPool();
                uint256 jackpotPool = token.balanceOf(this);

                if (croupierPool == 0) {
                    // Simple case: only Jackpot is selling
                    sellingFromJackpot = tokens;
                    if (sellingFromJackpot > jackpotPool) {
                        sellingFromJackpot = jackpotPool;
                    }
                } else if (jackpotPool == 0 || tokens <= croupierPool) {
                    // Simple case: only Croupier is selling
                    // either because Jackpot has 0, or because Croupier takes over
                    // by priority and has enough tokens in its pool
                    sellingFromCroupier = tokens;
                    if (sellingFromCroupier > croupierPool) {
                        sellingFromCroupier = croupierPool;
                    }
                } else {
                    // Complex case: both are selling now
                    sellingFromCroupier = croupierPool;  // (tokens > croupierPool) is guaranteed at this point
                    sellingFromJackpot = tokens.sub(sellingFromCroupier);
                    if (sellingFromJackpot > jackpotPool) {
                        sellingFromJackpot = jackpotPool;
                    }
                }
            }

            // 3. [EFFECT] Croupier deposit
            // Transfer a portion to the Croupier for dividend payout and house benefit
            // Dividends are a sum of:
            //   + 25% of bet
            //   + 50% of price of tokens sold from Jackpot (or just anything other than the bet and Croupier payment)
            //   + 0%  of price of tokens sold from Croupier
            //          (that goes in SoldTokensFromCroupier instead)
            uint256 tokenValue = msg.value.sub(currentBetAmount);

            uint256 croupierSaleRevenue = 0;
            if (sellingFromCroupier > 0) {
                croupierSaleRevenue = tokenValue.div(
                    sellingFromJackpot.add(sellingFromCroupier)
                ).mul(sellingFromCroupier);
            }
            uint256 jackpotSaleRevenue = tokenValue.sub(croupierSaleRevenue);

            uint256 dividends = (currentBetAmount.div(4)).add(jackpotSaleRevenue.div(2));

            // 100% of money for selling from Croupier still goes to Croupier
            // so that it's later paid out to the selling user
            pendingEtherForCroupier = pendingEtherForCroupier.add(dividends.add(croupierSaleRevenue));

            // 4. [EFFECT] Log and mark bid
            // Log the bet with actual amount charged (value less change)
            EtherBet(msg.sender, msg.value, dividends);
            lastBetUser = msg.sender;
            terminationTime = now + _terminationDuration();

            // If anything was sold from Croupier, log it appropriately
            if (croupierSaleRevenue > 0) {
                SoldTokensFromCroupier(msg.sender, croupierSaleRevenue, sellingFromCroupier);
            }

            // 5. [INT] Minor prizes
            // Check for winning minor prizes
            _checkMinorPrizes(msg.sender, currentBetAmount);

            // 6. [EFFECT] Update bet amount
            _updateBetAmount();

            // 7. [INT] Awarding tokens
            if (sellingFromJackpot > 0) {
                token.transfer(msg.sender, sellingFromJackpot);
            }
            if (sellingFromCroupier > 0) {
                token.transferFromCroupier(msg.sender, sellingFromCroupier);
            }

        } else if (stage == Stages.GameOver) {

            require(msg.sender == winner || msg.sender == house);

            if (msg.sender == winner) {
                require(pendingJackpotForWinner > 0);

                uint256 winnersPay = pendingJackpotForWinner;
                pendingJackpotForWinner = 0;

                msg.sender.transfer(winnersPay);
            } else if (msg.sender == house) {
                require(pendingJackpotForHouse > 0);

                uint256 housePay = pendingJackpotForHouse;
                pendingJackpotForHouse = 0;

                msg.sender.transfer(housePay);
            }
        }
    }

    // Croupier will call this function when the jackpot is won
    // If Croupier fails to call the function for any reason, house and winner
    // still can claim their jackpot portion by sending ether to Jackpot
    function payOutJackpot() onlyCroupier public {
        require(winner != 0x0);
    
        if (pendingJackpotForHouse > 0) {
            uint256 housePay = pendingJackpotForHouse;
            pendingJackpotForHouse = 0;

            house.transfer(housePay);
        }

        if (pendingJackpotForWinner > 0) {
            uint256 winnersPay = pendingJackpotForWinner;
            pendingJackpotForWinner = 0;

            winner.transfer(winnersPay);
        }

    }

    //////
    /// @title Public Functions
    //

    /// @dev View function to check whether the game should be terminated
    ///      Used as internal function by checkTermination, as well as by the
    ///      Croupier bot, to check whether it should call checkTermination
    /// @return Whether the game should be terminated by timeout
    function shouldBeTerminated() public view returns (bool should) {
        return stage == Stages.GameOn && terminationTime != 0 && now > terminationTime;
    }

    /// @dev Check whether the game should be terminated, and if it should, terminate it
    /// @return Whether the game was terminated as the result
    function checkTermination() public returns (bool terminated) {
        if (shouldBeTerminated()) {
            stage = Stages.GameOver;

            winner = lastBetUser;

            // Flush amount due for Croupier immediately
            _flushEtherToCroupier();

            // The rest should be claimed by the winner (except what house gets)
            JackpotWon(winner, this.balance);


            uint256 jackpot = this.balance;
            pendingJackpotForHouse = jackpot.div(5);
            pendingJackpotForWinner = jackpot.sub(pendingJackpotForHouse);

            return true;
        }

        return false;
    }

    /// @dev View function to check whether the game should be started
    ///      Used as internal function by `checkGameStart`, as well as by the
    ///      Croupier bot, to check whether it should call `checkGameStart`
    /// @return Whether the game should be started
    function shouldBeStarted() public view returns (bool should) {
        return stage == Stages.InitialOffer && icoEndTime != 0 && now > icoEndTime;
    }

    /// @dev Check whether the game should be started, and if it should, start it
    /// @return Whether the game was started as the result
    function checkGameStart() public returns (bool started) {
        if (shouldBeStarted()) {
            stage = Stages.GameOn;

            return true;
        }

        return false;
    }

    /// @dev Bet 1 token in the game
    ///      The token has already been burned having passed all checks, so
    ///      just process the bet of 1 token
    function betToken(address _user) onlyToken public {
        // Token bets can only be accepted in the game stage
        require(stage == Stages.GameOn);

        bool terminated = checkTermination();
        if (terminated) {
            return;
        }

        TokenBet(_user);
        lastBetUser = _user;
        terminationTime = now + _terminationDuration();

        // Check for winning minor prizes
        _checkMinorPrizes(_user, 0);
    }

    /// @dev Allows House to terminate ICO as an emergency measure
    function abort() onlyHouse public {
        require(stage == Stages.InitialOffer);

        stage = Stages.Aborted;
        abortTime = now;
    }

    /// @dev In case the ICO is emergency-terminated by House, allows investors
    ///      to pull back the investments
    function claimRefund() public {
        require(stage == Stages.Aborted);
        require(investmentOf[msg.sender] > 0);

        uint256 payment = investmentOf[msg.sender];
        investmentOf[msg.sender] = 0;

        msg.sender.transfer(payment);
    }

    /// @dev In case the ICO was terminated, allows House to kill the contract in 2 months
    ///      after the termination date
    function killAborted() onlyHouse public {
        require(stage == Stages.Aborted);
        require(now > abortTime + 60 days);

        selfdestruct(house);
    }



    //////
    /// @title Internal Functions
    //

    /// @dev Get current bid timer duration
    /// @return duration The duration
    function _terminationDuration() internal view returns (uint256 duration) {
        return (5 + 19200 / (100 + totalBets)) * 1 minutes;
    }

    /// @dev Updates the current ICO price according to the rules
    function _updateIcoPrice() internal {
        uint256 newIcoTokenPrice = currentIcoTokenPrice;

        if (icoSoldTokens < 10000) {
            newIcoTokenPrice = 4 finney;
        } else if (icoSoldTokens < 20000) {
            newIcoTokenPrice = 5 finney;
        } else if (icoSoldTokens < 30000) {
            newIcoTokenPrice = 5.3 finney;
        } else if (icoSoldTokens < 40000) {
            newIcoTokenPrice = 5.7 finney;
        } else {
            newIcoTokenPrice = 6 finney;
        }

        if (newIcoTokenPrice != currentIcoTokenPrice) {
            currentIcoTokenPrice = newIcoTokenPrice;
        }
    }

    /// @dev Updates the current bid price according to the rules
    function _updateBetAmount() internal {
        uint256 newBetAmount = 10 finney + (totalBets / 100) * 6 finney;

        if (newBetAmount != currentBetAmount) {
            currentBetAmount = newBetAmount;
        }
    }

    /// @dev Calculates how many tokens a user should get with a given Ether bid
    /// @param value The bid amount
    /// @return tokens The number of tokens
    function _betTokensForEther(uint256 value) internal view returns (uint256 tokens) {
        // One bet amount is for the bet itself, for the rest we will sell
        // tokens
        tokens = (value / currentBetAmount) - 1;

        if (tokens >= 1000) {
            tokens = tokens + tokens / 4; // +25%
        } else if (tokens >= 300) {
            tokens = tokens + tokens / 5; // +20%
        } else if (tokens >= 100) {
            tokens = tokens + tokens / 7; // ~ +14.3%
        } else if (tokens >= 50) {
            tokens = tokens + tokens / 10; // +10%
        } else if (tokens >= 20) {
            tokens = tokens + tokens / 20; // +5%
        }
    }

    /// @dev Calculates how many tokens a user should get with a given ICO transfer
    /// @param value The transfer amount
    /// @return tokens The number of tokens
    function _icoTokensForEther(uint256 value) internal returns (uint256 tokens) {
        // How many times the input is greater than current token price
        tokens = value / currentIcoTokenPrice;

        if (tokens >= 10000) {
            tokens = tokens + tokens / 4; // +25%
        } else if (tokens >= 5000) {
            tokens = tokens + tokens / 5; // +20%
        } else if (tokens >= 1000) {
            tokens = tokens + tokens / 7; // ~ +14.3%
        } else if (tokens >= 500) {
            tokens = tokens + tokens / 10; // +10%
        } else if (tokens >= 200) {
            tokens = tokens + tokens / 20; // +5%
        }

        // Checking if Jackpot has the tokens in reserve
        if (tokens > token.balanceOf(this)) {
            tokens = token.balanceOf(this);
        }

        icoSoldTokens += (uint32)(tokens);

        _updateIcoPrice();
    }

    /// @dev Flush the currently pending Ether to Croupier
    function _flushEtherToCroupier() internal {
        if (pendingEtherForCroupier > 0) {
            uint256 willTransfer = pendingEtherForCroupier;
            pendingEtherForCroupier = 0;
            
            croupier.transfer(willTransfer);
        }
    }

    /// @dev Count the bid towards minor prize fund, check if the user
    ///      wins a minor prize, and if they did, transfer the prize to them
    /// @param user The user in question
    /// @param value The bid value
    function _checkMinorPrizes(address user, uint256 value) internal {
        // First and foremost, increment the counters and ether counters
        totalBets ++;
        if (value > 0) {
            etherSince20 = etherSince20.add(value);
            etherSince50 = etherSince50.add(value);
            etherSince100 = etherSince100.add(value);
        }

        // Now actually check if the bets won

        uint256 etherPayout;

        if ((totalBets + 30) % 100 == 0) {
            // Won 100th
            etherPayout = (etherSince100 - 1) / 10;
            etherSince100 = 1;

            MinorPrizePayout(user, etherPayout, 3);

            user.transfer(etherPayout);
            return;
        }

        if ((totalBets + 5) % 50 == 0) {
            // Won 100th
            etherPayout = (etherSince50 - 1) / 10;
            etherSince50 = 1;

            MinorPrizePayout(user, etherPayout, 2);

            user.transfer(etherPayout);
            return;
        }

        if (totalBets % 20 == 0) {
            // Won 20th
            etherPayout = (etherSince20 - 1) / 10;
            etherSince20 = 1;

            _flushEtherToCroupier();

            MinorPrizePayout(user, etherPayout, 1);

            user.transfer(etherPayout);
            return;
        }

        return;
    }

}
function _flushEtherToCroupier() internal {
    if (pendingEtherForCroupier > 0) {
        uint256 willTransfer = pendingEtherForCroupier;
        pendingEtherForCroupier = 0;
            
        croupier.transfer(willTransfer);
    }
}

/// @dev Flush the currently pending Ether to Croupier