contract MultipleBidReservation is Administrable, Whitelistable {
    using SafeMath for uint256;

    event LogMultipleBidReservationCreated(
        uint256 indexed startBlock,
        uint256 indexed endBlock,
        uint256 maxSubscribers,
        uint256 maxCap,
        uint256 minDeposit,
        uint256 maxWhitelistLength,
        uint256 indexed whitelistThreshold
    );
    event LogStartBlockChanged(uint256 indexed startBlock);
    event LogEndBlockChanged(uint256 indexed endBlock);
    event LogMaxCapChanged(uint256 indexed maxCap);
    event LogMinDepositChanged(uint256 indexed minDeposit);
    event LogMaxSubscribersChanged(uint256 indexed maxSubscribers);
    event LogCrowdsaleAddressChanged(address indexed crowdsale);
    event LogAbort(address indexed caller);
    event LogDeposit(
        address indexed subscriber,
        uint256 indexed amount,
        uint256 indexed balance,
        uint256 raisedFunds
    );
    event LogBuy(address caller, uint256 indexed from, uint256 indexed to);
    event LogRefund(address indexed subscriber, uint256 indexed amount, uint256 indexed raisedFunds);

    // The block interval [start, end] where investments are allowed (both inclusive)
    uint256 public startBlock;
    uint256 public endBlock;

    // RC maximum cap (expressed in wei)
    uint256 public maxCap;

    // RC minimum balance per subscriber (expressed in wei)
    uint256 public minDeposit;

    // RC maximum number of allowed subscribers
    uint256 public maxSubscribers;

    // Crowdsale public address
    TokenSale public crowdsale;

    // RC current raised balance expressed in wei
    uint256 public raisedFunds;

    // ERC20-compliant token issued during ICO
    ERC20 public token;

    // Reservation balances (expressed in wei) deposited by each subscriber
    mapping (address => uint256) public balances;

    // The list of subscribers in incoming order
    address[] public subscribers;

    // Flag indicating if reservation has been forcibly terminated
    bool public aborted;

    // The maximum value for whitelist threshold in wei
    uint256 constant public MAX_WHITELIST_THRESHOLD = 2**256 - 1;

    modifier beforeStart() {
        require(block.number < startBlock, "already started");
        _;
    }

    modifier beforeEnd() {
        require(block.number <= endBlock, "already ended");
        _;
    }

    modifier whenReserving() {
        require(!aborted, "aborted");
        _;
    }

    modifier whenAborted() {
        require(aborted, "not aborted");
        _;
    }

    constructor(
        uint256 _startBlock,
        uint256 _endBlock,
        uint256 _maxSubscribers,
        uint256 _maxCap,
        uint256 _minDeposit,
        uint256 _maxWhitelistLength,
        uint256 _whitelistThreshold
    )
    Whitelistable(_maxWhitelistLength, _whitelistThreshold) public
    {
        require(_startBlock >= block.number, "_startBlock < current block");
        require(_endBlock >= _startBlock, "_endBlock < _startBlock");
        require(_maxSubscribers > 0, "_maxSubscribers is 0");
        require(_maxCap > 0, "_maxCap is 0");
        require(_minDeposit > 0, "_minDeposit is 0");

        startBlock = _startBlock;
        endBlock = _endBlock;
        maxSubscribers = _maxSubscribers;
        maxCap = _maxCap;
        minDeposit = _minDeposit;

        emit LogMultipleBidReservationCreated(
            startBlock,
            endBlock,
            maxSubscribers,
            maxCap,
            minDeposit,
            _maxWhitelistLength,
            _whitelistThreshold
        );
    }

    function hasStarted() public view returns(bool started) {
        return block.number >= startBlock;
    }

    function hasEnded() public view returns(bool ended) {
        return block.number > endBlock;
    }

    /**
     * @return The current number of RC subscribers
     */
    function numSubscribers() public view returns(uint256 numberOfSubscribers) {
        return subscribers.length;
    }

    /**
     * Change the RC start block number.
     * @param _startBlock The start block
     */
    function setStartBlock(uint256 _startBlock) external onlyOwner beforeStart whenReserving {
        require(_startBlock >= block.number, "_startBlock < current block");
        require(_startBlock <= endBlock, "_startBlock > endBlock");
        require(_startBlock != startBlock, "_startBlock == startBlock");

        startBlock = _startBlock;

        emit LogStartBlockChanged(_startBlock);
    }

    /**
     * Change the RC end block number.
     * @param _endBlock The end block
     */
    function setEndBlock(uint256 _endBlock) external onlyOwner beforeEnd whenReserving {
        require(_endBlock >= block.number, "_endBlock < current block");
        require(_endBlock >= startBlock, "_endBlock < startBlock");
        require(_endBlock != endBlock, "_endBlock == endBlock");

        endBlock = _endBlock;

        emit LogEndBlockChanged(_endBlock);
    }

    /**
     * Change the RC maximum cap. New value shall be at least equal to raisedFunds.
     * @param _maxCap The RC maximum cap, expressed in wei
     */
    function setMaxCap(uint256 _maxCap) external onlyOwner beforeEnd whenReserving {
        require(_maxCap > 0 && _maxCap >= raisedFunds, "invalid _maxCap");

        maxCap = _maxCap;

        emit LogMaxCapChanged(maxCap);
    }

    /**
     * Change the minimum deposit for each RC subscriber. New value shall be lower than previous.
     * @param _minDeposit The minimum deposit for each RC subscriber, expressed in wei
     */
    function setMinDeposit(uint256 _minDeposit) external onlyOwner beforeEnd whenReserving {
        require(_minDeposit > 0 && _minDeposit < minDeposit, "_minDeposit not in (0, minDeposit)");

        minDeposit = _minDeposit;

        emit LogMinDepositChanged(minDeposit);
    }

    /**
     * Change the maximum number of accepted RC subscribers. New value shall be at least equal to the current
     * number of subscribers.
     * @param _maxSubscribers The maximum number of subscribers
     */
    function setMaxSubscribers(uint256 _maxSubscribers) external onlyOwner beforeEnd whenReserving {
        require(_maxSubscribers > 0 && _maxSubscribers >= subscribers.length, "invalid _maxSubscribers");

        maxSubscribers = _maxSubscribers;

        emit LogMaxSubscribersChanged(maxSubscribers);
    }

    /**
     * Change the ICO crowdsale address.
     * @param _crowdsale The ICO crowdsale address
     */
    function setCrowdsaleAddress(address _crowdsale) external onlyOwner whenReserving {
        require(_crowdsale != address(0), "_crowdsale is 0");

        crowdsale = TokenSale(_crowdsale);

        emit LogCrowdsaleAddressChanged(_crowdsale);
    }

    /**
     * Change the maximum whitelist length. New value shall satisfy the #isAllowedWhitelist conditions.
     * @param _maxWhitelistLength The maximum whitelist length
     */
    function setMaxWhitelistLength(uint256 _maxWhitelistLength) external onlyOwner beforeEnd whenReserving {
        setMaxWhitelistLengthInternal(_maxWhitelistLength);
    }

    /**
     * Change the whitelist threshold balance. New value shall satisfy the #isAllowedWhitelist conditions.
     * @param _whitelistThreshold The threshold balance (in wei) above which whitelisting is required to invest
     */
    function setWhitelistThresholdBalance(uint256 _whitelistThreshold) external onlyOwner beforeEnd whenReserving {
        setWhitelistThresholdBalanceInternal(_whitelistThreshold);
    }

    /**
     * Add the subscriber to the whitelist.
     * @param _subscriber The subscriber to add to the whitelist.
     */
    function addToWhitelist(address _subscriber) external onlyOwner beforeEnd whenReserving {
        addToWhitelistInternal(_subscriber);
    }

    /**
     * Removed the subscriber from the whitelist.
     * @param _subscriber The subscriber to remove from the whitelist.
     */
    function removeFromWhitelist(address _subscriber) external onlyOwner beforeEnd whenReserving {
        removeFromWhitelistInternal(_subscriber, balances[_subscriber]);
    }

    /**
     * Abort the contract before the ICO start time. An administrator is allowed to use this 'kill switch'
     * to deactivate any contract function except the investor refunding.
     */
    function abort() external onlyAdministrator whenReserving {
        aborted = true;

        emit LogAbort(msg.sender);
    }

    /**
     * Let the caller invest its money before the ICO start time.
     */
    function invest() external payable whenReserving {
        deposit(msg.sender, msg.value);
    }

    /**
     * Execute a batch of multiple bids into the ICO crowdsale.
     * @param _from The subscriber index, included, from which the batch starts.
     * @param _to The subscriber index, excluded, to which the batch ends.
     */
    function buy(uint256 _from, uint256 _to) external onlyAdministrator whenReserving {
        require(_from < _to, "_from >= _to");
        require(crowdsale != address(0), "crowdsale not set");
        require(subscribers.length > 0, "subscribers size is 0");
        require(hasEnded(), "not ended");

        uint to = _to > subscribers.length ? subscribers.length : _to;

        for (uint256 i=_from; i<to; i++) {
            address subscriber = subscribers[i];

            uint256 subscriberBalance = balances[subscriber];

            if (subscriberBalance > 0) {
                balances[subscriber] = 0;

                crowdsale.buyTokens.value(subscriberBalance)(subscriber);
            }
        }

        emit LogBuy(msg.sender, _from, _to);
    }

    /**
     * Refund the invested money to the caller after the RC termination.
     */
    function refund() external whenAborted {
        // Read the calling subscriber balance once
        uint256 subscriberBalance = balances[msg.sender];

        // Withdraw is allowed IFF the calling subscriber has not zero balance
        require(subscriberBalance > 0, "caller balance is 0");

        // Withdraw is allowed IFF the contract has some token balance
        require(raisedFunds > 0, "token balance is 0");

        // Safely decrease the total balance
        raisedFunds = raisedFunds.sub(subscriberBalance);

        // Clear the subscriber balance before transfer to prevent re-entrant attacks
        balances[msg.sender] = 0;

        emit LogRefund(msg.sender, subscriberBalance, raisedFunds);

        // Transfer the balance back to the calling subscriber or throws on error
        msg.sender.transfer(subscriberBalance);
    }

    /**
     * Allow investing by just sending money to the contract address.
     */
    function () external payable whenReserving {
        deposit(msg.sender, msg.value);
    }

    /**
     * Deposit the money amount for the beneficiary when RC is running.
     */
    function deposit(address beneficiary, uint256 amount) internal {
        // Deposit is allowed IFF the RC is currently running
        require(startBlock <= block.number && block.number <= endBlock, "not open");

        uint256 newRaisedFunds = raisedFunds.add(amount);

        // Deposit is allowed IFF the contract balance will not reach its maximum cap
        require(newRaisedFunds <= maxCap, "over max cap");

        uint256 currentBalance = balances[beneficiary];
        uint256 finalBalance = currentBalance.add(amount);

        // Deposit is allowed IFF investor deposit shall be at least equal to the minimum deposit threshold
        require(finalBalance >= minDeposit, "deposit < min deposit");

        // Balances over whitelist threshold are allowed IFF the sender is in whitelist
        require(isAllowedBalance(beneficiary, finalBalance), "balance not allowed");

        // Increase the subscriber count if sender does not have a balance yet
        if (currentBalance == 0) {
            // New subscribers are allowed IFF the contract has not yet the max number of subscribers
            require(subscribers.length < maxSubscribers, "max subscribers reached");

            subscribers.push(beneficiary);
        }

        // Add the received amount to the subscriber balance
        balances[beneficiary] = finalBalance;

        raisedFunds = newRaisedFunds;

        emit LogDeposit(beneficiary, amount, finalBalance, newRaisedFunds);
    }
}
/**
 * A multiple-bid Reservation Contract (RC) for early deposit collection and manual token bid during
 * the Initial Coin Offering (ICO) crowdsale events.
 * The RC implements the following spec:
 * - investors allowed to simply send ethers to the RC address
 * - investors allowed to get refunded after ICO event if RC failed
 * - multiple bids using investor addresses performed by owner or authorized administator
 * - maximum cap on the total balance
 * - minimum threshold on each subscriber balance
 * - maximum number of subscribers
 * - optional whitelist with max deposit threshold for non-whitelisted subscribers
 * - kill switch callable by owner or authorized administator
 * - withdraw pattern for refunding
 * Just the RC owner or an authorized administator is allowed to shutdown the lifecycle halting the
 * RC; no bounties are provided.
 */
function buy(uint256 _from, uint256 _to) external onlyAdministrator whenReserving {
    require(_from < _to, "_from >= _to");
    require(crowdsale != address(0), "crowdsale not set");
    require(subscribers.length > 0, "subscribers size is 0");
    require(hasEnded(), "not ended");

    uint to = _to > subscribers.length ? subscribers.length : _to;

    for (uint256 i=_from; i<to; i++) {
        address subscriber = subscribers[i];

        uint256 subscriberBalance = balances[subscriber];

        if (subscriberBalance > 0) {
            balances[subscriber] = 0;

            crowdsale.buyTokens.value(subscriberBalance)(subscriber);
        }
    }

    emit LogBuy(msg.sender, _from, _to);
}

/**
 * Execute a batch of multiple bids into the ICO crowdsale.
 * @param _from The subscriber index, included, from which the batch starts.
 * @param _to The subscriber index, excluded, to which the batch ends.
 */