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 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 to populate website with status of the sale 