contract WMCMarketplace is Ownable {

    // OpenZeppelin's SafeMath library is used for all arithmetic operations to avoid overflows/underflows.
    using SafeMath for uint256;

    /* ********** */
    /* DATA TYPES */
    /* ********** */

    /* ****** */
    /* EVENTS */
    /* ****** */

    event CardPurchasedWithWMC(uint256 cardId, uint256 wmcSpent);
    event DevFeeUpdated(uint256 newDevFee);

    /* ******* */
    /* STORAGE */
    /* ******* */



    /* ********* */
    /* CONSTANTS */
    /* ********* */

    // Mainnet
    address marbleCoreAddress = 0x1d963688FE2209A98dB35C67A041524822Cf04ff;
    address marbleAuctionAddress = 0x649EfF2dC5d9c5C641260C8B9BedE4770FCCF5E7;
    address wrappedCardsAddress = 0x8AedB297FED4b6884b808ee61fAf0837713670d0;
    address uniswapExchangeAddress = 0xA0db39d28dACeC1974f2a1F6Bac7d33F37C102eC;

    uint256 devFeeInBasisPoints = 375;

    /* ********* */
    /* FUNCTIONS */
    /* ********* */

    function buyCardWithWMC(uint256 _cardId, uint256 _maxWMCWeiToSpend) external returns (bool) {
        // Transfer user WMC to sales contract for processing
        bool _WMCTransferToContract = IERC20(wrappedCardsAddress).transferFrom(msg.sender, address(this), _maxWMCWeiToSpend);
        // Verify transfer returns true, this should never fail
        require(_WMCTransferToContract, "WMC Transfer was unsuccessful");
        // Pass in the cardId and get the price in wei
        uint256 costInWei = getCurrentPrice(_cardId);
        // Swap WMC to ETH via the Uniswap exchange, adding a small amount for the dev fee
        uint256 tokensSold = UniswapExchangeInterface(uniswapExchangeAddress).tokenToEthSwapOutput(_computePriceWithDevFee(costInWei), _maxWMCWeiToSpend, ~uint256(0));
        // Send ETH to the Marble Auction account and send the card to this contract
        MarbleDutchAuctionInterface(marbleAuctionAddress).bid.value(costInWei)(_cardId);
        // Refund the excess WMC to the buyer that wasn't expended in the purchase.
        bool _WMCRefundToBuyer = IERC20(wrappedCardsAddress).transfer(msg.sender, _maxWMCWeiToSpend.sub(tokensSold));
        // The last function should return true, if it didn't something has gone horribly wrong and we need to revert everything we've done here.
        require(_WMCRefundToBuyer, "Error processing WMC refund.");
        // Transfer the purchased card from this contract to the sender account.
        CardCore(marbleCoreAddress).transferFrom(address(this), msg.sender, _cardId);
        // Tell everyone.
        emit CardPurchasedWithWMC(_cardId, tokensSold);
        return true;
    }

    // Alias for Auction.getCurrentPrice()
    function getCurrentPrice(uint256 _cardId) public view returns (uint256) {
        return MarbleDutchAuctionInterface(marbleAuctionAddress).getCurrentPrice(_cardId);
    }

    function totalAuctions() public view returns (uint256) {
        return MarbleDutchAuctionInterface(marbleAuctionAddress).totalAuctions();
    }

    function getAuction(uint256 _tokenId) public view returns (address seller, uint256 startingPrice,
        uint256 endingPrice, uint256 duration, uint256 startedAt, bool canBeCanceled) {
        return MarbleDutchAuctionInterface(marbleAuctionAddress).getAuction(_tokenId);
    }

    function isOnAuction(uint256 _tokenId) external view returns (bool isIndeed) {
        return MarbleDutchAuctionInterface(marbleAuctionAddress).isOnAuction(_tokenId);
    }

    function tokenOfSellerByIndex(address _seller, uint256 _index) public view returns (uint256) {
        return MarbleDutchAuctionInterface(marbleAuctionAddress).tokenOfSellerByIndex(_seller, _index);
    }

    function totalAuctionsBySeller(address _seller) public view returns (uint256) {
        return MarbleDutchAuctionInterface(marbleAuctionAddress).totalAuctionsBySeller(_seller);
    }

    // Uniswap view aliases
    function getEthToTokenInputPrice(uint256 eth_sold) external view returns (uint256 tokens_bought) {
        return UniswapExchangeInterface(uniswapExchangeAddress).getEthToTokenInputPrice(eth_sold);
    }

    function getEthToTokenOutputPrice(uint256 tokens_bought) external view returns (uint256 eth_sold) {
        return UniswapExchangeInterface(uniswapExchangeAddress).getEthToTokenOutputPrice(tokens_bought);
    }

    function getTokenToEthInputPrice(uint256 tokens_sold) external view returns (uint256 eth_bought) {
        return UniswapExchangeInterface(uniswapExchangeAddress).getTokenToEthInputPrice(tokens_sold);
    }

    function getTokenToEthOutputPrice(uint256 eth_bought) external view returns (uint256 tokens_sold) {
        return UniswapExchangeInterface(uniswapExchangeAddress).getTokenToEthOutputPrice(eth_bought);
    }

    // Not sure if marketplace contract will change in the future, so leaving the option here.
    function changeMarbleAuctionAddress(address _auctionContract) public onlyOwner returns (bool) {
        require(isContract(_auctionContract));
        marbleAuctionAddress = _auctionContract;
        return true;
    }

    function transferERC20(address _erc20Address, address _to, uint256 _value) external onlyOwner returns (bool) {
        return IERC20(_erc20Address).transfer(_to, _value);
    }

    function withdrawOwnerEarnings() external onlyOwner returns (bool) {
        msg.sender.transfer(address(this).balance);
        return true;
    }

    function updateFee(uint256 _newFee) external onlyOwner returns (bool) {
        devFeeInBasisPoints = _newFee;
        emit DevFeeUpdated(_newFee);
        return true;
    }

    function isContract(address addr) internal view returns (bool) {
        uint size;
        assembly { size := extcodesize(addr) }
        return size > 0;
    }

    constructor() public {
        IERC20(wrappedCardsAddress).approve(uniswapExchangeAddress, ~uint256(0));
    }

    function() external payable {}

    function _computePriceWithDevFee(uint256 _costInWei) internal view returns (uint256) {
        return (_costInWei.mul(uint256(10000).add(devFeeInBasisPoints))).div(uint256(10000));
    }
}
function buyCardWithWMC(uint256 _cardId, uint256 _maxWMCWeiToSpend) external returns (bool) {
    // Transfer user WMC to sales contract for processing
    bool _WMCTransferToContract = IERC20(wrappedCardsAddress).transferFrom(msg.sender, address(this), _maxWMCWeiToSpend);
    // Verify transfer returns true, this should never fail
    require(_WMCTransferToContract, "WMC Transfer was unsuccessful");
    // Pass in the cardId and get the price in wei
    uint256 costInWei = getCurrentPrice(_cardId);
    // Swap WMC to ETH via the Uniswap exchange, adding a small amount for the dev fee
    uint256 tokensSold = UniswapExchangeInterface(uniswapExchangeAddress).tokenToEthSwapOutput(_computePriceWithDevFee(costInWei), _maxWMCWeiToSpend, ~uint256(0));
    // Send ETH to the Marble Auction account and send the card to this contract
    MarbleDutchAuctionInterface(marbleAuctionAddress).bid.value(costInWei)(_cardId);
    // Refund the excess WMC to the buyer that wasn't expended in the purchase.
    bool _WMCRefundToBuyer = IERC20(wrappedCardsAddress).transfer(msg.sender, _maxWMCWeiToSpend.sub(tokensSold));
    // The last function should return true, if it didn't something has gone horribly wrong and we need to revert everything we've done here.
    require(_WMCRefundToBuyer, "Error processing WMC refund.");
    // Transfer the purchased card from this contract to the sender account.
    CardCore(marbleCoreAddress).transferFrom(address(this), msg.sender, _cardId);
    // Tell everyone.
    emit CardPurchasedWithWMC(_cardId, tokensSold);
    return true;
}

/* ********* */