contract REBCrowdsale is FinalizableCrowdsale, WhitelistedCrowdsale, Pausable {
    uint256 constant public BOUNTY_SHARE =               125000000e18;   // 125 M
    uint256 constant public TEAM_SHARE =                 2800000000e18;  // 2.8 B
    uint256 constant public ADVISOR_SHARE =              1750000000e18;  // 1.75 B

    uint256 constant public AIRDROP_SHARE =              200000000e18;   // 200 M
    uint256 constant public TOTAL_TOKENS_FOR_CROWDSALE = 5125000000e18;  // 5.125 B

    uint256 constant public PUBLIC_CROWDSALE_SOFT_CAP =  800000000e18;  // 800 M

    address public bountyWallet;
    address public teamReserve;
    address public advisorReserve;
    address public airdrop;

    // remainderPurchaser and remainderTokens info saved in the contract
    // used for reference for contract owner to send refund if any to last purchaser after end of crowdsale
    address public remainderPurchaser;
    uint256 public remainderAmount;

    // external contracts

    event MintedTokensFor(address indexed investor, uint256 tokensPurchased);
    event TokenRateChanged(uint256 previousRate, uint256 newRate);

    /**
     * @dev Contract constructor function
     * @param _openingTime The timestamp of the beginning of the crowdsale
     * @param _closingTime Timestamp when the crowdsale will finish
     * @param _token REB token address
     * @param _rate The token rate per ETH
     * @param _wallet Multisig wallet that will hold the crowdsale funds.
     * @param _bountyWallet Ethereum address where bounty tokens will be minted to
     */
    function REBCrowdsale
        (
            uint256 _openingTime,
            uint256 _closingTime,
            REBToken _token,
            uint256 _rate,
            address _wallet,
            address _bountyWallet
        )
        public
        FinalizableCrowdsale()
        Crowdsale(_rate, _wallet, _token)
        TimedCrowdsale(_openingTime, _closingTime)
    {
        require(_bountyWallet != address(0));
        bountyWallet = _bountyWallet;

        require(REBToken(token).paused());
        // NOTE: Ensure token ownership is transferred to crowdsale so it able to mint tokens
    }

    /**
     * @dev change crowdsale rate
     * @param newRate Figure that corresponds to the new rate per token
     */
    function setRate(uint256 newRate) external onlyOwner {
        require(newRate != 0);

        TokenRateChanged(rate, newRate);
        rate = newRate;
    }

    /**
     * @dev Mint tokens investors that send fiat for token purchases.
     * The send of fiat will be off chain and custom minting happens in this function and it performed by the owner
     * @param beneficiaryAddress Address of beneficiary
     * @param amountOfTokens Number of tokens to be created
     */
    function mintTokensFor(address beneficiaryAddress, uint256 amountOfTokens)
        public
        onlyOwner
    {
        require(beneficiaryAddress != address(0));
        require(token.totalSupply().add(amountOfTokens) <= TOTAL_TOKENS_FOR_CROWDSALE);

        _deliverTokens(beneficiaryAddress, amountOfTokens);
        MintedTokensFor(beneficiaryAddress, amountOfTokens);
    }

    /**
     * @dev Set the address which should receive the vested team and advisors tokens plus airdrop shares on finalization
     * @param _teamReserve address of team and advisor allocation contract
     * @param _advisorReserve address of team and advisor allocation contract
     * @param _airdrop address of airdrop contract
     */
    function setTeamAndAdvisorAndAirdropAddresses
        (
            address _teamReserve,
            address _advisorReserve,
            address _airdrop
        )
        public
        onlyOwner
    {
        // only able to be set once
        require(teamReserve == address(0x0) && advisorReserve == address(0x0) && airdrop == address(0x0));
        // ensure that the addresses as params to the func are not empty
        require(_teamReserve != address(0x0) && _advisorReserve != address(0x0) && _airdrop != address(0x0));

        teamReserve = _teamReserve;
        advisorReserve = _advisorReserve;
        airdrop = _airdrop;
    }

    // overriding TimeCrowdsale#hasClosed to add cap logic
    // @return true if crowdsale event has ended
    function hasClosed() public view returns (bool) {
        if (token.totalSupply() > PUBLIC_CROWDSALE_SOFT_CAP) {
            return true;
        }

        return super.hasClosed();
    }

    /**
     * @dev Overrides delivery by minting tokens upon purchase.
     * @param _beneficiary Token purchaser
     * @param _tokenAmount Number of tokens to be minted
     */
    function _deliverTokens(address _beneficiary, uint256 _tokenAmount) internal {
        require(MintableToken(token).mint(_beneficiary, _tokenAmount));
    }

    /**
     * @dev Override validation of an incoming purchase.
     * Use require statemens to revert state when conditions are not met. Use super to concatenate validations.
     * @param _beneficiary Address performing the token purchase
     * @param _weiAmount Value in wei involved in the purchase
     */
    function _preValidatePurchase(address _beneficiary, uint256 _weiAmount)
        internal
        isWhitelisted(_beneficiary)
        whenNotPaused
    {
        require(_beneficiary != address(0));
        require(_weiAmount != 0);
        require(token.totalSupply() < TOTAL_TOKENS_FOR_CROWDSALE);
    }

    /**
     * @dev Override to extend the way in which ether is converted to tokens.
     * @param _weiAmount Value in wei to be converted into tokens
     * @return Number of tokens that can be purchased with the specified _weiAmount
     */
    function _getTokenAmount(uint256 _weiAmount) internal view returns (uint256) {
        uint256 tokensAmount = _weiAmount.mul(rate);

        // remainder logic
        if (token.totalSupply().add(tokensAmount) > TOTAL_TOKENS_FOR_CROWDSALE) {
            tokensAmount = TOTAL_TOKENS_FOR_CROWDSALE.sub(token.totalSupply());
            uint256 _weiAmountLocalScope = tokensAmount.div(rate);

            // save info so as to refund purchaser after crowdsale's end
            remainderPurchaser = msg.sender;
            remainderAmount = _weiAmount.sub(_weiAmountLocalScope);

            // update state here so when it is updated again in buyTokens the weiAmount reflects the remainder logic
            if (weiRaised > _weiAmount.add(_weiAmountLocalScope))
                weiRaised = weiRaised.sub(_weiAmount.add(_weiAmountLocalScope));
        }

        return tokensAmount;
    }

    /**
     * @dev finalizes crowdsale
     */
    function finalization() internal {
        // This must have been set manually prior to finalize().
        require(teamReserve != address(0x0) && advisorReserve != address(0x0) && airdrop != address(0x0));

        if (TOTAL_TOKENS_FOR_CROWDSALE > token.totalSupply()) {
            uint256 remainingTokens = TOTAL_TOKENS_FOR_CROWDSALE.sub(token.totalSupply());
            _deliverTokens(wallet, remainingTokens);
        }

        // final minting
        _deliverTokens(bountyWallet, BOUNTY_SHARE);
        _deliverTokens(teamReserve, TEAM_SHARE);
        _deliverTokens(advisorReserve, ADVISOR_SHARE);
        _deliverTokens(airdrop, AIRDROP_SHARE);

        REBToken(token).finishMinting();
        REBToken(token).unpause();
        super.finalization();
    }
}
/**
 * @title REB Crowdsale contract - crowdsale contract for the REB tokens.
 */
function REBCrowdsale
    (
        uint256 _openingTime,
        uint256 _closingTime,
        REBToken _token,
        uint256 _rate,
        address _wallet,
        address _bountyWallet
    )
    public
    FinalizableCrowdsale()
    Crowdsale(_rate, _wallet, _token)
    TimedCrowdsale(_openingTime, _closingTime)
{
    require(_bountyWallet != address(0));
    bountyWallet = _bountyWallet;

    require(REBToken(token).paused());
    // NOTE: Ensure token ownership is transferred to crowdsale so it able to mint tokens
}

/**
 * @dev Contract constructor function
 * @param _openingTime The timestamp of the beginning of the crowdsale
 * @param _closingTime Timestamp when the crowdsale will finish
 * @param _token REB token address
 * @param _rate The token rate per ETH
 * @param _wallet Multisig wallet that will hold the crowdsale funds.
 * @param _bountyWallet Ethereum address where bounty tokens will be minted to
 */