contract HbeCrowdsale is CanReclaimToken, CappedCrowdsale, MintedCrowdsale, WhitelistedCrowdsale, FinalizableCrowdsale, Pausable {
    /*** PRE-DEPLOYMENT CONFIGURED CONSTANTS */
    address public constant ETH_WALLET = 0x9E35Ee118D9B305F27AE1234BF5c035c1860989C;
    address public constant TEAM_WALLET = 0x992CEad41b885Dc90Ef82673c3c211Efa1Ef1AE2;
    uint256 public constant START_EASTER_BONUS = 1555668000; // Friday, 19 April 2019 12:00:00 GMT+02:00
    uint256 public constant END_EASTER_BONUS = 1555970399;   // Monday, 22 April 2019 23:59:59 GMT+02:00
    /*** CONSTANTS ***/
    uint256 public constant ICO_HARD_CAP = 22e8;             // 2,200,000,000 tokens, 0 decimals spec v1.7
    uint256 public constant CHF_HBE_RATE = 0.0143 * 1e4;    // 0.0143 (.10/7) CHF per HBE Token
    uint256 public constant TEAM_HBE_AMOUNT = 200e6;        // spec v1.7 200,000,000 team tokens
    uint256 public constant FOUR = 4;            // 25%
    uint256 public constant TWO = 2;             // 50%
    uint256 public constant HUNDRED = 100;
    uint256 public constant ONE_YEAR = 365 days;
    uint256 public constant BONUS_DURATION = 14 days;   // two weeks
    uint256 public constant BONUS_1 = 15;   // set 1 - 15% bonus
    uint256 public constant BONUS_2 = 10;   // set 2 and Easter Bonus - 10% bonus
    uint256 public constant BONUS_3 = 5;    // set 3 - 5% bonus
    uint256 public constant PRECISION = 1e6; // precision to account for none decimals

    /*** VARIABLES ***/
    // marks team allocation as minted
    bool public isTeamTokensMinted;
    address[3] public teamTokensLocked;

    // allow managers to whitelist and confirm contributions by manager accounts
    // managers can be set and altered by owner, multiple manager accounts are possible
    mapping(address => bool) public isManager;

    uint256 public tokensMinted;    // total token supply that has been minted and sold. does not include team tokens
    uint256 public rateDecimals;    // # of decimals that the CHF/ETH rate came in as

    /*** EVENTS  ***/
    event ChangedManager(address indexed manager, bool active);
    event NonEthTokenPurchase(uint256 investmentType, address indexed beneficiary, uint256 tokenAmount);
    event RefundAmount(address indexed beneficiary, uint256 refundAmount);
    event UpdatedFiatRate(uint256 fiatRate, uint256 rateDecimals);

    /*** MODIFIERS ***/
    modifier onlyManager() {
        require(isManager[msg.sender], "not manager");
        _;
    }

    modifier onlyValidAddress(address _address) {
        require(_address != address(0), "invalid address");
        _;
    }

    modifier onlyNoneZero(address _to, uint256 _amount) {
        require(_to != address(0), "invalid address");
        require(_amount > 0, "invalid amount");
        _;
    }

    /**
     * @dev constructor Deploy HBE Token Crowdsale
     * @param _startTime uint256 Start time of the crowdsale
     * @param _endTime uint256 End time of the crowdsale
     * @param _token ERC20 token address
     * @param _rate current CHF per ETH rate
     * @param _rateDecimals the # of decimals contained in the _rate variable
     */
    constructor(
        uint256 _startTime,
        uint256 _endTime,
        address _token,
        uint256 _rate,
        uint256 _rateDecimals
        )
        public
        Crowdsale(_rate, ETH_WALLET, ERC20(_token))
        TimedCrowdsale(_startTime, _endTime)
        CappedCrowdsale(ICO_HARD_CAP) {
            setManager(msg.sender, true);
            _updateRate(_rate, _rateDecimals);
        }

    /**
     * @dev Allow manager to update the exchange rate when necessary.
     * @param _rate uint256 current CHF per ETH rate
     * @param _rateDecimals the # of decimals contained in the _rate variable
     */
    function updateRate(uint256 _rate, uint256 _rateDecimals) external onlyManager {
        _updateRate(_rate, _rateDecimals);
    }

    /**
    * @dev create 3 token lockup contracts for X years to be released to the TEAM_WALLET
    */
    function mintTeamTokens() external onlyManager {
        require(!isTeamTokensMinted, "team tokens already minted");

        isTeamTokensMinted = true;

        TokenTimelock team1 = new TokenTimelock(ERC20Basic(token), TEAM_WALLET, openingTime.add(ONE_YEAR));
        TokenTimelock team2 = new TokenTimelock(ERC20Basic(token), TEAM_WALLET, openingTime.add(2 * ONE_YEAR));
        TokenTimelock team3 = new TokenTimelock(ERC20Basic(token), TEAM_WALLET, openingTime.add(3 * ONE_YEAR));

        teamTokensLocked[0] = address(team1);
        teamTokensLocked[1] = address(team2);
        teamTokensLocked[2] = address(team3);

        _deliverTokens(address(team1), TEAM_HBE_AMOUNT.div(FOUR));
        _deliverTokens(address(team2), TEAM_HBE_AMOUNT.div(FOUR));
        _deliverTokens(address(team3), TEAM_HBE_AMOUNT.div(TWO));
    }

    /**
    * @dev onlyManager allowed to handle batches of non-ETH investments
    * @param _investmentTypes uint256[] array of ids to identify investment types IE: BTC, CHF, EUR, etc...
    * @param _beneficiaries address[]
    * @param _amounts uint256[]
    */
    function batchNonEthPurchase(uint256[] _investmentTypes, address[] _beneficiaries, uint256[] _amounts) external {
        require(_beneficiaries.length == _amounts.length && _investmentTypes.length == _amounts.length, "length !=");

        for (uint256 i; i < _beneficiaries.length; i = i.add(1)) {
            nonEthPurchase(_investmentTypes[i], _beneficiaries[i], _amounts[i]);
        }
    }

    /**
    * @dev return the array of 3 token lock contracts for the HBE Team
    */
    function getTeamLockedContracts() external view returns (address[3]) {
        return teamTokensLocked;
    }

    /** OVERRIDE
    * @dev low level token purchase
    * @param _beneficiary Address performing the token purchase
    */
    function buyTokens(address _beneficiary) public payable {
        uint256 weiAmount = msg.value;

        _preValidatePurchase(_beneficiary, weiAmount);

        // calculate token amount to be created
        uint256 tokens = _getTokenAmount(weiAmount);

        // calculate a wei refund, if any, since decimal place is 0
        // update weiAmount if refund is > 0
        weiAmount = weiAmount.sub(refundLeftOverWei(weiAmount, tokens));

        // calculate bonus, if in bonus time period(s)
        tokens = tokens.add(_calcBonusAmount(tokens));

        // update state
        weiRaised = weiRaised.add(weiAmount);
        //push to investments array
        _processPurchase(_beneficiary, tokens);
        // throw event
        emit TokenPurchase(msg.sender, _beneficiary, weiAmount, tokens);

        // forward wei to the wallet
        _forwardFunds(weiAmount);
    }

    /** OVERRIDE - change to tokensMinted from weiRaised
    * @dev Checks whether the cap has been reached.
    * only active if a cap has been set
    * @return Whether the cap was reached
    */
    function capReached() public view returns (bool) {
        return tokensMinted >= cap;
    }

    /**
     * @dev Set / alter manager / whitelister "account". This can be done from owner only
     * @param _manager address address of the manager to create/alter
     * @param _active bool flag that shows if the manager account is active
     */
    function setManager(address _manager, bool _active) public onlyOwner onlyValidAddress(_manager) {
        isManager[_manager] = _active;
        emit ChangedManager(_manager, _active);
    }

    /** OVERRIDE
    * @dev add an address to the whitelist
    * @param _address address
    * @return true if the address was added to the whitelist, false if the address was already in the whitelist
    */
    function addAddressToWhitelist(address _address)
        public
        onlyManager
    {
        addRole(_address, ROLE_WHITELISTED);
    }

    /** OVERRIDE
    * @dev remove an address from the whitelist
    * @param _address address
    * @return true if the address was removed from the whitelist,
    * false if the address wasn't in the whitelist in the first place
    */
    function removeAddressFromWhitelist(address _address)
        public
        onlyManager
    {
        removeRole(_address, ROLE_WHITELISTED);
    }

    /** OVERRIDE
    * @dev remove addresses from the whitelist
    * @param _addresses addresses
    * @return true if at least one address was removed from the whitelist,
    * false if all addresses weren't in the whitelist in the first place
    */
    function removeAddressesFromWhitelist(address[] _addresses)
        public
        onlyManager
    {
        for (uint256 i = 0; i < _addresses.length; i++) {
            removeAddressFromWhitelist(_addresses[i]);
        }
    }

    /** OVERRIDE
    * @dev add addresses to the whitelist
    * @param _addresses addresses
    * @return true if at least one address was added to the whitelist,
    * false if all addresses were already in the whitelist
    */
    function addAddressesToWhitelist(address[] _addresses)
        public
        onlyManager
    {
        for (uint256 i = 0; i < _addresses.length; i++) {
            addAddressToWhitelist(_addresses[i]);
        }
    }

    /**
    * @dev onlyManager allowed to allocate non-ETH investments during the crowdsale
    * @param _investmentType uint256
    * @param _beneficiary address
    * @param _tokenAmount uint256
    */
    function nonEthPurchase(uint256 _investmentType, address _beneficiary, uint256 _tokenAmount) public
        onlyManager
        onlyWhileOpen
        onlyNoneZero(_beneficiary, _tokenAmount)
    {
        _processPurchase(_beneficiary, _tokenAmount);
        emit NonEthTokenPurchase(_investmentType, _beneficiary, _tokenAmount);
    }

    /** OVERRIDE
    * @dev called by the manager to pause, triggers stopped state
    */
    function pause() public onlyManager whenNotPaused onlyWhileOpen {
        paused = true;
        emit Pause();
    }

    /** OVERRIDE
    * @dev called by the manager to unpause, returns to normal state
    */
    function unpause() public onlyManager whenPaused {
        paused = false;
        emit Unpause();
    }

    /** OVERRIDE
    * @dev onlyManager allows tokens to be tradeable transfers HBE Token ownership back to owner
    */
    function finalize() public onlyManager {
        Pausable(address(token)).unpause();
        Ownable(address(token)).transferOwnership(owner);

        super.finalize();
    }

    /*** INTERNAL/PRIVATE FUNCTIONS ***/
    /** OVERRIDE - do not call super.METHOD
    * @dev Validation of an incoming purchase. Use require statements 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
        onlyWhileOpen
        whenNotPaused
        onlyIfWhitelisted(_beneficiary) {
            require(_weiAmount != 0, "invalid amount");
            require(!capReached(), "cap has been reached");
        }

    /** OVERRIDE
    * @dev Executed when a purchase has been validated and is ready to be executed. Not necessarily emits/sends tokens.
    * @param _beneficiary Address receiving the tokens
    * @param _tokenAmount Number of tokens to be purchased
    */
    function _processPurchase(address _beneficiary, uint256 _tokenAmount) internal {
        tokensMinted = tokensMinted.add(_tokenAmount);
        // respect the token cap
        require(tokensMinted <= cap, "tokensMinted > cap");
        _deliverTokens(_beneficiary, _tokenAmount);
    }

    /** OVERRIDE
    * @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) {
        return _weiAmount.mul(rate).div(rateDecimals).div(1e18).div(PRECISION);
    }

    /**
    * @dev calculate the bonus amount pending on time
    */
    function _calcBonusAmount(uint256 _tokenAmount) internal view returns (uint256) {
        uint256 currentBonus;

        /* solhint-disable */
        if (block.timestamp < openingTime.add(BONUS_DURATION)) {
            currentBonus = BONUS_1;
        } else if (block.timestamp < openingTime.add(BONUS_DURATION.mul(2))) {
            currentBonus = BONUS_2;
        } else if (block.timestamp < openingTime.add(BONUS_DURATION.mul(3))) {
            currentBonus = BONUS_3;
        } else if (block.timestamp >= START_EASTER_BONUS && block.timestamp < END_EASTER_BONUS) {
            currentBonus = BONUS_2;
        }
        /* solhint-enable */

        return _tokenAmount.mul(currentBonus).div(HUNDRED);
    }

    /**
     * @dev calculate wei refund to investor, if any. This handles rounding errors
     * which are important here due to the 0 decimals
     * @param _weiReceived uint256 wei received from the investor
     * @param _tokenAmount uint256 HBE tokens minted for investor
     */
    function refundLeftOverWei(uint256 _weiReceived, uint256 _tokenAmount) internal returns (uint256 refundAmount) {
        uint256 weiInvested = _tokenAmount.mul(1e18).mul(PRECISION).mul(rateDecimals).div(rate);

        if (weiInvested < _weiReceived) {
            refundAmount = _weiReceived.sub(weiInvested);
        }

        if (refundAmount > 0) {
            msg.sender.transfer(refundAmount);
            emit RefundAmount(msg.sender, refundAmount);
        }

        return refundAmount;
    }

    /** OVERRIDE
    * @dev Determines how ETH is stored/forwarded on purchases.
    * @param _weiAmount uint256
    */
    function _forwardFunds(uint256 _weiAmount) internal {
        wallet.transfer(_weiAmount);
    }

    /**
     * @dev Allow manager to update the exchange rate when necessary.
     * @param _rate uint256
     * @param _rateDecimals the # of decimals contained in the _rate variable
     */
    function _updateRate(uint256 _rate, uint256 _rateDecimals) internal {
        require(_rateDecimals <= 18);

        rateDecimals = 10**_rateDecimals;
        rate = (_rate.mul(1e4).mul(PRECISION).div(CHF_HBE_RATE));

        emit UpdatedFiatRate(_rate, _rateDecimals);
    }
}
// solhint-disable-next-line
function getTeamLockedContracts() external view returns (address[3]) {
    return teamTokensLocked;
}

/**
 * @dev return the array of 3 token lock contracts for the HBE Team
 */