contract Sale is Ownable, Whitelistable, TimedStateMachine, TokenControllerI {
    using SafeMath for uint256;

    // State machine states
    bytes32 private constant SETUP = 'setup';
    bytes32 private constant FREEZE = 'freeze';
    bytes32 private constant SALE_IN_PROGRESS = 'saleInProgress';
    bytes32 private constant SALE_ENDED = 'saleEnded';
    bytes32[] public states = [SETUP, FREEZE, SALE_IN_PROGRESS, SALE_ENDED];

    // Stores the contribution for each user
    mapping(address => uint256) public contributions;
    // Records which users have contributed throughout the sale
    mapping(address => bool) public hasContributed;

    DisbursementHandler public disbursementHandler;

    uint256 public weiContributed = 0;
    uint256 public totalSaleCap;
    uint256 public minContribution;
    uint256 public minThreshold;

    // How many tokens a user will receive per each wei contributed
    uint256 public tokensPerWei;
    uint256 public tokensForSale;

    Token public trustedToken;
    Vault public trustedVault;

    event LogContribution(address indexed contributor, uint256 value, uint256 excess);
    event LogTokensAllocated(address indexed contributor, uint256 amount);

    function Sale(
        uint256 _totalSaleCap,
        uint256 _minContribution,
        uint256 _minThreshold,
        uint256 _maxTokens,
        address _whitelistAdmin,
        address _wallet,
        uint256 _closingDuration,
        uint256 _vaultInitialAmount,
        uint256 _vaultDisbursementAmount,
        uint256 _startTime,
        string _tokenName,
        string _tokenSymbol,
        uint8 _tokenDecimals
    ) 
        Whitelistable(_whitelistAdmin)
        public 
    {
        require(_totalSaleCap != 0);
        require(_maxTokens != 0);
        require(_wallet != 0);
        require(_minThreshold <= _totalSaleCap);
        require(_vaultInitialAmount <= _minThreshold);
        require(now < _startTime);

        totalSaleCap = _totalSaleCap;
        minContribution = _minContribution;
        minThreshold = _minThreshold;

        // Setup the necessary contracts
        trustedToken = new Token(_maxTokens, _tokenName, _tokenSymbol, _tokenDecimals);
        disbursementHandler = new DisbursementHandler(trustedToken);

        trustedToken.setController(this);

        trustedVault = new Vault(
            _wallet,
            _vaultInitialAmount,
            _vaultDisbursementAmount, // disbursement amount
            _closingDuration
        );

        // Set the states
        setStates(states);

        allowFunction(SETUP, this.setup.selector);
        allowFunction(FREEZE, this.setEndTime.selector);
        allowFunction(SALE_IN_PROGRESS, this.setEndTime.selector);
        allowFunction(SALE_IN_PROGRESS, this.contribute.selector);
        allowFunction(SALE_IN_PROGRESS, this.endSale.selector);
        allowFunction(SALE_ENDED, this.allocateTokens.selector);

        // End the sale when the cap is reached
        addStartCondition(SALE_ENDED, wasCapReached);

        // Set the onSaleEnded callback (will be called when the sale ends)
        addCallback(SALE_ENDED, onSaleEnded);

        // Set the start and end times for the sale
        setStateStartTime(SALE_IN_PROGRESS, _startTime);
    }

    /// @dev Setup the disbursements and tokens for sale.
    /// @dev This needs to be outside the constructor because the token needs to query the sale for allowed transfers.
    function setup() public onlyOwner checkAllowed {
        require(trustedToken.transfer(disbursementHandler, disbursementHandler.totalAmount()));
        tokensForSale = trustedToken.balanceOf(this);       
        require(tokensForSale >= totalSaleCap);

        // Go to freeze state
        goToNextState();
    }

    /// @dev Called by users to contribute ETH to the sale.
    function contribute(uint256 contributionLimit, uint256 currentSaleCap, uint8 v, bytes32 r, bytes32 s) 
        external 
        payable
        checkAllowed 
    {
        // Check that the signature is valid
        require(currentSaleCap <= totalSaleCap);
        require(weiContributed < currentSaleCap);
        require(checkWhitelisted(msg.sender, contributionLimit, currentSaleCap, v, r, s));

        uint256 current = contributions[msg.sender];
        require(current < contributionLimit);

        // Get the max amount that the user can contribute
        uint256 remaining = Math.min256(contributionLimit.sub(current), currentSaleCap.sub(weiContributed));

        // Check if it goes over the contribution limit of the user or the eth cap. 
        uint256 contribution = Math.min256(msg.value, remaining);

        // Get the total contribution for the contributor after the previous checks
        uint256 totalContribution = current.add(contribution);
        require(totalContribution >= minContribution);

        contributions[msg.sender] = totalContribution;
        hasContributed[msg.sender] = true;

        weiContributed = weiContributed.add(contribution);

        trustedVault.deposit.value(contribution)(msg.sender);

        if (weiContributed >= minThreshold && trustedVault.state() != Vault.State.Success) trustedVault.saleSuccessful();

        // If there is an excess, return it to the user
        uint256 excess = msg.value.sub(contribution);
        if (excess > 0) msg.sender.transfer(excess);

        LogContribution(msg.sender, contribution, excess);

        assert(totalContribution <= contributionLimit);
    }

    /// @dev Sets the end time for the sale
    /// @param _endTime The timestamp at which the sale will end.
    function setEndTime(uint256 _endTime) external onlyOwner checkAllowed {
        require(now < _endTime);
        require(getStateStartTime(SALE_ENDED) == 0);
        setStateStartTime(SALE_ENDED, _endTime);
    }

    /// @dev Called to allocate the tokens depending on eth contributed by the end of the sale.
    /// @param _contributor The address of the contributor.
    function allocateTokens(address _contributor) external checkAllowed {
        require(contributions[_contributor] != 0);

        // Transfer the respective tokens to the contributor
        uint256 amount = contributions[_contributor].mul(tokensPerWei);

        // Set contributions to 0
        contributions[_contributor] = 0;

        require(trustedToken.transfer(_contributor, amount));

        LogTokensAllocated(_contributor, amount);
    }

    /// @dev Called to end the sale by the owner. Can only be called in SALE_IN_PROGRESS state
    function endSale() external onlyOwner checkAllowed {
        goToNextState();
    }

    /// @dev Since Sale is TokenControllerI, it has to implement transferAllowed() function
    /// @notice only the Sale and DisbursementHandler can disburse the initial tokens to their future owners
    function transferAllowed(address _from, address) external view returns (bool) {
        return _from == address(this) || _from == address(disbursementHandler);
    }

    /// @dev Called internally by the sale to setup a disbursement (it has to be called in the constructor of child sales)
    /// param _beneficiary Tokens will be disbursed to this address.
    /// param _amount Number of tokens to be disbursed.
    /// param _duration Tokens will be locked for this long.
    function setupDisbursement(address _beneficiary, uint256 _amount, uint256 _duration) internal {
        require(tokensForSale == 0);
        disbursementHandler.setupDisbursement(_beneficiary, _amount, now.add(_duration));
    }
   
    /// @dev Returns true if the cap was reached.
    function wasCapReached(bytes32) internal returns (bool) {
        return totalSaleCap <= weiContributed;
    }

    /// @dev Callback that gets called when entering the SALE_ENDED state.
    function onSaleEnded() internal {
        // If the minimum threshold wasn't reached, enable refunds
        if (weiContributed < minThreshold) {
            trustedVault.enableRefunds();
        } else {
            trustedVault.beginClosingPeriod();
            tokensPerWei = tokensForSale.div(weiContributed);
        }

        trustedToken.transferOwnership(owner); 
        trustedVault.transferOwnership(owner);
    }

}
/// @title Sale base contract
function wasCapReached(bytes32) internal returns (bool) {
    return totalSaleCap <= weiContributed;
}

/// @dev Returns true if the cap was reached.