contract REDTTokenSale is REDTTokenSaleConfig, Claimable, Pausable, Salvageable {
    using SafeMath for uint;
    bool public isFinalized = false;
    REDTToken public token;
    
    uint public tokensRaised;           
    uint public weiRaised;              // Amount of raised money in WEI
    WhiteListed public whiteListed;
    uint public numContributors;        // Discrete number of contributors

    mapping (address => uint) public contributions; // to allow them to have multiple spends

    event Finalized();
    event TokenPurchase(address indexed beneficiary, uint value, uint amount);
    event TokenPresale(address indexed purchaser, uint amount);

    struct capRec  {
        uint time;
        uint amount;
    }
    capRec[] public capz;
    uint public capDefault;


    constructor( WhiteListed _whiteListed ) public {
        
        require(now < SALE_START);
        
        require(_whiteListed != address(0));
        
        whiteListed = _whiteListed;

        token = new REDTToken(owner);
        token.mint(MULTISIG_TKN,RESERVE_AMOUNT);
        initCaps();
    }

    
    function initCaps() public {
        uint[4] memory caps = [uint(10),20,30,40];
        uint[4] memory times = [uint(1),4,12,24];
        for (uint i = 0; i < caps.length; i++) {
            capRec memory cr;
            cr.time = times[i];
            cr.amount = caps[i];
            capz.push(cr);
        }
        capDefault = 100;
    }
    
    function setCapRec(uint[] capsInEther, uint[] timesInHours, uint defaultCapInEther) public onlyOwner {
        //capRec[] memory cz = new capRec[](caps.length);
        require(capsInEther.length == timesInHours.length);
        capz.length = 0;
        for (uint i = 0; i < capsInEther.length; i++) {
            capRec memory cr;
            cr.time = timesInHours[i];
            cr.amount = capsInEther[i];
            capz.push(cr);
        }
        capDefault = defaultCapInEther;
        
    }
    
    function currentCap() public view returns (uint) {
        for (uint i = 0; i < capz.length; i++) {
            if (now < SALE_START + capz[i].time * 1 hours)
                return (capz[i].amount * 1 ether);
        }
        return capDefault;
    }


    function getRateAndCheckCap() public view returns (uint) {
        
        require(now>SALE_START);
        
        if ((now<SALE0_END) && (tokensRaised < SALE0_CAP))
            return SALE0_RATE;
        
        if ((now<SALE1_END) && (tokensRaised < SALE1_CAP))
            return SALE1_RATE;
        
        if ((now<SALE2_END) && (tokensRaised < SALE2_CAP))
            return SALE2_RATE;
        
        revert();
    }

    // Only fallback function can be used to buy tokens
    function () external payable {
        buyTokens(msg.sender, msg.value);
    }

    function buyTokens(address beneficiary, uint weiAmount) internal whenNotPaused {
        require(contributions[beneficiary].add(weiAmount) < currentCap());
        require(whiteListed.whiteList(beneficiary));
        require((weiAmount > MIN_CONTRIBUTION) || (weiAmount == SALE_CAP.sub(MIN_CONTRIBUTION)));

        weiRaised = weiRaised.add(weiAmount);
        uint tokens = weiAmount.mul(getRateAndCheckCap());

        if (contributions[beneficiary] == 0) {
            numContributors++;
        }

        tokensRaised = tokensRaised.add(tokens);

        contributions[beneficiary] = contributions[beneficiary].add(weiAmount);
        token.mint(beneficiary, tokens);
        emit TokenPurchase(beneficiary, weiAmount, tokens);
        forwardFunds();
    }

    function placeTokens(address beneficiary, uint256 numtokens) 
    public
	  ownerOrMinter
    {
        require(now < SALE_START);  
        tokensRaised = tokensRaised.add(numtokens);
        token.mint(beneficiary,numtokens);
    }


    function tokensUnsold() public view returns(uint) {
        return token.TOTALSUPPLY().sub(token.totalSupply());
    }

    // Return true if crowdsale event has ended
    function hasEnded() public view returns (bool) {
        return ((now > SALE_END) || (tokensRaised >= SALE_CAP));
    }

    // Send ether to the fund collection wallet
    function forwardFunds() internal {
        
        MULTISIG_ETH.transfer(address(this).balance);
    }

    // Must be called after crowdsale ends, to do some extra finalization
    function finalize() onlyOwner public {
        require(!isFinalized);
        require(hasEnded());

        finalization();
        emit Finalized();

        isFinalized = true;
    }

    // Stops the minting and transfer token ownership to sale owner. Mints unsold tokens to owner
    function finalization() internal {
        token.finishMinting();
        token.transferOwnership(owner);
    }
}
function finalize() onlyOwner public {
    require(!isFinalized);
    require(hasEnded());

    finalization();
    emit Finalized();

    isFinalized = true;
}

// Must be called after crowdsale ends, to do some extra finalization