contract Crowdsale is LiquidToken {
    
    using SafeMath for uint256;

    address public ETHCollector;
    uint256 public tokenCost = 140; //140 cents
    uint256 public ETH_USD; //in cents
    uint256 public saleStartDate;
    uint256 public saleEndDate;
    uint256 public softCap;
    uint256 public hardCap; 
    uint256 public minContribution = 28000; //280$ =280,00 cent
    uint256 public tokensSold;
    uint256 public weiCollected;
    //Number of investors who have received refund
    uint256 public countInvestorsRefunded;
    uint256 public countTotalInvestors;
    // Whether the Crowdsale is paused.
    bool public paused;
    bool public start;
    bool public stop;
    //Set status of refund
    bool public refundStatus;
    
    //Structure to store token sent and wei received by the buyer of tokens
    struct Investor {
        uint256 investorID;
        uint256 weiReceived;
        uint256 tokenSent;
    }
    
    //investors indexed by their ETH address
    mapping(address => Investor) public investors;
    mapping(address => bool) public isinvestor;
    mapping(address => bool) public whitelist;
    //investors indexed by their IDs
    mapping(uint256 => address) public investorList;

    //event to log token supplied
    event TokenSupplied(address beneficiary, uint256 tokens, uint256 value);   
    event RefundedToInvestor(address indexed beneficiary, uint256 weiAmount);
    event NewSaleEndDate(uint256 endTime);
    event StateChanged(bool changed);

    modifier respectTimeFrame() {
        require (start);
        require(!paused);
        require(now >= saleStartDate);
        require(now <= saleEndDate);
       _;
    }

    constructor(address _ETHCollector) public {
        ETHCollector = _ETHCollector;    
        hardCap = 40e6 * 10**18;
        softCap = 2e6 * 10**18;
        //Initially no investor has been refunded
        countInvestorsRefunded = 0;
        //Refund eligible or not
        refundStatus = false;
    }
    
    //transfer ownership with token balance
    function transferOwnership(address _newOwner) public onlyOwner {
        super.transfer(_newOwner, balances[owner]);
        _transferOwnership(_newOwner);
    }

     //function to start sale
    function startSale(uint256 _saleStartDate, uint256 _saleEndDate, uint256 _newETH_USD) public onlyOwner{
       require (_saleStartDate < _saleEndDate);
       require (now <= _saleStartDate);
       assert(!start);
       saleStartDate = _saleStartDate;
       saleEndDate = _saleEndDate;  
       start = true; 
       ETH_USD = _newETH_USD;
    }

    //function to finalize sale
    function finalizeSale() public onlyOwner{
        assert(start);
        //end sale only when tokens is not sold and sale time is over OR,
        //end time is not over and all tokens are sold
        assert(!(tokensSold < hardCap && now < saleEndDate) || (hardCap.sub(tokensSold) <= 1e18));  
        if(!softCapReached()){
            refundStatus = true;
        }
        start = false;
        stop = true;
    }

    /**
    * @dev called by the owner to stopInEmergency , triggers stopped state
    */
    function stopInEmergency() onlyOwner public {
        require(!paused);
        paused = true;
        emit StateChanged(true);
    }

    /**
    * @dev after stopping crowdsale, the contract owner can release the crowdsale
    * 
    */
    function release() onlyOwner public {
        require(paused);
        paused = false;
        emit StateChanged(true);
    }

    //function to set ETH_USD rate in cent. Eg: 1 ETH = 300 USD so we need to pass 300,00 cents
    function setETH_USDRate(uint256 _newETH_USD) public onlyOwner {
        require(_newETH_USD > 0);
        ETH_USD = _newETH_USD;
    }

    //function to change token cost
    function changeTokenCost(uint256 _tokenCost) public onlyOwner {
        require(_tokenCost > 0);
        tokenCost = _tokenCost;
    }

    //funcion to change minContribution
    //_minContribution parameter should be in cent 
    function changeMinContribution(uint256 _minContribution) public onlyOwner {
        require(_minContribution > 0);
        minContribution = _minContribution;
    }

    //function to increase time
    function extendTime(uint256 _newEndSaleDate) onlyOwner public {
        //current time should always be less than endTime+extendedTime
        require(saleEndDate < _newEndSaleDate);
        require(_newEndSaleDate != 0);
        saleEndDate = _newEndSaleDate;
        emit NewSaleEndDate(saleEndDate);
    }
    
    
    // function to add single whitelist
    function addWhitelistAddress(address addr) public onlyOwner{
        require (!whitelist[addr]); 
        require(addr != address(0x0));
        // owner approves buyers by address when they pass the whitelisting procedure
        whitelist[addr] = true;
    }
    
    /**
    * @dev add addresses to the whitelist
    * @return true if at least one address was added to the whitelist,
    * false if all addresses were already in the whitelist
    */
    function addWhitelistAddresses(address[] _addrs) public onlyOwner{
        for (uint256 i = 0; i < _addrs.length; i++) {
            addWhitelistAddress(_addrs[i]);        
        }
    }

    function transfer(address to, uint256 value) public returns (bool){
        if(isinvestor[msg.sender]){
            //sale has ended
            require(stop);
            super.transfer(to, value);
        }
        
        else if(isTeamOrAdvisorsOrFounder[msg.sender]){
            //180 days = 6 months
            require(now > saleEndDate.add(180 days));
            super.transfer(to, value);
        }
        else {
            super.transfer(to, value);
        }
    }

    function transferFrom(address from, address to, uint256 value) public returns (bool){
        if(isinvestor[from]){
            //sale has ended
            require(stop);
            super.transferFrom(from, to, value);
        } 
         else if(isTeamOrAdvisorsOrFounder[from]){
            //180 days = 6 months
            require(now > saleEndDate.add(180 days));
            super.transferFrom(from,to, value);
        } 
        else {
           super.transferFrom(from, to, value);
        }
    }
    
    //function to buy tokens
    function buyTokens (address beneficiary) public payable respectTimeFrame {
        // only approved buyers can call this function
        require(whitelist[beneficiary]);
        // No contributions below the minimum
        require(msg.value >= getMinContributionInWei());

        uint256 tokenToTransfer = getTokens(msg.value);

        // Check if the CrowdSale hard cap will be exceeded
        require(tokensSold.add(tokenToTransfer) <= hardCap);
        tokensSold = tokensSold.add(tokenToTransfer);

        //initializing structure for the address of the beneficiary
        Investor storage investorStruct = investors[beneficiary];

        //Update investor's balance
        investorStruct.tokenSent = investorStruct.tokenSent.add(tokenToTransfer);
        investorStruct.weiReceived = investorStruct.weiReceived.add(msg.value);

        //If it is a new investor, then create a new id
        if(investorStruct.investorID == 0){
            countTotalInvestors++;
            investorStruct.investorID = countTotalInvestors;
            investorList[countTotalInvestors] = beneficiary;
        }

        isinvestor[beneficiary] = true;
        ETHCollector.transfer(msg.value);
        
        weiCollected = weiCollected.add(msg.value);
        
        balances[owner] = balances[owner].sub(tokenToTransfer);
        balances[beneficiary] = balances[beneficiary].add(tokenToTransfer);

        emit TokenSupplied(beneficiary, tokenToTransfer, msg.value);
    }

    /**
    * @dev payable function to accept ether.
    *
    */
    function () external payable  {
        buyTokens(msg.sender);
    }

    /*
    * Refund the investors in case target of crowdsale not achieved
    */
    function refund() public onlyOwner {
        assert(refundStatus);
        uint256 batchSize = countInvestorsRefunded.add(50) < countTotalInvestors ? countInvestorsRefunded.add(50): countTotalInvestors;
        for(uint256 i = countInvestorsRefunded.add(1); i <= batchSize; i++){
            address investorAddress = investorList[i];
            Investor storage investorStruct = investors[investorAddress];
            //return everything
            investorAddress.transfer(investorStruct.weiReceived);
            //burn investor tokens and total supply isTeamOrAdvisorsOrFounder
            burn(investorAddress, investorStruct.tokenSent);
            //set everything to zero after transfer successful
            investorStruct.weiReceived = 0;
            investorStruct.tokenSent = 0;
        }
        //Update the number of investors that have recieved refund
        countInvestorsRefunded = batchSize;
    }

    /*
    * Failsafe drain
    */
    function drain() public onlyOwner {
        ETHCollector.transfer(address(this).balance);
    }

    /*
    * Function to add Ether in the contract 
    */
    function fundContractForRefund()public payable{
    }

    /**
    *
    *getter functions
    *
    *
    */
    //function to return the number of tokens sent to investor
    
    function getTokens(uint256 weiReceived) internal view returns(uint256){
        uint256 tokens;
        //Token Sale Stage 1 = Dates Start 10/15/18 End 10/31/18 35% discount
        if(now >= saleStartDate && now <= saleStartDate.add(10 days)){
            tokens = getTokensForWeiReceived(weiReceived);
            tokens = tokens.mul(100 + 60) / 100;
        //Token Sale Stage 2 = Dates Start 11/1/18 End 11/15/18 20% discount    
        }else if (now > saleStartDate.add(10 days) && now <= saleStartDate.add(25 days)){
            tokens = getTokensForWeiReceived(weiReceived);
            tokens = tokens.mul(100 + 50) / 100;
        //Token Sale Stage 3 = Dates Start 11/16/18 End 11/30/18 No discount    
        }else if (now > saleStartDate.add(25 days)  && now <= saleEndDate){
            tokens = getTokensForWeiReceived(weiReceived);
            tokens = tokens.mul(100 + 30) / 100;
        }
        return tokens;
    }

    //function to get tokens number for eth send
    function getTokensForWeiReceived(uint256 _weiAmount) internal view returns (uint256) {
        return _weiAmount.mul(ETH_USD).div(tokenCost);
    }

    //function to check softcap reached or not
    function softCapReached() view public returns(bool) {
        return tokensSold >= softCap;
    }

    //getSaleStage will return current sale stage
    function getSaleStage() view public returns(uint8){
        if(now >= saleStartDate && now <= saleStartDate.add(10 days)){
            return 1;
        }else if(now > saleStartDate.add(10 days) && now <= saleStartDate.add(25 days)){
            return 2;
        }else if (now > saleStartDate.add(25 days)  && now <= saleEndDate){
            return 3;
        }
    }
    
    //get minimum contribution in wei
     function getMinContributionInWei() public view returns(uint256){
        return (minContribution.mul(1e18)).div(ETH_USD);
    }
    
    //is address whitelisted
    function isAddressWhitelisted(address addr) public view returns(bool){
        return whitelist[addr];
    }
}
function getSaleStage() view public returns(uint8){
    if(now >= saleStartDate && now <= saleStartDate.add(10 days)){
        return 1;
    }else if(now > saleStartDate.add(10 days) && now <= saleStartDate.add(25 days)){
        return 2;
    }else if (now > saleStartDate.add(25 days)  && now <= saleEndDate){
        return 3;
    }
}

//getSaleStage will return current sale stage