pragma solidity >=0.0;
import "../Markets/Market.sol";
import "../Tokens/Token.sol";
import "../Events/Event.sol";
import "../MarketMakers/MarketMaker.sol";


/// @title Market factory contract - Allows to create market contracts
/// @author Stefan George - <stefan@gnosis.pm>
contract StandardMarket is Market {
    using Math for *;

    /*
     *  Constants
     */
    uint24 public constant FEE_RANGE = 1000000; // 100%

    /*
     *  Modifiers
     */
    modifier isCreator () {
        // Only creator is allowed to proceed
        require(msg.sender == creator);
        _;
    }

    modifier atStage(Stages _stage) {
        // Contract has to be in given stage
        require(stage == _stage);
        _;
    }

    /*
     *  Public functions
     */
    /// @dev Constructor validates and sets market properties
    /// @param _creator Market creator
    /// @param _eventContract Event contract
    /// @param _marketMaker Market maker contract
    /// @param _fee Market fee
    constructor(address _creator, Event _eventContract, MarketMaker _marketMaker, uint24 _fee)
    {
        // Validate inputs
        require(address(_eventContract) != address(0) && address(_marketMaker) != address(0) && _fee < FEE_RANGE);
        creator = _creator;
        createdAtBlock = block.number;
        eventContract = _eventContract;
        netOutcomeTokensSold = new int[](eventContract.getOutcomeCount());
        fee = _fee;
        marketMaker = _marketMaker;
        stage = Stages.MarketCreated;
    }

    /// @dev Allows to fund the market with collateral tokens converting them into outcome tokens
    /// @param _funding Funding amount
    function fund(uint _funding)
        public
        override
        isCreator
        atStage(Stages.MarketCreated)
    {
        // Request collateral tokens and allow event contract to transfer them to buy all outcomes
        require(   eventContract.collateralToken().transferFrom(msg.sender, address(this), _funding)
                && eventContract.collateralToken().approve(address(eventContract), _funding));
        eventContract.buyAllOutcomes(_funding);
        funding = _funding;
        stage = Stages.MarketFunded;
        emit MarketFunding(funding);
    }

    /// @dev Allows market creator to close the markets by transferring all remaining outcome tokens to the creator
    function close()
        override
        public
        isCreator
        atStage(Stages.MarketFunded)
    {
        uint8 outcomeCount = eventContract.getOutcomeCount();
        for (uint8 i = 0; i < outcomeCount; i++)
            require(eventContract.outcomeTokens(i).transfer(creator, eventContract.outcomeTokens(i).balanceOf(address(this))));
        stage = Stages.MarketClosed;
        emit MarketClosing();
    }

    /// @dev Allows market creator to withdraw fees generated by trades
    /// @return fees Fee amount
    function withdrawFees()
        public
        override
        isCreator
        returns (uint fees)
    {
        fees = eventContract.collateralToken().balanceOf(address(this));
        // Transfer fees
        require(eventContract.collateralToken().transfer(creator, fees));
        emit FeeWithdrawal(fees);
    }

    /// @dev Allows to buy outcome tokens from market maker
    /// @param outcomeTokenIndex Index of the outcome token to buy
    /// @param outcomeTokenCount Amount of outcome tokens to buy
    /// @param maxCost The maximum cost in collateral tokens to pay for outcome tokens
    /// @return cost Cost in collateral tokens
    function buy(uint8 outcomeTokenIndex, uint outcomeTokenCount, uint maxCost)
        public
        override
        atStage(Stages.MarketFunded)
        returns (uint cost)
    {
        // Calculate cost to buy outcome tokens
        uint outcomeTokenCost = marketMaker.calcCost(this, outcomeTokenIndex, outcomeTokenCount);
        // Calculate fees charged by market
        uint fees = calcMarketFee(outcomeTokenCost);
        cost = outcomeTokenCost.add(fees);
        // Check cost doesn't exceed max cost
        require(cost > 0 && cost <= maxCost);
        // Transfer tokens to markets contract and buy all outcomes
        require(   eventContract.collateralToken().transferFrom(msg.sender, address(this), cost)
                && eventContract.collateralToken().approve(address(eventContract), outcomeTokenCost));
        // Buy all outcomes
        eventContract.buyAllOutcomes(outcomeTokenCost);
        // Transfer outcome tokens to buyer
        require(eventContract.outcomeTokens(outcomeTokenIndex).transfer(msg.sender, outcomeTokenCount));
        // Add outcome token count to market maker net balance
        require(int(outcomeTokenCount) >= 0);
        netOutcomeTokensSold[outcomeTokenIndex] = netOutcomeTokensSold[outcomeTokenIndex].add(int(outcomeTokenCount));
        emit OutcomeTokenPurchase(msg.sender, outcomeTokenIndex, outcomeTokenCount, cost);
    }

    /// @dev Allows to sell outcome tokens to market maker
    /// @param outcomeTokenIndex Index of the outcome token to sell
    /// @param outcomeTokenCount Amount of outcome tokens to sell
    /// @param minProfit The minimum profit in collateral tokens to earn for outcome tokens
    /// @return profit Profit in collateral tokens
    function sell(uint8 outcomeTokenIndex, uint outcomeTokenCount, uint minProfit)
        public
        override
        atStage(Stages.MarketFunded)
        returns (uint profit)
    {
        // Calculate profit for selling outcome tokens
        uint outcomeTokenProfit = marketMaker.calcProfit(this, outcomeTokenIndex, outcomeTokenCount);
        // Calculate fee charged by market
        uint fees = calcMarketFee(outcomeTokenProfit);
        profit = outcomeTokenProfit.sub(fees);
        // Check profit is not too low
        require(profit > 0 && profit >= minProfit);
        // Transfer outcome tokens to markets contract to sell all outcomes
        require(eventContract.outcomeTokens(outcomeTokenIndex).transferFrom(msg.sender, address(this), outcomeTokenCount));
        // Sell all outcomes
        eventContract.sellAllOutcomes(outcomeTokenProfit);
        // Transfer profit to seller
        require(eventContract.collateralToken().transfer(msg.sender, profit));
        // Subtract outcome token count from market maker net balance
        require(int(outcomeTokenCount) >= 0);
        netOutcomeTokensSold[outcomeTokenIndex] = netOutcomeTokensSold[outcomeTokenIndex].sub(int(outcomeTokenCount));
        emit OutcomeTokenSale(msg.sender, outcomeTokenIndex, outcomeTokenCount, profit);
    }

    /// @dev Buys all outcomes, then sells all shares of selected outcome which were bought, keeping
    ///      shares of all other outcome tokens.
    /// @param outcomeTokenIndex Index of the outcome token to short sell
    /// @param outcomeTokenCount Amount of outcome tokens to short sell
    /// @param minProfit The minimum profit in collateral tokens to earn for short sold outcome tokens
    /// @return cost Cost to short sell outcome in collateral tokens
    function shortSell(uint8 outcomeTokenIndex, uint outcomeTokenCount, uint minProfit)
        public
        override
        returns (uint cost)
    {
        // Buy all outcomes
        require(   eventContract.collateralToken().transferFrom(msg.sender, address(this), outcomeTokenCount)
                && eventContract.collateralToken().approve(address(eventContract), outcomeTokenCount));
        eventContract.buyAllOutcomes(outcomeTokenCount);
        // Short sell selected outcome
        eventContract.outcomeTokens(outcomeTokenIndex).approve(address(this), outcomeTokenCount);
        uint profit = this.sell(outcomeTokenIndex, outcomeTokenCount, minProfit);
        cost = outcomeTokenCount - profit;
        // Transfer outcome tokens to buyer
        uint8 outcomeCount = eventContract.getOutcomeCount();
        for (uint8 i = 0; i < outcomeCount; i++)
            if (i != outcomeTokenIndex)
                require(eventContract.outcomeTokens(i).transfer(msg.sender, outcomeTokenCount));
        // Send change back to buyer
        require(eventContract.collateralToken().transfer(msg.sender, profit));
        emit OutcomeTokenShortSale(msg.sender, outcomeTokenIndex, outcomeTokenCount, cost);
    }

    /// @dev Calculates fee to be paid to market maker
    /// @param outcomeTokenCost Cost for buying outcome tokens
    /// @return Fee for trade
    function calcMarketFee(uint outcomeTokenCost)
        public
        override
        view
        returns (uint)
    {
        return outcomeTokenCost * fee / FEE_RANGE;
    }
}
