contract Sale is CappedCrowdsale, Ownable {
    using SafeMath for uint256;

    // Initialization Variables
    uint256 public amountPerDay; // 30 eth
    uint256 public constant UNIX_DAY = 86400;

    bool public isFinalized = false;
    event Finalized();

    mapping (address => bool) public whitelist;
    mapping (address => uint256) public amountContributedBy;
    // * * *

    // Constructor
    function Sale(uint256 _startTime, uint256 _endTime, uint256 _rate, uint256 _cap, address _wallet, uint256 _amountPerDay, address _tokenAddress)
    Crowdsale(_startTime, _endTime, _rate, _wallet)
    CappedCrowdsale(_cap)
    {
        amountPerDay = _amountPerDay;
        token = LamdenTau(_tokenAddress);
    }
    // * * *
    
    // Crowdsale overrides
    function createTokenContract() internal returns (MintableToken) {
        return LamdenTau(0x0);
    }
    
    function validPurchase() internal constant returns (bool) {
        bool withinCap = weiRaised.add(msg.value) <= cap;
        bool withinContributionLimit = msg.value <= currentPersonalLimit(msg.sender);
        bool valid = super.validPurchase() && withinCap && whitelist[msg.sender] && withinContributionLimit;
        return valid;
    }

    function buyTokens(address beneficiary) public payable {
        super.buyTokens(beneficiary);
        amountContributedBy[msg.sender] = amountContributedBy[msg.sender].add(msg.value);
    }
    // * * *

    // Finalizer functions
    function finalize() onlyOwner public {
      require(!isFinalized);
      require(hasEnded());

      finalization();
      Finalized();

      isFinalized = true;
    }
    
    function finalization() internal {
        token.finishMinting();
    }
    // * * * 
    
    // Contract Specific functions
    function daysSinceLaunch() public constant returns(uint256) {
        return now.sub(startTime).div(UNIX_DAY);
    }
    
    function currentContributionLimit() public constant returns(uint256) {
        return amountPerDay.mul(2 ** daysSinceLaunch());
    }
    
    function currentPersonalLimit(address _a) public constant returns(uint256) {
        return currentContributionLimit().sub(amountContributedBy[_a]);
    }
    
    function claimToken(address _m) public onlyOwner returns(bool) {
        Mediator m = Mediator(_m);
        m.passOff();
        token.acceptOwnership();
        return true;
    }
    
    function whitelistUser(address _a) onlyOwner public returns(bool) {
        whitelist[_a] = true;
        return whitelist[_a];
    }
    
    function whitelistUsers(address[] users) external onlyOwner {
        for (uint i = 0; i < users.length; i++) {
            whitelist[users[i]] = true;
        }
    }

    function unWhitelistUser(address _a) public onlyOwner returns(bool){
        whitelist[_a] = false;
        return whitelist[_a];
    }

    function unWhitelistUsers(address[] users) external onlyOwner {
        for (uint i = 0; i < users.length; i++) {
            whitelist[users[i]] = false;
        }
    }

    function changeEndTime(uint256 _e) public onlyOwner returns(uint256) {
        require(_e > startTime);
        endTime = _e;
        return endTime;
    }
    // * * *
}
function finalize() onlyOwner public {
  require(!isFinalized);
  require(hasEnded());

  finalization();
  Finalized();

  isFinalized = true;
}

// * * *
// Finalizer functions