contract ForegroundTokenSale is Ownable, ForegroundCaller {
    using SafeMath for uint256;

    uint256 public publicTokenCap;
    uint256 public baseTokenPrice;
    uint256 public currentTokenPrice;

    uint256 public priceStepDuration;

    uint256 public numberOfParticipants;
    uint256 public maxSaleBalance;
    uint256 public minSaleBalance;
    uint256 public saleBalance;
    uint256 public tokenBalance;

    uint256 public startBlock;
    uint256 public endBlock;

    address public saleWalletAddress;

    address public devTeamTokenAddress;
    address public partnershipsTokenAddress;
    address public incentiveTokenAddress;
    address public bountyTokenAddress;

    bool public saleSuspended = false;

    DealToken public dealToken;
    SaleState public state;

    mapping (address => PurchaseDetails) public purchases;

    struct PurchaseDetails {
        uint256 tokenBalance;
        uint256 weiBalance;
    }

    enum SaleState {Prepared, Deployed, Configured, Started, Ended, Finalized, Refunding}

    event TokenPurchased(address indexed buyer, uint256 tokenPrice, uint256 txAmount, uint256 actualPurchaseAmount, uint256 refundedAmount, uint256 tokensPurchased);
    event SaleStarted();
    event SaleEnded();
    event Claimed(address indexed owner, uint256 tokensClaimed);
    event Refunded(address indexed buyer, uint256 amountRefunded);

    /**
     * @dev - modifier that evaluates which state the sale should be in. Functions that use this modifier cannot be constant due to potential state change
     */
    modifier evaluateSaleState {
        require(saleSuspended == false);

        if (state == SaleState.Configured && block.number >= startBlock) {
            state = SaleState.Started;
            SaleStarted();
        }

        if (state == SaleState.Started) {
            setCurrentPrice();
        }

        if (state == SaleState.Started && (block.number > endBlock || saleBalance == maxSaleBalance || maxSaleBalance.sub(saleBalance) < currentTokenPrice)) {
            endSale();
        }

        if (state == SaleState.Ended) {
            finalizeSale();
        }
        _;
    }

    /**
     * @dev - Constructor for the Foreground token sale contract
     * @param _publicTokenCap - Max number of tokens made available to the public
     * @param _tokenFloor - Min number of tokens to be sold to be considered a successful sale
     * @param _tokenRate - Initial price per token
     * @param _foreground - Address of the Foreground contract that gets passed on to ForegroundCaller
     */
    function ForegroundTokenSale(
        uint256 _publicTokenCap,
        uint256 _tokenFloor,
        uint256 _tokenRate,
        IForeground _foreground
    )
        public
        ForegroundCaller(_foreground)
    {
        require(_publicTokenCap > 0);
        require(_tokenFloor < _publicTokenCap);
        require(_tokenRate > 0);

        publicTokenCap = _publicTokenCap;
        baseTokenPrice = _tokenRate;
        currentTokenPrice = _tokenRate;

        dealToken = new DealToken();
        maxSaleBalance = publicTokenCap.mul(currentTokenPrice);
        minSaleBalance = _tokenFloor.mul(currentTokenPrice);
        state = SaleState.Deployed;
    }

    /**
     * @dev - Default payable function. Will result in tokens being purchased
     */
    function() public payable {
        purchaseToken(msg.sender, msg.value);
    }

    /**
     * @dev - Configure specific params of the sale. Can only be called once
     * @param _startBlock - Block the sale should start at
     * @param _endBlock - Block the sale should end at
     * @param _wallet - Sale wallet address - funds will be transferred here once sale is done
     * @param _stepDuration - How many blocks to wait to increase price
     * @param _devAddress - Address for the tokens distributed for Foreground development purposes
     * @param _partnershipAddress - Address for the tokens distributed for Foreground partnerships
     * @param _incentiveAddress - Address for the tokens distributed for Foreground incentives
     * @param _bountyAddress - Address for the tokens distributed for Foreground bounties
     */
    function configureSale(
        uint256 _startBlock,
        uint256 _endBlock,
        address _wallet,
        uint256 _stepDuration,
        address _devAddress,
        address _partnershipAddress,
        address _incentiveAddress,
        address _bountyAddress
    )
        external
        onlyOwner
    {
        require(_startBlock >= block.number);
        require(_endBlock >= _startBlock);
        require(state == SaleState.Deployed);
        require(_wallet != 0x0);
        require(_stepDuration > 0);
        require(_devAddress != 0x0);
        require(_partnershipAddress != 0x0);
        require(_incentiveAddress != 0x0);
        require(_bountyAddress != 0x0);

        state = SaleState.Configured;
        startBlock = _startBlock;
        endBlock = _endBlock;
        saleWalletAddress = _wallet;
        priceStepDuration = _stepDuration;
        devTeamTokenAddress = _devAddress;
        partnershipsTokenAddress = _partnershipAddress;
        incentiveTokenAddress = _incentiveAddress;
        bountyTokenAddress = _bountyAddress;
    }

    /**
     * @dev - Claim tokens once sale is over
     */
    function claimToken()
        external
        evaluateSaleState
    {
        require(state == SaleState.Finalized);
        require(purchases[msg.sender].tokenBalance > 0);

        uint256 _tokensPurchased = purchases[msg.sender].tokenBalance;
        purchases[msg.sender].tokenBalance = 0;
        purchases[msg.sender].weiBalance = 0;

        /* Transfer the tokens */
        dealToken.transfer(msg.sender, _tokensPurchased);
        Claimed(msg.sender, _tokensPurchased);
    }

    /**
     * @dev - Claim a refund if the token sale did not reach its minimum value
     */
    function claimRefund()
        external
    {
        require(state == SaleState.Refunding);

        uint256 _amountToRefund = purchases[msg.sender].weiBalance;
        require(_amountToRefund > 0);
        purchases[msg.sender].weiBalance = 0;
        purchases[msg.sender].tokenBalance = 0;
        msg.sender.transfer(_amountToRefund);
        Refunded(msg.sender, _amountToRefund);
    }

    /**
     * @dev - Ability for contract owner to suspend the sale if necessary
     * @param _suspend - Boolean value to indicate whether the sale is suspended or not
     */
    function suspendSale(bool _suspend)
        external
        onlyOwner
    {
        saleSuspended = _suspend;
    }

    /**
     * @dev - Returns the correct sale state based on the current block number
     * @return - current sale state and current sale price
     */
    function updateLatestSaleState()
        external
        evaluateSaleState
        returns (uint256)
    {
        return uint256(state);
    }

    /**
     * @dev - Purchase a DEAL token. Sale must be in the correct state
     * @param _recipient - address to assign the purchased tokens to
     * @param _amount - eth value of tokens to be purchased
     */
    function purchaseToken(address _recipient, uint256 _amount)
        internal
        evaluateSaleState
        returns (uint256)
    {
        require(state == SaleState.Started);
        require(_amount >= currentTokenPrice);

        uint256 _saleRemainingBalance = maxSaleBalance.sub(saleBalance);
        bool _shouldEndSale = false;

        /* Ensure purchaseAmount buys exact amount of tokens, refund the rest immediately */
        uint256 _amountToRefund = _amount % currentTokenPrice;
        uint256 _purchaseAmount = _amount.sub(_amountToRefund);

        /* This purchase will push us over the max balance - so refund that amount that is over */
        if (_saleRemainingBalance < _purchaseAmount) {
            uint256 _endOfSaleRefund = _saleRemainingBalance % currentTokenPrice;
            _amountToRefund = _amountToRefund.add(_purchaseAmount.sub(_saleRemainingBalance).add(_endOfSaleRefund));
            _purchaseAmount = _saleRemainingBalance.sub(_endOfSaleRefund);
            _shouldEndSale = true;
        }

        /* Count the number of unique participants */
        if (purchases[_recipient].tokenBalance == 0) {
            numberOfParticipants = numberOfParticipants.add(1);
        }

        uint256 _tokensPurchased = _purchaseAmount.div(currentTokenPrice);
        purchases[_recipient].tokenBalance = purchases[_recipient].tokenBalance.add(_tokensPurchased);
        purchases[_recipient].weiBalance = purchases[_recipient].weiBalance.add(_purchaseAmount);
        saleBalance = saleBalance.add(_purchaseAmount);
        tokenBalance = tokenBalance.add(_tokensPurchased);

        if (_purchaseAmount == _saleRemainingBalance || _shouldEndSale) {
            endSale();
        }

        /* Refund amounts due if there are any */
        if (_amountToRefund > 0) {
            _recipient.transfer(_amountToRefund);
        }

        TokenPurchased(_recipient, currentTokenPrice, msg.value, _purchaseAmount, _amountToRefund, _tokensPurchased);
        return _purchaseAmount;
    }

    /**
     * @dev - Implementation of Foreground function to receive payment
     * @param _originatingAddress - address to assign the purchased tokens to
     * @param _amount - eth value of tokens to be purchased
     * @return - The actual amount spent to buy tokens after taking sale state and refunds into account
     */
    function receiveEtherFromRelayAddress(address _originatingAddress, uint256 _amount)
        internal
        returns (uint256)
    {
        return purchaseToken(_originatingAddress, _amount);
    }

    /**
     * @dev - Internal function to calculate and store the current token price based on block number
     */
    function setCurrentPrice() internal {
        uint256 _saleBlockNo = block.number - startBlock;
        uint256 _numIncreases = _saleBlockNo.div(priceStepDuration);

        if (_numIncreases == 0)
            currentTokenPrice = baseTokenPrice;
        else if (_numIncreases == 1)
            currentTokenPrice = 0.06 ether;
        else if (_numIncreases == 2)
            currentTokenPrice = 0.065 ether;
        else if (_numIncreases == 3)
            currentTokenPrice = 0.07 ether;
        else if (_numIncreases >= 4)
            currentTokenPrice = 0.08 ether;
    }

    /**
     * @dev - Sale end condition reached, determine if sale was successful and set state accordingly
     */
    function endSale() internal {
        /* If we didn't reach the min value - set state to refund so that funds can reclaimed by sale participants */
        if (saleBalance < minSaleBalance) {
            state = SaleState.Refunding;
        } else {
            state = SaleState.Ended;
            /* Mint the tokens and distribute internally */
            mintTokens();
        }
        SaleEnded();
    }

    /**
     * @dev - Mints tokens and distributes pre-allocated tokens to Foreground addresses
     */
    function mintTokens() internal {
        uint256 _totalTokens = (tokenBalance.mul(10 ** 18)).div(74).mul(100);

        /* Mint the tokens and assign them all to the TokenSaleContract for distribution */
        dealToken.mint(address(this), _totalTokens.div(10 ** 18));

        /* Distribute non public tokens */
        dealToken.transfer(devTeamTokenAddress, (_totalTokens.mul(10).div(100)).div(10 ** 18));
        dealToken.transfer(partnershipsTokenAddress, (_totalTokens.mul(10).div(100)).div(10 ** 18));
        dealToken.transfer(incentiveTokenAddress, (_totalTokens.mul(4).div(100)).div(10 ** 18));
        dealToken.transfer(bountyTokenAddress, (_totalTokens.mul(2).div(100)).div(10 ** 18));

        /* Finish minting so that no more tokens can be minted */
        dealToken.finishMinting();
    }

    /**
     * @dev - Finalizes the sale transfers the contract balance to the sale wallet.
     */
    function finalizeSale() internal {
        state = SaleState.Finalized;
        /* Transfer contract balance to sale wallet */
        saleWalletAddress.transfer(this.balance);
    }
}
function finalizeSale() internal {
    state = SaleState.Finalized;
    /* Transfer contract balance to sale wallet */
    saleWalletAddress.transfer(this.balance);
}

/**
 * @dev - Finalizes the sale transfers the contract balance to the sale wallet.
 */