contract Crowdsale is Ownable {
    using SafeMath for uint256;
    using SafeERC20 for ATHLETICOToken;

    event LogStateSwitch(State newState);
    event LogRefunding(address indexed to, uint256 amount);
    mapping(address => uint) public crowdsaleBalances;

    uint256 public softCap = 250 * 1 ether;
    address internal myAddress = this;
    ATHLETICOToken public token = new ATHLETICOToken(myAddress);
    uint64 public crowdSaleStartTime;       
    uint64 public crowdSaleEndTime = 1559347200;       // 01.06.2019 0:00:00
    uint256 internal minValue = 0.005 ether;

    //Addresses for store tokens
    TeamAddress public teamAddress = new TeamAddress();
    BountyAddress public bountyAddress = new BountyAddress();
      
    // How many token units a buyer gets per wei.
    uint256 public rate;

    // Amount of wei raised
    uint256 public weiRaised;

    event LogWithdraw(
        address indexed from, 
        address indexed to, 
        uint256 amount
    );

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

    // Create state of contract
    enum State { 
        Init,    
        CrowdSale,
        Refunding,
        WorkTime
    }

    State public currentState = State.Init;

    modifier onlyInState(State state){ 
        require(state==currentState); 
        _; 
    }


    constructor() public {
        uint256 totalTokens = token.INITIAL_SUPPLY();
        /**
        * @dev Inicial distributing tokens to special adresses
        * TeamAddress - 10%
        * BountyAddress - 5%
        */
        _deliverTokens(teamAddress, totalTokens.div(10));
        _deliverTokens(bountyAddress, totalTokens.div(20));

        rate = 20000;
        setState(State.CrowdSale);
        crowdSaleStartTime = uint64(now);
    }

    /**
     * @dev public function finishing crowdsale if enddate is coming or softcap is passed
     */
    function finishCrowdSale() public onlyInState(State.CrowdSale) {
        require(now >= crowdSaleEndTime || myAddress.balance >= softCap, "Too early");
        if(myAddress.balance >= softCap) {
        setState(State.WorkTime);
        token.setICOover();
        } else {
        setState(State.Refunding);
        }
    }


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

    /**
    * @dev token purchase
    * @param _beneficiary Address performing the token purchase
    */
    function buyTokens(address _beneficiary) public payable {
        uint256 weiAmount = msg.value;
        _preValidatePurchase(_beneficiary, weiAmount);

        // calculate token amount to be created
        uint256 tokens = _getTokenAmount(weiAmount);

        // update state
        weiRaised = weiRaised.add(weiAmount);

        _processPurchase(_beneficiary, tokens);

        crowdsaleBalances[_beneficiary] = crowdsaleBalances[_beneficiary].add(weiAmount);
        
        emit LogTokensPurchased(
            msg.sender,
            _beneficiary,
            weiAmount,
            tokens
        );

    }


    function setState(State _state) internal {
        currentState = _state;
        emit LogStateSwitch(_state);
    }


    /**
     * @dev called by the owner to pause, triggers stopped state
     */
    function pauseCrowdsale() public onlyOwner {
        token.pause();
    }

    /**
     * @dev called by the owner to unpause, returns to normal state
     */
    function unpauseCrowdsale() public onlyOwner {
        token.unpause();
    }

    /**
     * @dev called by the DAO to set new rate
     */
    function setRate(uint256 _newRate) public onlyDAO {
        rate = _newRate;
    }

    /**
     * @dev function set kyc bool to true
     * @param _investor The investor who passed the procedure KYC
     */
    function setKYCpassed(address _investor) public onlyDAO returns(bool){
        token.kycPass(_investor);
        return true;
    }

    /**
     * @dev function set kyc bool to false
     * @param _investor The investor who not passed the procedure KYC after passing
     */
    function setKYCNotPassed(address _investor) public onlyDAO returns(bool){
        token.kycNotPass(_investor);
        return true;
    }

    /**
     * @dev the function tranfer tokens from TeamAddress 
     */
    function transferTokensFromTeamAddress(address _investor, uint256 _value) public onlyDAO returns(bool){
        token.transferTokensFromSpecialAddress(address(teamAddress), _investor, _value); 
        return true;
    } 

    
    /**
     * @dev the function tranfer tokens from BountyAddress 
     */
    function transferTokensFromBountyAddress(address _investor, uint256 _value) public onlyDAO returns(bool){
        token.transferTokensFromSpecialAddress(address(bountyAddress), _investor, _value); 
        return true;
    } 
    
    /**
     * @dev Validation of an incoming purchase. internal function.
     * @param _beneficiary Address performing the token purchase
     * @param _weiAmount Value in wei involved in the purchase
     */
    function _preValidatePurchase(address _beneficiary, uint256 _weiAmount) internal view{
        require(_beneficiary != address(0),"Invalid address");
        require(_weiAmount >= minValue,"Min amount is 0.005 ether");
        require(currentState != State.Refunding, "Only for CrowdSale and Work stage.");
    }

    /**
     * @dev internal function
     * @param _beneficiary Address performing the token purchase
     * @param _tokenAmount Number of tokens to be emitted
     */
    function _deliverTokens(address _beneficiary, uint256 _tokenAmount) internal {
        token.safeTransfer(_beneficiary, _tokenAmount);
    }


    /**
     * @dev Function transfer token to new investors
     * Access restricted DAO
     */ 
    function transferTokens(address _newInvestor, uint256 _tokenAmount) public onlyDAO {
        _deliverTokens(_newInvestor, _tokenAmount);
    }

    /**
     * @dev Function mint tokens to winners or prize funds contracts
     * Access restricted DAO
     */ 
    function mintTokensToWinners(address _address, uint256 _tokenAmount) public onlyDAO {
        require(currentState == State.WorkTime, "CrowdSale is not finished yet. Access denied.");
        token.mint(_address, _tokenAmount);
    }

    /**
    * @dev Executed when a purchase has been validated and is ready to be executed. Not necessarily emits/sends tokens.
    * @param _beneficiary Address receiving the tokens
    * @param _tokenAmount Number of tokens to be purchased
    */
    function _processPurchase(address _beneficiary, uint256 _tokenAmount) internal {
        _deliverTokens(_beneficiary, _tokenAmount);
        
    }


    /**
    * @dev this function is ether converted to tokens.
    * @param _weiAmount Value in wei to be converted into tokens
    * @return Number of tokens that can be purchased with the specified _weiAmount
    */
    function _getTokenAmount(uint256 _weiAmount) internal view returns (uint256) {
        uint256 bonus = 0;
        uint256 resultAmount = _weiAmount;


        /**
        * ICO bonus                        UnisTimeStamp 
        *                                  Start date       End date
        * StartTime -01.01.2019 - 100%     1543622400       1546300800
        * 01.01.2019-01.02.2019 - 50%      1546300800       1548979200
        * 01.02.2019-01.03.2019 - 25%      1548979200       1551398400
        */
        if (now >= crowdSaleStartTime && now < 1546300800) {
            bonus = 100;
        }
        if (now >= 1546300800 && now < 1548979200) {
            bonus = 50;
        }
        if (now >= 1548979200 && now < 1551398400) {
            bonus = 25;
        }
        
        if (bonus > 0) {
            resultAmount += _weiAmount.mul(bonus).div(100);
        }
        return resultAmount.mul(rate);
    }

    /**
     * @dev function returns funds to investors in case of project failure.
     */
    function refund() public payable{
        require(currentState == State.Refunding, "Only for Refunding stage.");
        // refund ether to investors
        uint value = crowdsaleBalances[msg.sender]; 
        crowdsaleBalances[msg.sender] = 0; 
        msg.sender.transfer(value);
        emit LogRefunding(msg.sender, value);
    }

    /**
     * @dev function of withdrawal of funds for the development of the project if successful
     */
    function withdrawFunds (address _to, uint256 _value) public onlyDAO {
        require(currentState == State.WorkTime, "CrowdSale is not finished yet. Access denied.");
        require (myAddress.balance >= _value,"Value is more than balance");
        require(_to != address(0),"Invalid address");
        _to.transfer(_value);
        emit LogWithdraw(msg.sender, _to, _value);
    }

}
/**
 * @title Crowdsale
 * @dev Crowdsale is a base contract for managing a token crowdsale
 */
function unpauseCrowdsale() public onlyOwner {
    token.unpause();
}

/**
 * @dev called by the owner to unpause, returns to normal state
 */