contract Presale is Whitelistable, Pausable {
    using AddressUtils for address;
    using SafeMath for uint256;

    event LogCreated(
        address caller,
        uint256 indexed startBlock,
        uint256 indexed endBlock,
        uint256 minDeposit,
        address wallet,
        address indexed providerWallet,
        uint256 maxWhitelistLength,
        uint256 whitelistThreshold
    );
    event LogMinDepositChanged(address indexed caller, uint256 indexed minDeposit);
    event LogInvestmentReceived(
        address indexed caller,
        address indexed beneficiary,
        uint256 indexed amount,
        uint256 netAmount
    );
    event LogPresaleTokenChanged(
        address indexed caller,
        address indexed presaleToken,
        uint256 indexed rate
    );

    // The start and end block where investments are allowed (both inclusive)
    uint256 public startBlock;
    uint256 public endBlock;

    // Address where funds are collected
    address public wallet;

    // Presale minimum deposit in wei
    uint256 public minDeposit;

    // Presale balances (expressed in wei) deposited by each subscriber
    mapping (address => uint256) public balanceOf;
    
    // Amount of raised money in wei
    uint256 public raisedFunds;

    // Amount of service provider fees in wei
    uint256 public providerFees;

    // Address where service provider fees are collected
    address public providerWallet;

    // Two fee thresholds separating the raised money into three partitions
    uint256 public feeThreshold1;
    uint256 public feeThreshold2;

    // Three percentage levels for fee calculation in each partition
    uint256 public lowFeePercentage;
    uint256 public mediumFeePercentage;
    uint256 public highFeePercentage;

    // Optional ERC20 presale token (0 means no presale token)
    MintableToken public presaleToken;

    // How many ERC20 presale token units a buyer gets per wei (0 means no presale token)
    uint256 public rate;

    constructor(
        uint256 _startBlock,
        uint256 _endBlock,
        uint256 _minDeposit,
        address _wallet,
        address _providerWallet,
        uint256 _maxWhitelistLength,
        uint256 _whitelistThreshold,
        uint256 _feeThreshold1,
        uint256 _feeThreshold2,
        uint256 _lowFeePercentage,
        uint256 _mediumFeePercentage,
        uint256 _highFeePercentage
    )
    Whitelistable(_maxWhitelistLength, _whitelistThreshold)
    public
    {
        require(_startBlock >= block.number, "_startBlock is lower than current block number");
        require(_endBlock >= _startBlock, "_endBlock is lower than _startBlock");
        require(_minDeposit > 0, "_minDeposit is zero");
        require(_wallet != address(0) && !_wallet.isContract(), "_wallet is zero or contract");
        require(!_providerWallet.isContract(), "_providerWallet is contract");
        require(_feeThreshold2 >= _feeThreshold1, "_feeThreshold2 is lower than _feeThreshold1");
        require(0 <= _lowFeePercentage && _lowFeePercentage <= 100, "_lowFeePercentage not in range [0, 100]");
        require(0 <= _mediumFeePercentage && _mediumFeePercentage <= 100, "_mediumFeePercentage not in range [0, 100]");
        require(0 <= _highFeePercentage && _highFeePercentage <= 100, "_highFeePercentage not in range [0, 100]");

        startBlock = _startBlock;
        endBlock = _endBlock;
        minDeposit = _minDeposit;
        wallet = _wallet;
        providerWallet = _providerWallet;
        feeThreshold1 = _feeThreshold1;
        feeThreshold2 = _feeThreshold2;
        lowFeePercentage = _lowFeePercentage;
        mediumFeePercentage = _mediumFeePercentage;
        highFeePercentage = _highFeePercentage;

        emit LogCreated(
            msg.sender,
            _startBlock,
            _endBlock,
            _minDeposit,
            _wallet,
            _providerWallet,
            _maxWhitelistLength,
            _whitelistThreshold
        );
    }

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

    // @return true if presale event has ended
    function hasEnded() public view returns (bool ended) {
        return block.number > endBlock;
    }

    // @return The current fee percentage based on raised funds
    function currentFeePercentage() public view returns (uint256 feePercentage) {
        return raisedFunds < feeThreshold1 ? lowFeePercentage :
            raisedFunds < feeThreshold2 ? mediumFeePercentage : highFeePercentage;
    }

    /**
     * Change the minimum deposit for each subscriber. New value shall be lower than previous.
     * @param _minDeposit The minimum deposit for each subscriber, expressed in wei
     */
    function setMinDeposit(uint256 _minDeposit) external onlyOwner {
        require(0 < _minDeposit && _minDeposit < minDeposit, "_minDeposit not in range [0, minDeposit]");
        require(!hasEnded(), "presale has ended");

        minDeposit = _minDeposit;

        emit LogMinDepositChanged(msg.sender, _minDeposit);
    }

    /**
     * Change the maximum whitelist length. New value shall satisfy the #isAllowedWhitelist conditions.
     * @param _maxWhitelistLength The maximum whitelist length
     */
    function setMaxWhitelistLength(uint256 _maxWhitelistLength) external onlyOwner {
        require(!hasEnded(), "presale has ended");
        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 {
        require(!hasEnded(), "presale has ended");
        setWhitelistThresholdBalanceInternal(_whitelistThreshold);
    }

    /**
     * Add the subscriber to the whitelist.
     * @param _subscriber The subscriber to add to the whitelist.
     */
    function addToWhitelist(address _subscriber) external onlyOwner {
        require(!hasEnded(), "presale has ended");
        addToWhitelistInternal(_subscriber);
    }

    /**
     * Removed the subscriber from the whitelist.
     * @param _subscriber The subscriber to remove from the whitelist.
     */
    function removeFromWhitelist(address _subscriber) external onlyOwner {
        require(!hasEnded(), "presale has ended");
        removeFromWhitelistInternal(_subscriber, balanceOf[_subscriber]);
    }

    /**
     * Set the ERC20 presale token address and conversion rate.
     * @param _presaleToken The ERC20 presale token.
     * @param _rate How many ERC20 presale token units a buyer gets per wei.
     */
    function setPresaleToken(MintableToken _presaleToken, uint256 _rate) external onlyOwner {
        require(_presaleToken != presaleToken || _rate != rate, "both _presaleToken and _rate equal to current ones");
        require(!hasEnded(), "presale has ended");

        presaleToken = _presaleToken;
        rate = _rate;

        emit LogPresaleTokenChanged(msg.sender, _presaleToken, _rate);
    }

    function isAllowedBalance(address _beneficiary, uint256 _balance) public view returns (bool isReallyAllowed) {
        bool hasMinimumBalance = _balance >= minDeposit;
        return hasMinimumBalance && super.isAllowedBalance(_beneficiary, _balance);
    }

    function isValidInvestment(address _beneficiary, uint256 _amount) public view returns (bool isValid) {
        bool withinPeriod = startBlock <= block.number && block.number <= endBlock;
        bool nonZeroPurchase = _amount != 0;
        bool isAllowedAmount = isAllowedBalance(_beneficiary, balanceOf[_beneficiary].add(_amount));

        return withinPeriod && nonZeroPurchase && isAllowedAmount;
    }

    function invest(address _beneficiary) public payable whenNotPaused {
        require(_beneficiary != address(0), "_beneficiary is zero");
        require(_beneficiary != wallet, "_beneficiary is equal to wallet");
        require(_beneficiary != providerWallet, "_beneficiary is equal to providerWallet");
        require(isValidInvestment(_beneficiary, msg.value), "forbidden investment for _beneficiary");

        balanceOf[_beneficiary] = balanceOf[_beneficiary].add(msg.value);
        raisedFunds = raisedFunds.add(msg.value);

        // Optionally distribute presale token to buyer, if configured
        if (presaleToken != address(0) && rate != 0) {
            uint256 tokenAmount = msg.value.mul(rate);
            presaleToken.mint(_beneficiary, tokenAmount);
        }

        if (providerWallet == 0) {
            wallet.transfer(msg.value);

            emit LogInvestmentReceived(msg.sender, _beneficiary, msg.value, msg.value);
        }
        else {
            uint256 feePercentage = currentFeePercentage();
            uint256 fees = msg.value.mul(feePercentage).div(100);
            uint256 netAmount = msg.value.sub(fees);

            providerFees = providerFees.add(fees);

            providerWallet.transfer(fees);
            wallet.transfer(netAmount);

            emit LogInvestmentReceived(msg.sender, _beneficiary, msg.value, netAmount);
        }
    }

    function () external payable whenNotPaused {
        invest(msg.sender);
    }
}
/**
 * @title Presale
 * @dev A simple Presale Contract (PsC) for deposit collection during pre-sale events.
 */
function currentFeePercentage() public view returns (uint256 feePercentage) {
    return raisedFunds < feeThreshold1 ? lowFeePercentage :
        raisedFunds < feeThreshold2 ? mediumFeePercentage : highFeePercentage;
}

// @return The current fee percentage based on raised funds