contract MappedMarketplace {
    EtherGenCore storageContract; // Main card storage and ERC721 transfer functionality
    TransferRestrictionVerifier transferRestrictionVerifier; // Allows future stuff (loan marketplace etc.) to restrict listing same card twice
    
    uint24 private constant removalDuration = 14 days; // Listings can be pruned from market after 14 days
    uint8 private constant marketCut = 100; // 1% (not 100% as it is divided)
    address public owner = 0x08F4aE96b647B30177cc15B21195960625BA4163;
    bool public paused = false;

    mapping(uint64 => Listing) private listings;
    mapping(address => bool) private whitelistedContracts;
    uint64[] private listedCardIds;

    struct Listing {
        uint64 listingPointer; // Index in the Market's listings
        
        uint64 cardId;
        uint64 listTime; // Seconds
        uint128 startPrice;
        uint128 endPrice;
        uint24 priceChangeDuration; // Seconds
    }
    
    function isListed(uint64 cardId) public constant returns(bool) {
        if (listedCardIds.length == 0) return false;
        return (listings[cardId].listTime > 0);
    }
    
    function getMarketSize() external constant returns(uint) {
        return listedCardIds.length;
    }
    
    function listCard(uint64 cardId, uint128 startPrice, uint128 endPrice, uint24 priceChangeDuration) external {
        require(storageContract.ownerOf(cardId) == msg.sender);
        require(storageContract.isCardTradeable(cardId));
        require(transferRestrictionVerifier.isAvailableForTransfer(cardId));
        require(isWhitelisted(msg.sender));
        require(!paused);
        require(startPrice > 99 szabo && startPrice <= 10 ether);
        require(endPrice > 99 szabo && endPrice <= 10 ether);
        require(priceChangeDuration > 21599 && priceChangeDuration < 259201); // 6-72 Hours
       
        listings[cardId] = Listing(0, cardId, uint64(now), startPrice, endPrice, priceChangeDuration);
        listings[cardId].listingPointer = uint64(listedCardIds.push(cardId) - 1);
    }
    
    
    function purchaseCard(uint64 cardId) payable external {
        require(isListed(cardId));
        require(!paused);

        uint256 price = getCurrentPrice(listings[cardId].startPrice, listings[cardId].endPrice, listings[cardId].priceChangeDuration, (uint64(now) - listings[cardId].listTime));
        require(msg.value >= price);
        
        address seller = storageContract.ownerOf(cardId);
        uint256 sellerProceeds = price - (price / marketCut); // 1% cut
        
        removeListingInternal(cardId);
        seller.transfer(sellerProceeds);
        
        uint256 bidExcess = msg.value - price;
        if (bidExcess > 1 szabo) { // Little point otherwise they'll just pay more in gas
            msg.sender.transfer(bidExcess);
        }
        
        storageContract.transferFrom(seller, msg.sender, cardId);
    }
    
    function getCurrentPrice(uint128 startPrice, uint128 endPrice, uint24 priceChangeDuration, uint64 secondsSinceListing) public constant returns (uint256) {
        if (secondsSinceListing >= priceChangeDuration) {
            return endPrice;
        } else {
            int256 totalPriceChange = int256(endPrice) - int256(startPrice); // Can be negative
            int256 currentPriceChange = totalPriceChange * int256(secondsSinceListing) / int256(priceChangeDuration);
            return uint256(int256(startPrice) + currentPriceChange);
        }
    }
    
    function removeListing(uint64 cardId) external {
        require(isListed(cardId));
        require(!paused);
        require(storageContract.ownerOf(cardId) == msg.sender || (now - listings[cardId].listTime) > removalDuration);
        removeListingInternal(cardId);
    }
    
    function removeListingInternal(uint64 cardId) internal {
        if (listedCardIds.length > 1) {
            uint64 rowToDelete = listings[cardId].listingPointer;
            uint64 keyToMove = listedCardIds[listedCardIds.length - 1];
            
            listedCardIds[rowToDelete] = keyToMove;
            listings[keyToMove].listingPointer = rowToDelete;
        }
        
        listedCardIds.length--;
        delete listings[cardId];
    }
    
    
    function getListings() external constant returns (uint64[], address[], uint64[], uint128[], uint128[], uint24[], uint8[14][]) {
        uint64[] memory cardIds = new uint64[](listedCardIds.length); // Not ideal but web3.js didn't like returning multiple 2d arrays
        address[] memory cardOwners = new address[](listedCardIds.length);
        uint64[] memory listTimes = new uint64[](listedCardIds.length);
        uint128[] memory startPrices = new uint128[](listedCardIds.length);
        uint128[] memory endPrices = new uint128[](listedCardIds.length);
        uint24[] memory priceChangeDurations = new uint24[](listedCardIds.length);
        uint8[14][] memory cardDetails = new uint8[14][](listedCardIds.length);
        
        for (uint64 i = 0; i < listedCardIds.length; i++) {
            Listing memory listing = listings[listedCardIds[i]];
            cardDetails[i] = storageContract.getCard(listing.cardId);
            cardOwners[i] = storageContract.ownerOf(listing.cardId);
            cardIds[i] = listing.cardId;
            listTimes[i] = listing.listTime;
            startPrices[i] = listing.startPrice;
            endPrices[i] = listing.endPrice;
            priceChangeDurations[i] = listing.priceChangeDuration;
        }
        return (cardIds, cardOwners, listTimes, startPrices, endPrices, priceChangeDurations, cardDetails);
    }
    
    function getListingAtPosition(uint64 i) external constant returns (uint128[5]) {
        Listing memory listing = listings[listedCardIds[i]];
        return ([listing.cardId, listing.listTime, listing.startPrice, listing.endPrice, listing.priceChangeDuration]);
    }
    
    function getListing(uint64 cardId) external constant returns (uint128[5]) {
        Listing memory listing = listings[cardId];
        return ([listing.cardId, listing.listTime, listing.startPrice, listing.endPrice, listing.priceChangeDuration]);
    }
    
    // Contracts can't list cards without contacting us (wallet addresses are unaffected)
    function isWhitelisted(address seller) internal constant returns (bool) {
        uint size;
        assembly { size := extcodesize(seller) }
        return size == 0 || whitelistedContracts[seller];
    }
    
    function whitelistContract(address seller, bool whitelisted) external {
        require(msg.sender == owner);
        whitelistedContracts[seller] = whitelisted;
    }
    
    function updateStorageContract(address newStorage) external {
        require(msg.sender == owner);
        storageContract = EtherGenCore(newStorage);
    }
    
    function updateTransferRestrictionVerifier(address newTransferRestrictionVerifier) external {
        require(msg.sender == owner);
        transferRestrictionVerifier = TransferRestrictionVerifier(newTransferRestrictionVerifier);
    }
    
    function setPaused(bool shouldPause) external {
        require(msg.sender == owner);
        paused = shouldPause;
    }
    
    function updateOwner(address newOwner) external {
        require(msg.sender == owner);
        owner = newOwner;
    }
    
    function withdrawBalance() external {
        require(msg.sender == owner);
        owner.transfer(this.balance);
    }

}
function isWhitelisted(address seller) internal constant returns (bool) {
    uint size;
    assembly { size := extcodesize(seller) }
    return size == 0 || whitelistedContracts[seller];
}

// Contracts can't list cards without contacting us (wallet addresses are unaffected)