contract NewBitNauticCrowdsale is Ownable, Pausable {
    using SafeMath for uint256;

    event TokenPurchase(address indexed purchaser, address indexed beneficiary, uint256 value, uint256 amount);

    uint256 public ICOStartTime = 1531267200; // 11 Jul 2018 00:00 GMT
    uint256 public ICOEndTime = 1537056000; // 16 Sep 2018 00:00 GMT

    uint256 public constant tokenBaseRate = 500; // 1 ETH = 500 BTNT

    bool public manualBonusActive = false;
    uint256 public manualBonus = 0;

    uint256 public constant crowdsaleSupply = 35000000 * 10 ** 18;
    uint256 public tokensSold = 0;

    uint256 public constant softCap = 2500000 * 10 ** 18;

    uint256 public teamSupply =     3000000 * 10 ** 18; // 6% of token cap
    uint256 public bountySupply =   2500000 * 10 ** 18; // 5% of token cap
    uint256 public reserveSupply =  5000000 * 10 ** 18; // 10% of token cap
    uint256 public advisorSupply =  2500000 * 10 ** 18; // 5% of token cap
    uint256 public founderSupply =  2000000 * 10 ** 18; // 4% of token cap

    // amount of tokens each address will receive at the end of the crowdsale
    mapping (address => uint256) public creditOf;

    // amount of ether invested by each address
    mapping (address => uint256) public weiInvestedBy;

    // refund vault used to hold funds while crowdsale is running
    RefundVault private vault;

    MintableToken public token;
    BitNauticWhitelist public whitelist;

    constructor(MintableToken _token, BitNauticWhitelist _whitelist, address _beneficiary) public {
        token = _token;
        whitelist = _whitelist;
        vault = new RefundVault(_beneficiary);
    }

    function() public payable {
        buyTokens(msg.sender);
    }

    function buyTokens(address beneficiary) whenNotPaused public payable {
        require(beneficiary != 0x0);
        require(validPurchase());

        // checks if the ether amount invested by the buyer is lower than his contribution cap
        require(SafeMath.add(weiInvestedBy[msg.sender], msg.value) <= whitelist.contributionCap(msg.sender));

        // compute the amount of tokens given the baseRate
        uint256 tokens = SafeMath.mul(msg.value, tokenBaseRate);
        // add the bonus tokens depending on current time
        tokens = tokens.add(SafeMath.mul(tokens, getCurrentBonus()).div(1000));

        // check hardcap
        require(SafeMath.add(tokensSold, tokens) <= crowdsaleSupply);

        // update total token sold counter
        tokensSold = SafeMath.add(tokensSold, tokens);

        // keep track of the token credit and ether invested by the buyer
        creditOf[beneficiary] = creditOf[beneficiary].add(tokens);
        weiInvestedBy[msg.sender] = SafeMath.add(weiInvestedBy[msg.sender], msg.value);

        emit TokenPurchase(msg.sender, beneficiary, msg.value, tokens);

        vault.deposit.value(msg.value)(msg.sender);
    }

    function privateSale(address beneficiary, uint256 tokenAmount) onlyOwner public {
        require(beneficiary != 0x0);
        require(SafeMath.add(tokensSold, tokenAmount) <= crowdsaleSupply); // check hardcap

        tokensSold = SafeMath.add(tokensSold, tokenAmount);

        assert(token.mint(beneficiary, tokenAmount));
    }

    // for payments in other currencies
    function offchainSale(address beneficiary, uint256 tokenAmount) onlyOwner public {
        require(beneficiary != 0x0);
        require(SafeMath.add(tokensSold, tokenAmount) <= crowdsaleSupply); // check hardcap

        tokensSold = SafeMath.add(tokensSold, tokenAmount);

        // keep track of the token credit of the buyer
        creditOf[beneficiary] = creditOf[beneficiary].add(tokenAmount);

        emit TokenPurchase(beneficiary, beneficiary, 0, tokenAmount);
    }

    // this function can be called by the contributor to claim his BTNT tokens at the end of the ICO
    function claimBitNauticTokens() public returns (bool) {
        return grantContributorTokens(msg.sender);
    }

    // if the ICO is finished and the goal has been reached, this function will be used to mint and transfer BTNT tokens to each contributor
    function grantContributorTokens(address contributor) public returns (bool) {
        require(creditOf[contributor] > 0);
        require(whitelist.AMLWhitelisted(contributor));
        require(now > ICOEndTime && tokensSold >= softCap);

        assert(token.mint(contributor, creditOf[contributor]));
        creditOf[contributor] = 0;

        return true;
    }

    // returns the token sale bonus permille depending on the current time
    function getCurrentBonus() public view returns (uint256) {
        if (manualBonusActive) return manualBonus;

        return Math.min(340, Math.max(100, (340 - (now - ICOStartTime) / (60 * 60 * 24) * 4)));
    }

    function setManualBonus(uint256 newBonus, bool isActive) onlyOwner public returns (bool) {
        manualBonus = newBonus;
        manualBonusActive = isActive;

        return true;
    }

    function setICOEndTime(uint256 newEndTime) onlyOwner public returns (bool) {
        ICOEndTime = newEndTime;

        return true;
    }

    function validPurchase() internal view returns (bool) {
        bool duringICO = ICOStartTime <= now && now <= ICOEndTime;
        bool minimumContribution = msg.value >= 0.05 ether;
        return duringICO && minimumContribution;
    }

    function hasEnded() public view returns (bool) {
        return now > ICOEndTime;
    }

    function unlockVault() onlyOwner public {
        if (tokensSold >= softCap) {
            vault.unlock();
        }
    }

    function withdraw(address beneficiary, uint256 amount) onlyOwner public {
        vault.withdraw(beneficiary, amount);
    }

    bool isFinalized = false;
    function finalizeCrowdsale() onlyOwner public {
        require(!isFinalized);
        require(now > ICOEndTime);

        if (tokensSold < softCap) {
            vault.enableRefunds();
        }

        isFinalized = true;
    }

    // if crowdsale is unsuccessful, investors can claim refunds here
    function claimRefund() public {
        require(isFinalized);
        require(tokensSold < softCap);

        vault.refund(msg.sender);
    }

    function transferTokenOwnership(address newTokenOwner) onlyOwner public returns (bool) {
        return token.transferOwnership(newTokenOwner);
    }

    function grantBountyTokens(address beneficiary) onlyOwner public {
        require(bountySupply > 0);

        token.mint(beneficiary, bountySupply);
        bountySupply = 0;
    }

    function grantReserveTokens(address beneficiary) onlyOwner public {
        require(reserveSupply > 0);

        token.mint(beneficiary, reserveSupply);
        reserveSupply = 0;
    }

    function grantAdvisorsTokens(address beneficiary) onlyOwner public {
        require(advisorSupply > 0);

        token.mint(beneficiary, advisorSupply);
        advisorSupply = 0;
    }

    function grantFoundersTokens(address beneficiary) onlyOwner public {
        require(founderSupply > 0);

        token.mint(beneficiary, founderSupply);
        founderSupply = 0;
    }

    function grantTeamTokens(address beneficiary) onlyOwner public {
        require(teamSupply > 0);

        token.mint(beneficiary, teamSupply);
        teamSupply = 0;
    }
}
function claimRefund() public {
    require(isFinalized);
    require(tokensSold < softCap);

    vault.refund(msg.sender);
}

// if crowdsale is unsuccessful, investors can claim refunds here