contract OrigamiTokenSale is Ownable, CappedCrowdsale, WhitelistedCrowdsale {
    // hard cap of the token sale in ether
    uint private constant HARD_CAP_IN_WEI = 5000 ether;
    uint private constant HARD_CAP_IN_WEI_PRESALE = 1000 ether;

    // Bonus
    uint private constant BONUS_TWENTY_AMOUNT = 200 ether;
    uint private constant BONUS_TEN_AMOUNT = 100 ether;
    uint private constant BONUS_FIVE_AMOUNT = 50 ether;   
    
    // Maximum / Minimum contribution
    uint private constant MINIMUM_INVEST_IN_WEI_PRESALE = 0.5 ether;
    uint private constant CONTRIBUTOR_MAX_PRESALE_CONTRIBUTION = 50 ether;
    uint private constant MINIMUM_INVEST_IN_WEI_SALE = 0.1 ether;
    uint private constant CONTRIBUTOR_MAX_SALE_CONTRIBUTION = 500 ether;

    // TEAM WALLET
    address private constant ORIGAMI_WALLET = 0xf498ED871995C178a5815dd6D80AE60e1c5Ca2F4;
    
    // Token initialy distributed for the bounty
    address private constant BOUNTY_WALLET = 0xDBA7a16383658AeDf0A28Eabf2032479F128f26D;
    uint private constant BOUNTY_AMOUNT = 3000000e18;

    // PERIOD WHEN TOKEN IS NOT TRANSFERABLE AFTER THE SALE
    uint private constant PERIOD_AFTERSALE_NOT_TRANSFERABLE_IN_SEC = 7 days;    

    // Total of ORI supply
    uint private constant TOTAL_ORI_TOKEN_SUPPLY = 50000000;

    // Token sale rate from ETH to ORI
    uint private constant RATE_ETH_ORI = 6000;
    

    // start and end timestamp PRESALE
    uint256 public presaleStartTime;
    uint256 public presaleEndTime;
    uint256 private presaleEndedAt;
    uint256 public preSaleWeiRaised;
    
    // Bonus Times
    uint public firstWeekEndTime;
    uint public secondWeekEndTime;  
    
    
    // Check wei invested by contributor on presale
    mapping(address => uint256) wei_invested_by_contributor_in_presale;
    mapping(address => uint256) wei_invested_by_contributor_in_sale;

    event OrigamiTokenPurchase(address indexed beneficiary, uint256 value, uint256 final_tokens, uint256 initial_tokens, uint256 bonus);

    function OrigamiTokenSale(uint256 _presaleStartTime, uint256 _presaleEndTime, uint256 _startTime, uint256 _endTime, uint256 _firstWeekEndTime, uint256 _secondWeekEndTime) public
      WhitelistedCrowdsale()
      CappedCrowdsale(HARD_CAP_IN_WEI)
      StandardCrowdsale(_startTime, _endTime, RATE_ETH_ORI, ORIGAMI_WALLET)
    {
        // create the token
        token = createTokenContract();
        // Get presale start / end time
        presaleStartTime = _presaleStartTime;
        presaleEndTime = _presaleEndTime;
        firstWeekEndTime = _firstWeekEndTime;
        secondWeekEndTime = _secondWeekEndTime;

        // transfer token to bountry wallet
        token.transfer(BOUNTY_WALLET, BOUNTY_AMOUNT);
    }
    
    /**
     * @dev return if the presale is open
     */
    function preSaleOpen() 
        public
        view 
        returns(bool)
    {
        return (now >= presaleStartTime && now <= presaleEndTime && preSaleWeiRaised < HARD_CAP_IN_WEI_PRESALE);
    }
    
    /**
     * @dev return the sale ended at time
     */
    function preSaleEndedAt() 
        public
        view 
        returns(uint256)
    {
        return presaleEndedAt;
    }
    
    /**
     * @dev return if the sale is open
     */
    function saleOpen() 
        public
        view 
        returns(bool)
    {
        return (now >= startTime && now <= endTime);
    }
    
    /**
     * @dev get invested amount for an address
     * @param _address address of the wallet
     */
    function getInvestedAmount(address _address)
    public
    view
    returns (uint256)
    {
        uint256 investedAmount = wei_invested_by_contributor_in_presale[_address];
        investedAmount = investedAmount.add(wei_invested_by_contributor_in_sale[_address]);
        return investedAmount;
    }

    /**
     * @dev Get bonus from an invested amount
     * @param _weiAmount weiAmount that will be invested
     */
    function getBonusFactor(uint256 _weiAmount)
        private view returns(uint256)
    {
        // declaration bonuses
        uint256 bonus = 0;

        // If presale : bonus 15% otheriwse bonus on volume
        if(now >= presaleStartTime && now <= presaleEndTime) {
            bonus = 15;
        //si week 1 : 10%
        } else {        
          // Bonus 20 % if ETH >= 200
          if(_weiAmount >= BONUS_TWENTY_AMOUNT) {
              bonus = 20;
          }
          //  Bonus 10 % if ETH >= 100 or first week
          else if(_weiAmount >= BONUS_TEN_AMOUNT || now <= firstWeekEndTime) {
              bonus = 10;
          }
          // Bonus 10 % if ETH >= 20 or second week
          else if(_weiAmount >= BONUS_FIVE_AMOUNT || now <= secondWeekEndTime) {
              bonus = 5;
          }
        }
        
        return bonus;
    }
    
    // ORI : token are not mintable, transfer to wallet instead
    function buyTokens() 
       public 
       payable 
    {
        require(validPurchase());
        uint256 weiAmount = msg.value;

        // calculate token amount to be created
        uint256 tokens = weiAmount.mul(rate);

        //get bonus
        uint256 bonus = getBonusFactor(weiAmount);
        
        // Calculate final bonus amount
        uint256 final_bonus_amount = (tokens * bonus) / 100;
        
         // Transfer bonus tokens to buyer and tokens
        uint256 final_tokens = tokens.add(final_bonus_amount);
        // Transfer token with bonus to buyer
        require(token.transfer(msg.sender, final_tokens)); 

         // Trigger event
        OrigamiTokenPurchase(msg.sender, weiAmount, final_tokens, tokens, final_bonus_amount);

        // Forward funds to team wallet
        forwardFunds();

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

        // If cap reached, set end time to be able to transfer after x days
        if (preSaleOpen()) {
            wei_invested_by_contributor_in_presale[msg.sender] =  wei_invested_by_contributor_in_presale[msg.sender].add(weiAmount);
            preSaleWeiRaised = preSaleWeiRaised.add(weiAmount);
            if(weiRaised >= HARD_CAP_IN_WEI_PRESALE){
                presaleEndedAt = now;
            }
        }else{
            wei_invested_by_contributor_in_sale[msg.sender] =  wei_invested_by_contributor_in_sale[msg.sender].add(weiAmount);  
            if(weiRaised >= HARD_CAP_IN_WEI){
              endTime = now;
            }
        }
    }


    /**
     * 
     * @return the StandardToken created
     */
    function createTokenContract () 
      internal 
      returns(StandardToken) 
    {
        return new OrigamiToken(TOTAL_ORI_TOKEN_SUPPLY, endTime.add(PERIOD_AFTERSALE_NOT_TRANSFERABLE_IN_SEC), ORIGAMI_WALLET, BOUNTY_WALLET);
    }

    // fallback function can be used to buy tokens
    function () external
       payable 
    {
        buyTokens();
    }
    
    /**
     * @dev Returns the remaining possibled presale amount for a given wallet
     * @return amount remaining
     */
    function getContributorRemainingPresaleAmount(address wallet) public view returns(uint256) {
        uint256 invested_amount =  wei_invested_by_contributor_in_presale[wallet];
        return CONTRIBUTOR_MAX_PRESALE_CONTRIBUTION - invested_amount;
    }
    
        /**
     * @dev Returns the remaining possibled sale amount for a given wallet
     * @return amount remaining
     */
    function getContributorRemainingSaleAmount(address wallet) public view returns(uint256) {
        uint256 invested_amount =  wei_invested_by_contributor_in_sale[wallet];
        return CONTRIBUTOR_MAX_SALE_CONTRIBUTION - invested_amount;
    }

    /**
     * @dev Transfer the unsold tokens to the origami team
     * @dev Only for owner
     * @return the StandardToken created
     */
    function drainRemainingToken () 
      public
      onlyOwner
    {
        require(hasEnded());
        token.transfer(ORIGAMI_WALLET, token.balanceOf(this));
    }
    
    /**
     * @dev test if the purchase can be operated
     */
    function validPurchase () internal view returns(bool) 
    {
        // if presale, add to wei raise by contributor
        if (preSaleOpen()) {
            // Test presale Cap
            if(preSaleWeiRaised > HARD_CAP_IN_WEI_PRESALE){
                return false;
            }
            // Test minimum investing for contributor in presale
            if(msg.value < MINIMUM_INVEST_IN_WEI_PRESALE){
                 return false;
            }
            // Test global invested amount for presale per contributor
            uint256 maxInvestAmount = getContributorRemainingPresaleAmount(msg.sender);
            if(msg.value > maxInvestAmount){
              return false;
            }
        }else if(saleOpen()){
            // Test minimum investing for contributor in presale
            if(msg.value < MINIMUM_INVEST_IN_WEI_SALE){
                 return false;
            }
            
             //Test global invested amount for sale per contributor
             uint256 maxInvestAmountSale = getContributorRemainingSaleAmount(msg.sender);
             if(msg.value > maxInvestAmountSale){
               return false;
            }
        }else{
            return false;
        }

        //Check if we are in Presale and Presale hard cap not reached yet
        bool nonZeroPurchase = msg.value != 0;
        return super.validPurchase() && nonZeroPurchase;
    }

}
/**
 * @title OrigamiTokenSale
 * @dev 
 * We add new features to a base crowdsale using multiple inheritance.
 * We are using the following extensions:
 * CappedCrowdsale - sets a max boundary for raised funds
 * WhitelistedCrowdsale - add a whitelist
 *
 * The code is based on the contracts of Open Zeppelin and we add our contracts : OrigamiTokenSale, WhiteListedCrowdsale, CappedCrowdsale and the Origami Token
 *
 * @author ori.network
 */
function drainRemainingToken () 
  public
  onlyOwner
{
    require(hasEnded());
    token.transfer(ORIGAMI_WALLET, token.balanceOf(this));
}

/**
 * @dev Transfer the unsold tokens to the origami team
 * @dev Only for owner
 * @return the StandardToken created
 */