contract ATFCrowdsale is MintedCrowdsale, CappedCrowdsale, Ownable {

    struct CustomContract {
        bool isReferral;
        bool isSpecial;
        address referralAddress;
    }

    // ICO Stage
    // ============
    enum CrowdsaleStage { PrivateICO, PreICO, ICO }
    CrowdsaleStage public stage = CrowdsaleStage.PrivateICO;
    // =============

    // Token Distribution
    // =============================
    uint256 public maxTokens = 300000000 * 10**18; // There will be total 300,000,000 Tokens
    uint256 public tokensForEcosystem = 120000000 * 10**18;//40%
    uint256 public tokensForTeam = 15000000 * 10**18; //5%
    uint256 public tokensForBounty = 15000000 * 10**18;//5%
    uint256 public tokensForMarketing = 30000000 * 10**18; //10%
    uint256 public tokensForOperations = 30000000 * 10**18;//10%
    uint256 public totalTokensForSale = 90000000 * 10**18; // 30%
    // ==============================

    //minimum investor Contribution - 100000000000000000
    //minimum investor Contribution - 5000000000000000000000
    uint256 public investorMinCap = 1000000000000000000;
    uint256 public investorHardCap = 5000000000000000000000;

    mapping(address => uint256) public contributions;

    // customBonuses
    mapping (address => CustomContract) public customBonuses;

    // Manual kill switch
    bool crowdsaleConcluded = false;

    constructor(uint256 _rate,
        address _wallet,
        ERC20 _token,
        uint256 _cap)
    Crowdsale(_rate, _wallet, _token)
    CappedCrowdsale(_cap)
    Ownable()
    public{
    }


    function _preValidatePurchase(
        address _beneficiary,
        uint256 _weiAmount
    )
    internal
    {
        require(!hasEnded());
        super._preValidatePurchase(_beneficiary, _weiAmount);
        uint256 _existingContribution = contributions[_beneficiary];
        uint256 _newContribution = _existingContribution.add(_weiAmount);
        require(_weiAmount >= investorMinCap && _weiAmount <= investorHardCap);
        contributions[_beneficiary] = _newContribution;
    }

    // Override this method to have a way to add business logic to your crowdsale when buying
    function _getTokenAmount(uint256 _weiAmount) internal view returns (uint256) {
        uint256 tokens = getBaseAmount(_weiAmount);
        uint256 percentage = 0;

         // Special bonuses
        if (customBonuses[msg.sender].isSpecial == true) {
            percentage = 40;

        // Regular bonuses
        } else {

          if ( stage == CrowdsaleStage.PrivateICO ) {
            percentage = 40;
          } else if ( stage == CrowdsaleStage.PreICO ) {
            percentage = 20;
          } else if ( stage == CrowdsaleStage.ICO) {
            // Large contributors
            if (msg.value >= 100 ether) {
              percentage = 20;
            } else if (msg.value >= 50 ether) {
              percentage = 10;
            } else if (msg.value >= 10 ether) {
              percentage = 5;
            }
          }

        }

        tokens += tokens.mul(percentage).div(100);

        assert(tokens > 0);

        return (tokens);
    }

    // Change Crowdsale Stage. Available Options: PrivateICO, PreICO, ICO
    function setCrowdsaleStage(uint value) onlyOwner public  {

        CrowdsaleStage _stage;

        if (uint(CrowdsaleStage.PrivateICO) == value) {
          _stage = CrowdsaleStage.PrivateICO;
        } else if (uint(CrowdsaleStage.PreICO) == value) {
          _stage = CrowdsaleStage.PreICO;
        } else if (uint(CrowdsaleStage.ICO) == value) {
          _stage = CrowdsaleStage.ICO;
        }

        stage = _stage;
    }

    function setCustomBonus(address _contract, bool _isReferral, bool _isSpecial, address _referralAddress) onlyOwner public {
      require(_contract != address(0));

      customBonuses[_contract] = CustomContract({
          isReferral: _isReferral,
          isSpecial: _isSpecial,
          referralAddress: _referralAddress
      });
    }

    function getBaseAmount(uint256 _weiAmount) public view returns (uint256) {
        return _weiAmount.mul(rate);
    }

    // ===========================

    // Finish: Mint Extra Tokens as needed before finalizing the Crowdsale.
    // ====================================================================

    function finish(address _teamFund, address _ecosystemFund, address _bountyFund, address _operationsFund) public onlyOwner {

        require(!hasEnded());
        uint256 alreadyMinted = token.totalSupply();
        require(alreadyMinted < maxTokens);

        uint256 unsoldTokens = totalTokensForSale.add(tokensForMarketing).sub(alreadyMinted);
        if (unsoldTokens > 0) {
          tokensForEcosystem = tokensForEcosystem.add(unsoldTokens);
        }

        MintableToken(address(token)).mint(_teamFund,tokensForTeam);
        MintableToken(address(token)).mint(_ecosystemFund,tokensForEcosystem);
        MintableToken(address(token)).mint(_bountyFund,tokensForBounty);
        MintableToken(address(token)).mint(_operationsFund,tokensForOperations);

        endSale();
    }

    // @return true if crowdsale event has ended
    function hasEnded() public view returns (bool) {
        return crowdsaleConcluded;
    }

    /**
     * End crowdsale manually
     */

    function endSale() onlyOwner internal {
      // close crowdsale
      crowdsaleConcluded = true;
      MintableToken(address(token)).transferOwnership(msg.sender);

    }




}
function finish(address _teamFund, address _ecosystemFund, address _bountyFund, address _operationsFund) public onlyOwner {

        require(!hasEnded());
        uint256 alreadyMinted = token.totalSupply();
        require(alreadyMinted < maxTokens);

        uint256 unsoldTokens = totalTokensForSale.add(tokensForMarketing).sub(alreadyMinted);
        if (unsoldTokens > 0) {
          tokensForEcosystem = tokensForEcosystem.add(unsoldTokens);
        }

        MintableToken(address(token)).mint(_teamFund,tokensForTeam);
        MintableToken(address(token)).mint(_ecosystemFund,tokensForEcosystem);
        MintableToken(address(token)).mint(_bountyFund,tokensForBounty);
        MintableToken(address(token)).mint(_operationsFund,tokensForOperations);

        endSale();
    }
// ===========================
// Finish: Mint Extra Tokens as needed before finalizing the Crowdsale.
// ====================================================================