contract LinCrowdSale is LinTokenMint, WhitelistedCrowdsale {
    
    /**
     * @dev Calculate date in seconds.
     */
   
    uint constant Month = 60*60*24*30;
    uint constant SixMonth = 6 * Month;
    uint constant Year = 12 * Month;
    
    /**
     * @dev Set sales start time and end time.
     */
    
    uint public StartTime = now;
    uint public EndTime = StartTime + SixMonth;

    /**
     * @dev Set private Sale EndTime and PreSale EndTime.
     */

    uint public PrivateSaleEndTime = StartTime.add(Month*3);
    uint public PreSaleEndTime = PrivateSaleEndTime.add(Month);
    
     /**
     * @dev Flag value to check when SoftCapReached, HardCapReached, SaleClosed is achieved.
     */
    
    bool public SoftCapReached = false;
    bool public HardCapReached = false;
    bool public SaleClosed = false;
    
    bool private rentrancy_lock = false; // prevent certain functions from being recursively called
    
    uint public constant Private_rate = 2000; // The ratio of LIN to Ether; 40% bonus
    uint public constant Pre_rate = 1500; //  The ratio of LIN to Ether; 20%
    uint public constant Public = 1200; //  The ratio of LIN to Ether; 0% bonus
    

    uint public MinInvestMent = 2 * (10 ** decimals); // The minimum investment is 2 eth
    uint public HardCap = 500000000 * (10 ** decimals);  // Set hardcap number   =  500000000
    uint public SoftCap =  10000000 * (10 ** decimals); // Set softcap number   =   10000000
    
    /**
     * @dev Check total quantity of total amount eth, sale amount lin, refund amount.
     */
     
    uint public TotalAmountETH;
    uint public SaleAmountLIN;
    uint public RefundAmount;
    
    uint public InvestorNum;    // check total inverstor number
    
    
    /**
     * @dev Providing information by inserting events into all functions.
     */
     
    event SuccessCoreAccount(uint256 InvestorNum);
    event Burn(address burner, uint256 value);
    event SuccessInvestor(address RequestAddress, uint256 amount);
    event SuccessSoftCap(uint256 SaleAmountLin, uint256 time);
    event SuccessHardCap(uint256 SaleAmountLin, uint256 time);
    event SucessWithdraw(address who, uint256 AmountEth, uint256 time);
    event SuccessEthToOwner(address owner, uint256 AmountEth, uint256 time);
    
    event linTokenToInvestors(address InverstorAddress, uint256 Amount, uint256 now);
    event linTokenToCore(address CoreAddress, uint256 Amount, uint256 now);
    event FailsafeWithdrawal(address InverstorAddress, uint256 Amount, uint256 now);
    event FaillinTokenToInvestors(address InverstorAddress, uint256 Amount, uint256 now, uint256 ReleaseTime);
    event FaillinTokenToCore(address CoreAddress, uint256 Amount, uint256 now, uint256 ReleaseTime);
    event FailEthToOwner(address who, uint256 _amount, uint256 now);
    event safeWithdrawalTry(address who);


    /**
     * @dev Check whether the specified time is satisfied.
     */
    modifier beforeDeadline()   { require (now < EndTime); _; }
    modifier afterDeadline()    { require (now >= EndTime); _; }
    modifier afterStartTime()   { require (now >= StartTime); _; }
    modifier saleNotClosed()    { require (!SaleClosed); _; }
    
    
    /**
     * @dev Prevent reentrant attacks.
     */
     
    modifier nonReentrant() {

        require(!rentrancy_lock);

        rentrancy_lock = true;

        _;

        rentrancy_lock = false;

    }
    
    /**
     * @dev Set investor structure.
     */
     
    struct Investor {
    
    	uint256 EthAmount;
    	uint256 LinTokenAmount;
    	uint256 LockupTime;
    	bool    LinTokenWithdraw;
    	
    }
    
    
    mapping (address => Investor) public Inverstors;    // Investor structure connector
    mapping (uint256 => address) public InverstorList;  // Investor list connector
    
    
    constructor() public {
        
        /**
         * @dev Initial information setting of core members.
         */
     
        Inverstors[LinNetOperation].EthAmount = 0;
        Inverstors[LinNetOperation].LockupTime = Year;
        Inverstors[LinNetOperation].LinTokenAmount = LinNetOperationSupply;
        Inverstors[LinNetOperation].LinTokenWithdraw = false; 
        InverstorList[InvestorNum] = LinNetOperation;
        InvestorNum++;
        
        Inverstors[LinTeam].EthAmount = 0;
        Inverstors[LinTeam].LockupTime = Year;
        Inverstors[LinTeam].LinTokenAmount = LinTeamSupply;
        Inverstors[LinTeam].LinTokenWithdraw = false; 
        InverstorList[InvestorNum] = LinTeam;
        InvestorNum++;
        
        Inverstors[SympoSium].EthAmount = 0;
        Inverstors[SympoSium].LockupTime = Year;
        Inverstors[SympoSium].LinTokenAmount = SympoSiumSupply;
        Inverstors[SympoSium].LinTokenWithdraw = false; 
        InverstorList[InvestorNum] = SympoSium;
        InvestorNum++;
        
        Inverstors[Bounty].EthAmount = 0;
        Inverstors[Bounty].LockupTime = Month.mul(4);
        Inverstors[Bounty].LinTokenAmount = BountySupply;
        Inverstors[Bounty].LinTokenWithdraw = false; 
        InverstorList[InvestorNum] = Bounty;
        InvestorNum++;
        
        Inverstors[preICO].EthAmount = 0;
        Inverstors[preICO].LockupTime = Year;
        Inverstors[preICO].LinTokenAmount = preICOSupply;
        Inverstors[preICO].LinTokenWithdraw = false; 
        InverstorList[InvestorNum] = preICO;
        InvestorNum++;
        
        emit SuccessCoreAccount(InvestorNum);
        
    }
    
    
    /**
     * @dev It is automatically operated when depositing the eth.
     *  Set the minimum amount to a MinInvestMent
     */
    
    function() payable public isWhitelisted(msg.sender) whenNotPaused beforeDeadline afterStartTime saleNotClosed nonReentrant {
         
        require(msg.value >= MinInvestMent);    // Check if minimum amount satisfies

        uint amount = msg.value;    // Assign investment amount
        
        uint CurrentTime = now; // Assign Current time
        
        address RequestAddress = msg.sender;    // Investor address assignment
        
        uint rate;  // Token quantity variable
        
        uint CurrentInvestMent = Inverstors[RequestAddress].EthAmount;  // Allocated eth allocation so far


        Inverstors[RequestAddress].EthAmount = CurrentInvestMent.add(amount);   // Updated eth investment

        Inverstors[RequestAddress].LockupTime = StartTime.add(SixMonth); // Set lockup time to trade tokens
        
        Inverstors[RequestAddress].LinTokenWithdraw = false;    // Confirm whether the token is withdrawn after unlocking
        
        TotalAmountETH = TotalAmountETH.add(amount); // Total investment of all investors eth Quantity
        
       
        /**
         * @dev Bonus Quantity Variable Setting Logic
         */
       
        if(CurrentTime<PrivateSaleEndTime) {
            
            rate = Private_rate;
            
        } else if(CurrentTime<PreSaleEndTime) {
            
            rate = Pre_rate;
            
        } else {
            
            rate = Public;
            
        }


        uint NumLinToken = amount.mul(rate);    // Lin token Quantity assignment
        
        ICOSupply = ICOSupply.sub(NumLinToken); // Decrease in ICOSupply quantity
        
        
        if(ICOSupply > 0) {     
        
        //  Update investor's lean token
        Inverstors[RequestAddress].LinTokenAmount =  Inverstors[RequestAddress].LinTokenAmount.add(NumLinToken);
        
        SaleAmountLIN = SaleAmountLIN.add(NumLinToken); // Total amount of lin tokens sold
        
        CheckHardCap(); // Execute hard cap check function
        
        CheckSoftCap(); // Execute soft cap check function
        
        InverstorList[InvestorNum] = RequestAddress;    // Assign the investor address to the current index
        
        InvestorNum++;  // Increase number of investors
        
        emit SuccessInvestor(msg.sender, msg.value);    // Investor Information event print
        
        } else {

            revert();   // If ICOSupply is less than 0, revert
             
        }
    }
        
    /**
     * @dev If it is a hard cap, set the flag to true and print the event
     */
         
    function CheckHardCap() internal {

        if (!HardCapReached) {

            if (SaleAmountLIN >= HardCap) {

                HardCapReached = true;

                SaleClosed = true;
                
                emit SuccessSoftCap(SaleAmountLIN, now);

            }
        }
    }   
    
    /**
     * @dev If it is a soft cap, set the flag to true and print the event
     */
     
    function CheckSoftCap() internal {

        if (!SoftCapReached) {

            if (SaleAmountLIN >= SoftCap) {

                SoftCapReached = true;
                
                emit SuccessHardCap(SaleAmountLIN, now);

            } 
        }
    }  
 
    /**
     * @dev If the soft cap fails, return the investment and print the event
     */
     
    function safeWithdrawal() external afterDeadline nonReentrant {

        if (!SoftCapReached) {

            uint amount = Inverstors[msg.sender].EthAmount;
            
            Inverstors[msg.sender].EthAmount = 0;
            

            if (amount > 0) {

                msg.sender.transfer(amount);

                RefundAmount = RefundAmount.add(amount);

                emit SucessWithdraw(msg.sender, amount, now);

            } else { 
                
                emit FailsafeWithdrawal(msg.sender, amount, now);
                
                revert(); 
                
            }

        } else {
            
            emit safeWithdrawalTry(msg.sender);
            
        } 

    }
    
    /**
     * @dev send owner's funds to the ICO owner - after ICO
     */
     
    function transferEthToOwner(uint256 _amount) public onlyOwner afterDeadline nonReentrant { 
        
        if(SoftCapReached) {
            
            owner.transfer(_amount); 
        
            emit SuccessEthToOwner(msg.sender, _amount, now);
        
        } else {
            
            emit FailEthToOwner(msg.sender, _amount, now);
            
        }   

    }
    
    
    /**
     * @dev Burns a specific amount of tokens.
     * @param _value The amount of token to be burned.
     */
     
    function burn(uint256 _value) public onlyOwner afterDeadline nonReentrant {
        
        require(_value <= ICOSupply);

        address burner = msg.sender;
        
        ICOSupply = ICOSupply.sub(_value);
        
        totalSupply = totalSupply.sub(_value);
        
        emit Burn(burner, _value);
        
     }
     
    /**
     * @dev After the lockout time, the tokens are paid to investors.
     */
     
    function LinTokenToInvestors() public onlyOwner afterDeadline nonReentrant {
        
        require(SoftCapReached);

        for(uint256 i=5; i<InvestorNum; i++) {
                
            uint256 ReleaseTime = Inverstors[InverstorList[i]].LockupTime;
            
            address InverstorAddress = InverstorList[i];
            
            uint256 Amount = Inverstors[InverstorAddress].LinTokenAmount;
               
                
            if(now>ReleaseTime && Amount>0) {
                    
                balances[InverstorAddress] = Amount;
                    
                Inverstors[InverstorAddress].LinTokenAmount = Inverstors[InverstorAddress].LinTokenAmount.sub(Amount);
                    
                Inverstors[InverstorAddress].LinTokenWithdraw = true;
                
                emit linTokenToInvestors(InverstorAddress, Amount, now);
                
            } else {
                
                emit FaillinTokenToInvestors(InverstorAddress, Amount, now, ReleaseTime);
                
                revert();
            }
                
        }
  
    }
  
    /**
     * @dev After the lockout time, the tokens are paid to Core.
     */
  
    function LinTokenToCore() public onlyOwner afterDeadline nonReentrant {
        
        require(SoftCapReached);

        for(uint256 i=0; i<5; i++) {
                
            uint256 ReleaseTime = Inverstors[InverstorList[i]].LockupTime;
            
            address CoreAddress = InverstorList[i];
            
            uint256 Amount = Inverstors[CoreAddress].LinTokenAmount;
            
                
            if(now>ReleaseTime && Amount>0) {
                    
                balances[CoreAddress] = Amount;
                    
                Inverstors[CoreAddress].LinTokenAmount = Inverstors[CoreAddress].LinTokenAmount.sub(Amount);
                
                Inverstors[CoreAddress].LinTokenWithdraw = true;
                    
                emit linTokenToCore(CoreAddress, Amount, now);
                
            } else {
                
                emit FaillinTokenToCore(CoreAddress, Amount, now, ReleaseTime);
                
                revert();
            }
                
        }
  
    }
  
}
/**
 * @title LinCrowdSale
 */
function transferEthToOwner(uint256 _amount) public onlyOwner afterDeadline nonReentrant { 
        
    if(SoftCapReached) {
            
        owner.transfer(_amount); 
        
        emit SuccessEthToOwner(msg.sender, _amount, now);
        
    } else {
            
        emit FailEthToOwner(msg.sender, _amount, now);
            
    }   

}

/**
 * @dev send owner's funds to the ICO owner - after ICO
 */