contract EtherGenCore is PlayersCollectionStorage, ERC721 {
    
    mapping(address => bool) private privilegedTransferModules; // Marketplace ( + future features)
    mapping(address => bool) private privilegedMintingModules; // Referals, Fusing, Workshop etc. ( + future features)
    
    mapping(uint64 => address) private cardIdApproveds; // Approval list (ERC721 transfers)
    uint64 private totalCardSupply; // Also used for cardId incrementation
    
    TransferRestrictionVerifier transferRestrictionVerifier = TransferRestrictionVerifier(0xd9861d9a6111bfbb9235a71151f654d0fe7ed954); 
    address public owner = 0x08F4aE96b647B30177cc15B21195960625BA4163;
    bool public paused = false;
    
    function totalSupply() public view returns (uint256 cards) {
        return totalCardSupply;
    }
    
    function ownerOf(uint256 cardId) external view returns (address cardOwner) {
        return cardIdMapping[uint64(cardId)].owner;
    }
    
    function balanceOf(address player) public view returns (uint256 balance) {
        return playersCollections[player].cardIds.length;
    }
    
    function tokensOfOwner(address player) external view returns (uint64[] cardIds) {
        return playersCollections[player].cardIds;
    }
    
    function transfer(address newOwner, uint256 cardId) external {
        uint64 castCardId = uint64(cardId);
        require(cardIdMapping[castCardId].owner == msg.sender);
        require(isCardTradeable(cardIdMapping[castCardId]));
        require(transferRestrictionVerifier.isAvailableForTransfer(castCardId));
        require(!paused);
        
        removeCardOwner(castCardId);
        assignCardOwner(newOwner, castCardId);
        Transfer(msg.sender, newOwner, castCardId); // Emit Event
    }
    
    function transferFrom(address currentOwner, address newOwner, uint256 cardId) external {
        uint64 castCardId = uint64(cardId);
        require(cardIdMapping[castCardId].owner == currentOwner);
        require(isApprovedTransferer(msg.sender, castCardId));
        require(isCardTradeable(cardIdMapping[castCardId]));
        require(transferRestrictionVerifier.isAvailableForTransfer(castCardId));
        require(!paused);
        
        removeCardOwner(castCardId);
        assignCardOwner(newOwner, castCardId);
        Transfer(currentOwner, newOwner, castCardId); // Emit Event
    }
    
    function approve(address approved, uint256 cardId) external {
        uint64 castCardId = uint64(cardId);
        require(cardIdMapping[castCardId].owner == msg.sender);
        
        cardIdApproveds[castCardId] = approved; // Register approval (replacing previous)
        Approval(msg.sender, approved, castCardId); // Emit Event
    }
    
    function isApprovedTransferer(address approvee, uint64 cardId) internal constant returns (bool) {
        // Will only return true if approvee (msg.sender) is a privileged transfer address (Marketplace) or santioned by card's owner using ERC721's approve()
        return privilegedTransferModules[approvee] || cardIdApproveds[cardId] == approvee;
    }
    
    function removeCardOwner(uint64 cardId) internal {
        address cardOwner = cardIdMapping[cardId].owner;

        if (playersCollections[cardOwner].cardIds.length > 1) {
            uint64 rowToDelete = cardIdMapping[cardId].collectionPointer;
            uint64 cardIdToMove = playersCollections[cardOwner].cardIds[playersCollections[cardOwner].cardIds.length - 1];
            playersCollections[cardOwner].cardIds[rowToDelete] = cardIdToMove;
            cardIdMapping[cardIdToMove].collectionPointer = rowToDelete;
        }
        
        playersCollections[cardOwner].cardIds.length--;
        cardIdMapping[cardId].owner = 0;
    }
    
    function assignCardOwner(address newOwner, uint64 cardId) internal {
        if (newOwner != address(0)) {
            cardIdMapping[cardId].owner = newOwner;
            cardIdMapping[cardId].collectionPointer = uint64(playersCollections[newOwner].cardIds.push(cardId) - 1);
        }
    }
    
    function mintCard(address recipient, uint8[14] cardDetails) external {
        require(privilegedMintingModules[msg.sender]);
        require(!paused);
        
        Card memory card;
        card.owner = recipient;
        
        card.cardType = cardDetails[0];
        card.race = cardDetails[1];
        card.trait = cardDetails[2];
        card.cost = cardDetails[3];
        card.attack = cardDetails[4];
        card.health = cardDetails[5];
        card.raceMutation0 = cardDetails[6];
        card.raceMutation1 = cardDetails[7];
        card.raceMutation2 = cardDetails[8];
        card.neutralMutation0 = cardDetails[9];
        card.neutralMutation1 = cardDetails[10];
        card.neutralMutation2 = cardDetails[11];
        card.isGolden = cardDetails[12] == 1;
        card.isReferalReward = cardDetails[13] == 1;
        
        card.id = totalCardSupply;
        totalCardSupply++;

        cardIdMapping[card.id] = card;
        cardIdMapping[card.id].collectionPointer = uint64(playersCollections[recipient].cardIds.push(card.id) - 1);
    }
    
    // Management functions to facilitate future contract extensibility, unlocking of (untradable) referal bonus cards and contract ownership
    
    function unlockUntradeableCards(address player) external {
        require(privilegedTransferModules[msg.sender]);
        playersCollections[player].referalCardsUnlocked = true;
    }
    
    function manageApprovedTransferModule(address moduleAddress, bool isApproved) external {
        require(msg.sender == owner);
        privilegedTransferModules[moduleAddress] = isApproved; 
    }
    
     function manageApprovedMintingModule(address moduleAddress, bool isApproved) external {
        require(msg.sender == owner);
        privilegedMintingModules[moduleAddress] = isApproved; 
    }
    
    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;
    }
    
}
// Extensibility of storage + ERCness
function unlockUntradeableCards(address player) external {
    require(privilegedTransferModules[msg.sender]);
    playersCollections[player].referalCardsUnlocked = true;
}

// Management functions to facilitate future contract extensibility, unlocking of (untradable) referal bonus cards and contract ownership