contract Crowdsale is Pausable, TokenVesting {

    using SafeMath for uint;

    address public multisigETH; // Multisig contract that will receive the ETH
    address public commissionAddress;  // address to deposit commissions
    uint public tokensForTeam; // tokens for the team
    uint public ethReceivedPresale; // Number of ETH received in presale
    uint public ethReceivedMain; // Number of ETH received in main sale
    uint public totalTokensSent; // Number of tokens sent to ETH contributors
    uint public tokensSentMain;
    uint public tokensSentPresale;       
    uint public tokensSentDev;         
    uint public startBlock; // Crowdsale start block
    uint public endBlock; // Crowdsale end block
    uint public maxCap; // Maximum number of token to sell
    uint public minCap; // Minimum number of ETH to raise
    uint public minContributionMainSale; // Minimum amount to contribute in main sale
    uint public minContributionPresale; // Minimum amount to contribut in presale
    uint public maxContribution;
    bool public crowdsaleClosed; // Is crowdsale still on going
    uint public tokenPriceWei;
    uint public refundCount;
    uint public totalRefunded;
    uint public campaignDurationDays; // campaign duration in days 
    uint public firstPeriod; 
    uint public secondPeriod; 
    uint public thirdPeriod; 
    uint public firstBonus; 
    uint public secondBonus;
    uint public thirdBonus;
    uint public multiplier;
    uint public status;    
    Step public currentStep;  // To allow for controlled steps of the campaign 
   
    // Looping through Backer
    //mapping(address => Backer) public backers; //backer list
    address[] public holdersIndex;   // to be able to itarate through backers when distributing the tokens
    address[] public devIndex;   // to be able to itarate through backers when distributing the tokens

    // @notice to set and determine steps of crowdsale
    enum Step {      
        FundingPreSale,     // presale mode
        FundingMainSale,  // public mode
        Refunding  // in case campaign failed during this step contributors will be able to receive refunds
    }

    // @notice to verify if action is not performed out of the campaing range
    modifier respectTimeFrame() {
        if ((block.number < startBlock) || (block.number > endBlock)) 
            revert();
        _;
    }

    modifier minCapNotReached() {
        if (totalTokensSent >= minCap) 
            revert();
        _;
    }

    // Events
    event LogReceivedETH(address indexed backer, uint amount, uint tokenAmount);
    event LogStarted(uint startBlockLog, uint endBlockLog);
    event LogFinalized(bool success);  
    event LogRefundETH(address indexed backer, uint amount);
    event LogStepAdvanced();
    event LogDevTokensAllocated(address indexed dev, uint amount);
    event LogNonVestedTokensSent(address indexed user, uint amount);

    // Crowdsale  {constructor}
    // @notice fired when contract is crated. Initilizes all constnat variables.
    function Crowdsale(uint _decimalPoints,
                        address _multisigETH,
                        uint _toekensForTeam, 
                        uint _minContributionPresale,
                        uint _minContributionMainSale,
                        uint _maxContribution,                        
                        uint _maxCap, 
                        uint _minCap, 
                        uint _tokenPriceWei, 
                        uint _campaignDurationDays,
                        uint _firstPeriod, 
                        uint _secondPeriod, 
                        uint _thirdPeriod, 
                        uint _firstBonus, 
                        uint _secondBonus,
                        uint _thirdBonus) public {
        multiplier = 10**_decimalPoints;
        multisigETH = _multisigETH; 
        tokensForTeam = _toekensForTeam * multiplier; 
        minContributionPresale = _minContributionPresale; 
        minContributionMainSale = _minContributionMainSale;
        maxContribution = _maxContribution;       
        maxCap = _maxCap * multiplier;       
        minCap = _minCap * multiplier;
        tokenPriceWei = _tokenPriceWei;
        campaignDurationDays = _campaignDurationDays;
        firstPeriod = _firstPeriod; 
        secondPeriod = _secondPeriod; 
        thirdPeriod = _thirdPeriod;
        firstBonus = _firstBonus;
        secondBonus = _secondBonus;
        thirdBonus = _thirdBonus;       
        //TODO replace this address below with correct address.
        commissionAddress = 0x326B5E9b8B2ebf415F9e91b42c7911279d296ea1;
        //commissionAddress = 0x853A3F142430658A32f75A0dc891b98BF4bDF5c1;
        currentStep = Step.FundingPreSale; 
    }

    // @notice to populate website with status of the sale 
    function returnWebsiteData() external view returns(uint, 
        uint, uint, uint, uint, uint, uint, uint, uint, uint, bool, bool, uint, Step) {
    
        return (startBlock, endBlock, numberOfBackers(), ethReceivedPresale + ethReceivedMain, maxCap, minCap, 
                totalTokensSent, tokenPriceWei, minContributionPresale, minContributionMainSale, 
                paused, crowdsaleClosed, token.decimals(), currentStep);
    }
    
    // @notice this function will determine status of crowdsale
    function determineStatus() external view returns (uint) {
       
        if (crowdsaleClosed)            // ICO finihsed
            return 1;   

        if (block.number < endBlock && totalTokensSent < maxCap - 100)   // ICO in progress
            return 2;            
    
        if (totalTokensSent < minCap && block.number > endBlock)      // ICO failed    
            return 3;            
    
        if (endBlock == 0)           // ICO hasn't been started yet 
            return 4;            
    
        return 0;         
    } 

    // {fallback function}
    // @notice It will call internal function which handels allocation of Ether and calculates tokens.
    function () public payable {    
             
        contribute(msg.sender);
    }

    // @notice to allow for contribution from interface
    function contributePublic() external payable {
        contribute(msg.sender);
    }

    // @notice set the step of the campaign from presale to public sale
    // contract is deployed in presale mode
    // WARNING: there is no way to go back
    function advanceStep() external onlyOwner() {
        currentStep = Step.FundingMainSale;
        LogStepAdvanced();
    }

    // @notice It will be called by owner to start the sale    
    function start() external onlyOwner() {
        startBlock = block.number;
        endBlock = startBlock + (4*60*24*campaignDurationDays); // assumption is that one block takes 15 sec. 
        crowdsaleClosed = false;
        LogStarted(startBlock, endBlock);
    }

    // @notice This function will finalize the sale.
    // It will only execute if predetermined sale time passed or all tokens are sold.
    function finalize() external onlyOwner() {

        require(!crowdsaleClosed);                       
        require(block.number >= endBlock || totalTokensSent > maxCap - 1000);
                    // - 1000 is used to allow closing of the campaing when contribution is near 
                    // finished as exact amount of maxCap might be not feasible e.g. you can't easily buy few tokens. 
                    // when min contribution is 0.1 Eth.  

        require(totalTokensSent >= minCap);
        crowdsaleClosed = true;
        
        // transfer commission portion to the platform
        commissionAddress.transfer(determineCommissions());         
        
        // transfer remaning funds to the campaign wallet
        multisigETH.transfer(this.balance);
        
        /*if (!token.transfer(owner, token.balanceOf(this))) 
            revert(); // transfer tokens to admin account  
            
        if (!token.burn(this, token.balanceOf(this))) 
            revert();  // burn all the tokens remaining in the contract   */
        token.unlock();    // release lock from transfering tokens. 

        LogFinalized(true);        
    }

    // @notice it will allow contributors to get refund in case campaign failed
    // @return {bool} true if successful
    function refund() external whenNotPaused returns (bool) {      
        
        uint totalEtherReceived = ethReceivedPresale + ethReceivedMain;

        require(totalEtherReceived < minCap);  // ensure that campaign failed
        require(this.balance > 0);  // contract will hold 0 ether at the end of campaign.
                                    // contract needs to be funded through fundContract() 
        TokenHolder storage backer = tokenHolders[msg.sender];

        require(backer.weiReceived > 0);  // ensure that user has sent contribution
        require(!backer.refunded);        // ensure that user hasn't been refunded yet

        backer.refunded = true;  // save refund status to true
        refundCount++;
        totalRefunded += backer.weiReceived;

        if (!token.burn(msg.sender, backer.tokensToSend)) // burn tokens
            revert();        
        msg.sender.transfer(backer.weiReceived);  // send back the contribution 
        LogRefundETH(msg.sender, backer.weiReceived);
        return true;
    }

    // @notice allocate tokens to dev/team/advisors
    // @param _dev {address} 
    // @param _amount {uint} amount of tokens
    function devAllocation(address _dev, uint _amount) external onlyOwner() returns (bool) {

        require(_dev != address(0));
        require(crowdsaleClosed); 
        require(totalTokensSent.add(_amount) <= token.totalSupply());
        devIndex.push(_dev);
        TokenHolder storage tokenHolder = tokenHolders[_dev];
        tokenHolder.tokensToSend = _amount;
        tokensSentDev += _amount;
        totalTokensSent += _amount;        
        LogDevTokensAllocated(_dev, _amount); // Register event
        return true;

    }

    // @notice Failsafe drain
    function drain(uint _amount) external onlyOwner() {
        owner.transfer(_amount);           
    }

    // @notice transfer tokens which are not subject to vesting
    // @param _recipient {addres}
    // @param _amont {uint} amount to transfer
    function transferTokens(address _recipient, uint _amount) external onlyOwner() returns (bool) {

        require(_recipient != address(0));
        if (!token.transfer(_recipient, _amount))
            revert();
        LogNonVestedTokensSent(_recipient, _amount);
    }

    // @notice determine amount of commissions for the platform    
    function determineCommissions() public view returns (uint) {
     
        if (this.balance <= 500 ether) {
            return (this.balance * 10)/100;
        }else if (this.balance <= 1000 ether) {
            return (this.balance * 8)/100;
        }else if (this.balance < 10000 ether) {
            return (this.balance * 6)/100;
        }else {
            return (this.balance * 6)/100;
        }
    }

    // @notice return number of contributors
    // @return  {uint} number of contributors
    function numberOfBackers() public view returns (uint) {
        return holdersIndex.length;
    }

    // @notice It will be called by fallback function whenever ether is sent to it
    // @param  _backer {address} address of beneficiary
    // @return res {bool} true if transaction was successful
    function contribute(address _backer) internal whenNotPaused respectTimeFrame returns(bool res) {

        //require(msg.value <= maxContribution);

        if (whiteList != address(0))  // if whitelist initialized verify member whitelist status
            require(whiteList.isWhiteListed(_backer));  // ensure that user is whitelisted
          
        uint tokensToSend = calculateNoOfTokensToSend(); // calculate number of tokens

        // Ensure that max cap hasn't been reached
        require(totalTokensSent + tokensToSend <= maxCap);
        
        TokenHolder storage backer = tokenHolders[_backer];

        if (backer.weiReceived == 0)
            holdersIndex.push(_backer);

        if (Step.FundingMainSale == currentStep) { // Update the total Ether received and tokens sent during public sale
            require(msg.value >= minContributionMainSale); // stop when required minimum is not met    
            ethReceivedMain = ethReceivedMain.add(msg.value);
            tokensSentMain += tokensToSend;
        }else {  
            require(msg.value >= minContributionPresale); // stop when required minimum is not met
            ethReceivedPresale = ethReceivedPresale.add(msg.value); 
            tokensSentPresale += tokensToSend;
        }  
       
        backer.tokensToSend += tokensToSend;
        backer.weiReceived = backer.weiReceived.add(msg.value);       
        totalTokensSent += tokensToSend;      
        
        // tokens are not transferrd to contributors during this phase
        // tokens will be transferred based on the vesting schedule, when contributor
        // calls release() function of this contract
        LogReceivedETH(_backer, msg.value, tokensToSend); // Register event
        return true;
    }

    // @notice This function will return number of tokens based on time intervals in the campaign
    function calculateNoOfTokensToSend() internal view returns (uint) {

        uint tokenAmount = msg.value.mul(multiplier) / tokenPriceWei;

        if (Step.FundingMainSale == currentStep) {
        
            if (block.number <= startBlock + firstPeriod) {  
                return  tokenAmount + tokenAmount.mul(firstBonus) / 100;
            }else if (block.number <= startBlock + secondPeriod) {
                return  tokenAmount + tokenAmount.mul(secondBonus) / 100; 
            }else if (block.number <= startBlock + thirdPeriod) { 
                return  tokenAmount + tokenAmount.mul(thirdBonus) / 100;        
            }else {              
                return  tokenAmount; 
            }
        }else 
            return  tokenAmount + tokenAmount.mul(presaleBonus) / 100;
    }  
}
// Crowdsale Smart Contract
// This smart contract collects ETH and in return sends  tokens to the Backers
function determineCommissions() public view returns (uint) {
     
    if (this.balance <= 500 ether) {
        return (this.balance * 10)/100;
    }else if (this.balance <= 1000 ether) {
        return (this.balance * 8)/100;
    }else if (this.balance < 10000 ether) {
        return (this.balance * 6)/100;
    }else {
        return (this.balance * 6)/100;
    }
}

// @notice determine amount of commissions for the platform    