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

    event LogContribution(address indexed contributor, uint256 amountSent, uint256 excessRefunded);
    event LogTokenAllocation(address indexed contributor, uint256 contribution, uint256 tokens);
    event LogDisbursement(address indexed beneficiary, uint256 tokens);

    // Stages for the state machine
    bytes32 public constant SETUP = "setup";
    bytes32 public constant SETUP_DONE = "setupDone";
    bytes32 public constant SALE_IN_PROGRESS = "saleInProgress";
    bytes32 public constant SALE_ENDED = "saleEnded";

    mapping(address => uint256) public contributions;

    uint256 public weiContributed = 0;
    uint256 public contributionCap;

    // Wallet where funds will be sent
    address public wallet;

    MintableToken public token;

    DisbursementHandler public disbursementHandler;

    function Sale(
        address _wallet, 
        uint256 _contributionCap
    ) 
        public 
    {
        require(_wallet != 0);
        require(_contributionCap != 0);

        wallet = _wallet;

        token = createTokenContract();
        disbursementHandler = new DisbursementHandler(token);

        contributionCap = _contributionCap;

        setupStages();
    }

    function() external payable {
        contribute();
    }

    /// @dev Sets the start timestamp for the SALE_IN_PROGRESS stage.
    /// @param timestamp The start timestamp.
    function setSaleStartTime(uint256 timestamp) 
        external 
        onlyOwner 
        checkAllowed
    {
        // require(_startTime < getStageStartTime(SALE_ENDED));
        setStageStartTime(SALE_IN_PROGRESS, timestamp);
    }

    /// @dev Sets the start timestamp for the SALE_ENDED stage.
    /// @param timestamp The start timestamp.
    function setSaleEndTime(uint256 timestamp) 
        external 
        onlyOwner 
        checkAllowed
    {
        require(getStageStartTime(SALE_IN_PROGRESS) < timestamp);
        setStageStartTime(SALE_ENDED, timestamp);
    }

    /// @dev Called in the SETUP stage, check configurations and to go to the SETUP_DONE stage.
    function setupDone() 
        public 
        onlyOwner 
        checkAllowed
    {
        uint256 _startTime = getStageStartTime(SALE_IN_PROGRESS);
        uint256 _endTime = getStageStartTime(SALE_ENDED);
        require(block.timestamp < _startTime);
        require(_startTime < _endTime);

        state.goToNextStage();
    }

    /// @dev Called by users to contribute ETH to the sale.
    function contribute() 
        public 
        payable
        checkAllowed 
    {
        require(msg.value > 0);   

        uint256 contributionLimit = getContributionLimit(msg.sender);
        require(contributionLimit > 0);

        // Check that the user is allowed to contribute
        uint256 totalContribution = contributions[msg.sender].add(msg.value);
        uint256 excess = 0;

        // Check if it goes over the eth cap for the sale.
        if (weiContributed.add(msg.value) > contributionCap) {
            // Subtract the excess
            excess = weiContributed.add(msg.value).sub(contributionCap);
            totalContribution = totalContribution.sub(excess);
        }

        // Check if it goes over the contribution limit of the user. 
        if (totalContribution > contributionLimit) {
            excess = excess.add(totalContribution).sub(contributionLimit);
            contributions[msg.sender] = contributionLimit;
        } else {
            contributions[msg.sender] = totalContribution;
        }

        // We are only able to refund up to msg.value because the contract will not contain ether
        // excess = excess < msg.value ? excess : msg.value;
        require(excess <= msg.value);

        weiContributed = weiContributed.add(msg.value).sub(excess);

        if (excess > 0) {
            msg.sender.transfer(excess);
        }

        wallet.transfer(this.balance);

        assert(contributions[msg.sender] <= contributionLimit);
        LogContribution(msg.sender, msg.value, excess);
    }

    /// @dev Create a disbursement of tokens.
    /// @param beneficiary The beneficiary of the disbursement.
    /// @param tokenAmount Amount of tokens to be locked.
    /// @param timestamp Tokens will be locked until this timestamp.
    function distributeTimelockedTokens(
        address beneficiary,
        uint256 tokenAmount,
        uint256 timestamp
    ) 
        public
        onlyOwner
        checkAllowed
    { 
        disbursementHandler.setupDisbursement(
            beneficiary,
            tokenAmount,
            timestamp
        );
        token.mint(disbursementHandler, tokenAmount);
        LogDisbursement(beneficiary, tokenAmount);
    }
    
    function setupStages() internal {
        // Set the stages
        state.setInitialStage(SETUP);
        state.createTransition(SETUP, SETUP_DONE);
        state.createTransition(SETUP_DONE, SALE_IN_PROGRESS);
        state.createTransition(SALE_IN_PROGRESS, SALE_ENDED);

        state.allowFunction(SETUP, this.distributeTimelockedTokens.selector);
        state.allowFunction(SETUP, this.setSaleStartTime.selector);
        state.allowFunction(SETUP, this.setSaleEndTime.selector);
        state.allowFunction(SETUP, this.setupDone.selector);
        state.allowFunction(SALE_IN_PROGRESS, this.contribute.selector);
        state.allowFunction(SALE_IN_PROGRESS, 0); // fallback
    }

    // Override in the child sales
    function createTokenContract() internal returns (MintableToken);
    function getContributionLimit(address userAddress) public view returns (uint256);

    /// @dev Stage start conditions.
    function startConditions(bytes32 stageId) internal constant returns (bool) {
        // If the cap has been reached, end the sale.
        if (stageId == SALE_ENDED && contributionCap <= weiContributed) {
            return true;
        }
        return super.startConditions(stageId);
    }

    /// @dev State transitions callbacks.
    function onTransition(bytes32 stageId) internal {
        if (stageId == SALE_ENDED) { 
            onSaleEnded(); 
        }
        super.onTransition(stageId);
    }

    /// @dev Callback that gets called when entering the SALE_ENDED stage.
    function onSaleEnded() internal {}
}
function onSaleEnded() internal {}
/// @dev Callback that gets called when entering the SALE_ENDED stage.