pragma solidity ^0.5.3;

library Strings {
  // via https://github.com/oraclize/ethereum-api/blob/master/oraclizeAPI_0.5.sol
  function strConcat(string memory _a, string memory _b, string memory _c, string memory _d, string memory _e) internal pure returns (string memory) {
      bytes memory _ba = bytes(_a);
      bytes memory _bb = bytes(_b);
      bytes memory _bc = bytes(_c);
      bytes memory _bd = bytes(_d);
      bytes memory _be = bytes(_e);
      string memory abcde = new string(_ba.length + _bb.length + _bc.length + _bd.length + _be.length);
      bytes memory babcde = bytes(abcde);
      uint k = 0;
      uint i =0;
      for (i = 0; i < _ba.length; i++) babcde[k++] = _ba[i];
      for (i = 0; i < _bb.length; i++) babcde[k++] = _bb[i];
      for (i = 0; i < _bc.length; i++) babcde[k++] = _bc[i];
      for (i = 0; i < _bd.length; i++) babcde[k++] = _bd[i];
      for (i = 0; i < _be.length; i++) babcde[k++] = _be[i];
      return string(babcde);
    }

    function strConcat(string memory _a, string memory _b, string memory _c, string memory _d) internal pure returns (string memory) {
        return strConcat(_a, _b, _c, _d, "");
    }

    function strConcat(string memory _a, string memory _b, string memory _c) internal pure returns (string memory) {
        return strConcat(_a, _b, _c, "", "");
    }

    function strConcat(string memory _a, string memory _b) internal pure returns (string memory) {
        return strConcat(_a, _b, "", "", "");
    }

    function uint2str(uint i) internal pure returns (string memory) {
        if (i == 0) return "0";
        uint j = i;
        uint len;
        while (j != 0){
            len++;
            j /= 10;
        }
        bytes memory bstr = new bytes(len);
        uint k = len - 1;
        while (i != 0){
            bstr[k--] = byte(uint8(48 + i % 10));
            i /= 10;
        }
        return string(bstr);
    }
}
contract AccessControl {
    address payable public creatorAddress;
    uint16 public totalSeraphims = 0;
    mapping (address => bool) public seraphims;

    bool public isMaintenanceMode = true;
 
    modifier onlyCREATOR() {
        require(msg.sender == creatorAddress);
        _;
    }

    modifier onlySERAPHIM() {
      
      require(seraphims[msg.sender] == true);
        _;
    }
    modifier isContractActive {
        require(!isMaintenanceMode);
        _;
    }
    
    // Constructor
    constructor() public {
        creatorAddress = msg.sender;
    }
    
//Seraphims are contracts or addresses that have write access
    function addSERAPHIM(address _newSeraphim) onlyCREATOR public {
        if (seraphims[_newSeraphim] == false) {
            seraphims[_newSeraphim] = true;
            totalSeraphims += 1;
        }
    }
    
    function removeSERAPHIM(address _oldSeraphim) onlyCREATOR public {
        if (seraphims[_oldSeraphim] == true) {
            seraphims[_oldSeraphim] = false;
            totalSeraphims -= 1;
        }
    }

    function updateMaintenanceMode(bool _isMaintaining) onlyCREATOR public {
        isMaintenanceMode = _isMaintaining;
    }

  
} 



/**
 * @title SafeMath
 * @dev Unsigned math operations with safety checks that revert on error
 */
library SafeMath {
    /**
    * @dev Multiplies two unsigned integers, reverts on overflow.
    */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b);

        return c;
    }

    /**
    * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
    */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
    * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
    */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a);
        uint256 c = a - b;

        return c;
    }

    /**
    * @dev Adds two unsigned integers, reverts on overflow.
    */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a);

        return c;
    }

    /**
    * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),
    * reverts when dividing by zero.
    */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b != 0);
        return a % b;
    }
     function getRandomNumber(uint16 maxRandom, uint8 min, address privateAddress) view public returns(uint8) {
        uint256 genNum = uint256(blockhash(block.number-1)) + uint256(privateAddress);
        return uint8(genNum % (maxRandom - min + 1)+min);
    }
}


/**
 * Utility library of inline functions on addresses
 */
library Address {
    /**
     * Returns whether the target address is a contract
     * @dev This function will return false if invoked during the constructor of a contract,
     * as the code is not actually created until after the constructor finishes.
     * @param account address of the account to check
     * @return whether the target address is a contract
     */
    function isContract(address account) internal view returns (bool) {
        uint256 size;
        // XXX Currently there is no better way to check if there is a contract in an address
        // than to check the size of the code at that address.
        // See https://ethereum.stackexchange.com/a/14016/36603
        // for more details about how this works.
        // TODO Check this again before the Serenity release, because all addresses will be
        // contracts then.
        // solhint-disable-next-line no-inline-assembly
        assembly { size := extcodesize(account) }
        return size > 0;
    }
}


/**
 * @title IERC165
 * @dev https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md
 */
interface IERC165 {
    /**
     * @notice Query if a contract implements an interface
     * @param interfaceId The interface identifier, as specified in ERC-165
     * @dev Interface identification is specified in ERC-165. This function
     * uses less than 30,000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

/**
 * @title ERC165
 * @author Matt Condon (@shrugs)
 * @dev Implements ERC165 using a lookup table.
 */
contract ERC165 is IERC165 {
    bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;
    /**
     * 0x01ffc9a7 ===
     *     bytes4(keccak256('supportsInterface(bytes4)'))
     */

    /**
     * @dev a mapping of interface id to whether or not it's supported
     */
    mapping(bytes4 => bool) private _supportedInterfaces;

    /**
     * @dev A contract implementing SupportsInterfaceWithLookup
     * implement ERC165 itself
     */
    constructor () internal {
        _registerInterface(_INTERFACE_ID_ERC165);
    }

    /**
     * @dev implement supportsInterface(bytes4) using a lookup table
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool) {
        return _supportedInterfaces[interfaceId];
    }

    /**
     * @dev internal method for registering an interface
     */
    function _registerInterface(bytes4 interfaceId) internal {
        require(interfaceId != 0xffffffff);
        _supportedInterfaces[interfaceId] = true;
    }
}

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
contract IERC721Receiver {
    /**
     * @notice Handle the receipt of an NFT
     * @dev The ERC721 smart contract calls this function on the recipient
     * after a `safeTransfer`. This function MUST return the function selector,
     * otherwise the caller will revert the transaction. The selector to be
     * returned can be obtained as `this.onERC721Received.selector`. This
     * function MAY throw to revert and reject the transfer.
     * Note: the ERC721 contract address is always the message sender.
     * @param operator The address which called `safeTransferFrom` function
     * @param from The address which previously owned the token
     * @param tokenId The NFT identifier which is being transferred
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
     */
    function onERC721Received(address operator, address from, uint256 tokenId, bytes memory data)
    public returns (bytes4);
}

/**
 * @title ERC721 Non-Fungible Token Standard basic interface
 * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
 */
contract IERC721 is IERC165 {
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    function balanceOf(address owner) public view returns (uint256 balance);
    function ownerOf(uint256 tokenId) public view returns (address owner);

    function approve(address to, uint256 tokenId) public;
    function getApproved(uint256 tokenId) public view returns (address operator);

    function setApprovalForAll(address operator, bool _approved) public;
    function isApprovedForAll(address owner, address operator) public view returns (bool);

    function transferFrom(address from, address to, uint256 tokenId) public;
    function safeTransferFrom(address from, address to, uint256 tokenId) public;

    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public;
}

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
contract ERC721Receiver {
  /**
   * @dev Magic value to be returned upon successful reception of an NFT
   *  Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`,
   *  which can be also obtained as `ERC721Receiver(0).onERC721Received.selector`
   */
  bytes4 internal constant ERC721_RECEIVED = 0x150b7a02;

  /**
   * @notice Handle the receipt of an NFT
   * @dev The ERC721 smart contract calls this function on the recipient
   * after a `safetransfer`. This function MAY throw to revert and reject the
   * transfer. Return of other than the magic value MUST result in the
   * transaction being reverted.
   * Note: the contract address is always the message sender.
   * @param _operator The address which called `safeTransferFrom` function
   * @param _from The address which previously owned the token
   * @param _tokenId The NFT identifier which is being transferred
   * @param _data Additional data with no specified format
   * @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
   */
  function onERC721Received(
    address _operator,
    address _from,
    uint256 _tokenId,
    bytes4 _data
  )
    public
    returns(bytes4);
}

contract OwnableDelegateProxy { }

contract ProxyRegistry {
    mapping(address => OwnableDelegateProxy) public proxies;
}
contract iABToken is AccessControl{
 
 
    function balanceOf(address owner) public view returns (uint256);
    function totalSupply() external view returns (uint256) ;
    function ownerOf(uint256 tokenId) public view returns (address) ;
    function setMaxAngels() external;
    function setMaxAccessories() external;
    function setMaxMedals()  external ;
    function initAngelPrices() external;
    function initAccessoryPrices() external ;
    function setCardSeriesPrice(uint8 _cardSeriesId, uint _newPrice) external;
    function approve(address to, uint256 tokenId) public;
    function getRandomNumber(uint16 maxRandom, uint8 min, address privateAddress) view public returns(uint8) ;
    function tokenURI(uint256 _tokenId) public pure returns (string memory) ;
    function baseTokenURI() public pure returns (string memory) ;
    function name() external pure returns (string memory _name) ;
    function symbol() external pure returns (string memory _symbol) ;
    function getApproved(uint256 tokenId) public view returns (address) ;
    function setApprovalForAll(address to, bool approved) public ;
    function isApprovedForAll(address owner, address operator) public view returns (bool);
    function transferFrom(address from, address to, uint256 tokenId) public ;
    function safeTransferFrom(address from, address to, uint256 tokenId) public ;
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public ;
    function _exists(uint256 tokenId) internal view returns (bool) ;
    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) ;
    function _mint(address to, uint256 tokenId) internal ;
    function mintABToken(address owner, uint8 _cardSeriesId, uint16 _power, uint16 _auraRed, uint16 _auraYellow, uint16 _auraBlue, string memory _name, uint16 _experience, uint16 _oldId) public;
    function addABTokenIdMapping(address _owner, uint256 _tokenId) private ;
    function getPrice(uint8 _cardSeriesId) public view returns (uint);
    function buyAngel(uint8 _angelSeriesId) public payable ;
    function buyAccessory(uint8 _accessorySeriesId) public payable ;
    function getAura(uint8 _angelSeriesId) pure public returns (uint8 auraRed, uint8 auraYellow, uint8 auraBlue) ;
    function getAngelPower(uint8 _angelSeriesId) private view returns (uint16) ;
    function getABToken(uint256 tokenId) view public returns(uint8 cardSeriesId, uint16 power, uint16 auraRed, uint16 auraYellow, uint16 auraBlue, string memory name, uint16 experience, uint64 lastBattleTime, uint16 lastBattleResult, address owner, uint16 oldId);
    function setAuras(uint256 tokenId, uint16 _red, uint16 _blue, uint16 _yellow) external;
    function setName(uint256 tokenId,string memory namechange) public ;
    function setExperience(uint256 tokenId, uint16 _experience) external;
    function setLastBattleResult(uint256 tokenId, uint16 _result) external ;
    function setLastBattleTime(uint256 tokenId) external;
    function setLastBreedingTime(uint256 tokenId) external ;
    function setoldId(uint256 tokenId, uint16 _oldId) external;
    function getABTokenByIndex(address _owner, uint64 _index) view external returns(uint256) ;
    function _burn(address owner, uint256 tokenId) internal ;
    function _burn(uint256 tokenId) internal ;
    function _transferFrom(address from, address to, uint256 tokenId) internal ;
    function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data) internal returns (bool);
    function _clearApproval(uint256 tokenId) private ;
}

/**
 * @title ERC721 Non-Fungible Token Standard basic implementation
 * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
 */
contract ABToken is IERC721, iABToken, ERC165 {
    using SafeMath for uint256;
    using SafeMath for uint8;
    using Address for address;
    uint256 public totalTokens;
    
    //Mapping or which IDs each address owns
    mapping(address => uint256[]) public ownerABTokenCollection;
    

    // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
    // which can be also obtained as `IERC721Receiver(0).onERC721Received.selector`
    bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;

    // Mapping from token ID to owner
    mapping (uint256 => address) private _tokenOwner;

    // Mapping from token ID to approved address
    mapping (uint256 => address) private _tokenApprovals;

    // Mapping from owner to number of owned token
    mapping (address => uint256) private _ownedTokensCount;

    // Mapping from owner to operator approvals
    mapping (address => mapping (address => bool)) private _operatorApprovals;

    bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;
    /*
     * 0x80ac58cd ===
     *     bytes4(keccak256('balanceOf(address)')) ^
     *     bytes4(keccak256('ownerOf(uint256)')) ^
     *     bytes4(keccak256('approve(address,uint256)')) ^
     *     bytes4(keccak256('getApproved(uint256)')) ^
     *     bytes4(keccak256('setApprovalForAll(address,bool)')) ^
     *     bytes4(keccak256('isApprovedForAll(address,address)')) ^
     *     bytes4(keccak256('transferFrom(address,address,uint256)')) ^
     *     bytes4(keccak256('safeTransferFrom(address,address,uint256)')) ^
     *     bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)'))
     */
     
     
       //current and max numbers of issued tokens for each series 
    uint32[100] public currentTokenNumbers;
    uint32[100] public maxTokenNumbers;
    
    //current price of each angel and accessory series
    uint[24] public angelPrice;
    uint[18] public accessoryPrice;
 
     address proxyRegistryAddress; 
     
   //  Main data structure for each token 
struct ABCard {
    uint256 tokenId;       
        uint8 cardSeriesId;
        //This is 0 to 23 for angels, 24 to 42 for pets, 43 to 60 for accessories, 61 to 72 for medals
        //address owner; 
        //already accounted in mapping. 
        uint16 power;
        //This number is luck for pets and battlepower for angels
        uint16 auraRed;
        uint16 auraYellow;
        uint16 auraBlue;
        string name;
        uint16 experience;
        uint64 lastBattleTime;
        uint64 lastBreedingTime;
        uint16 lastBattleResult;
        uint16 oldId; //for cards transfered from the first version of the game. 
    }
     //Main mapping storing an ABCard for each token ID
      mapping(uint256 => ABCard) public ABTokenCollection;
  
    constructor() public {
        // register the supported interfaces to conform to ERC721 via ERC165
        _registerInterface(_INTERFACE_ID_ERC721);
    }

    /**
     * @dev Gets the balance of the specified address
     * @param owner address to query the balance of
     * @return uint256 representing the amount owned by the passed address
     */
    function balanceOf(address owner) public view returns (uint256) {
        require(owner != address(0));
        return _ownedTokensCount[owner];
    }

 function totalSupply() external view returns (uint256) {
     return totalTokens;
 }
    /**
     * @dev Gets the owner of the specified token ID
     * @param tokenId uint256 ID of the token to query the owner of
     * @return owner address currently marked as the owner of the given token ID
     */
    function ownerOf(uint256 tokenId) public view returns (address) {
        address owner = _tokenOwner[tokenId];
        require(owner != address(0));
        return owner;
    }
    
    //Initial function to set the maximum numbers of each angel card

function setMaxAngels() external onlyCREATOR {
    uint i =0;
   
     //Angels 0 and 1 have no max
     //Lucifer and Michael have max numbers 250
     maxTokenNumbers[2] = 250;
     maxTokenNumbers[3] = 250;
     maxTokenNumbers[4] = 45;
     maxTokenNumbers[5] = 50;
     
    for (i=6; i<15; i++) {
        maxTokenNumbers[i]= 45;
    }
     for (i=15; i<24; i++) {
        maxTokenNumbers[i]= 65;
    }
   
    
}

//Initial function to set the maximum number of accessories
function setMaxAccessories() external onlyCREATOR {
     uint i = 0;
     for (i=43; i<60; i++) {
        maxTokenNumbers[i]= 200;
    }
}

//Initial function to set the max number of medals
  function setMaxMedals() onlyCREATOR external  {
      maxTokenNumbers[61] = 5000;
      maxTokenNumbers[62] = 5000;
      maxTokenNumbers[63] = 5000;
      maxTokenNumbers[64] = 5000;
      maxTokenNumbers[65] = 500;
      maxTokenNumbers[66] = 500;
      maxTokenNumbers[67] = 200;
      maxTokenNumbers[68] = 200;
      maxTokenNumbers[69] = 200;
      maxTokenNumbers[70] = 100;
      maxTokenNumbers[71] = 100;
      maxTokenNumbers[72] = 50;
  }
    //Function called once at the beginning to set the prices of all the angel cards. 
    function initAngelPrices() external onlyCREATOR {
       angelPrice[0] = 0;
       angelPrice[1] = 30000000000000000;
       angelPrice[2] = 666000000000000000;
       angelPrice[3] = 800000000000000000;
       angelPrice[4] = 10000000000000000;
       angelPrice[5] = 10000000000000000;
       angelPrice[6] = 20000000000000000;
       angelPrice[7] = 25000000000000000;
       angelPrice[8] = 16000000000000000;
       angelPrice[9] = 18000000000000000;
       angelPrice[10] = 14000000000000000;
       angelPrice[11] = 20000000000000000;
       angelPrice[12] = 24000000000000000;
       angelPrice[13] = 28000000000000000;
       angelPrice[14] = 40000000000000000;
       angelPrice[15] = 50000000000000000;
       angelPrice[16] = 53000000000000000;
       angelPrice[17] = 60000000000000000;
       angelPrice[18] = 65000000000000000;
       angelPrice[19] = 70000000000000000;
       angelPrice[20] = 75000000000000000;
       angelPrice[21] = 80000000000000000;
       angelPrice[22] = 85000000000000000;
       angelPrice[23] = 90000000000000000;
      
    }
    
        //Function called once at the beginning to set the prices of all the accessory cards. 
    function initAccessoryPrices() external onlyCREATOR {
       accessoryPrice[0] = 20000000000000000;
       accessoryPrice[1] = 60000000000000000;
       accessoryPrice[2] = 40000000000000000;
       accessoryPrice[3] = 90000000000000000;
       accessoryPrice[4] = 80000000000000000;
       accessoryPrice[5] = 160000000000000000;
       accessoryPrice[6] = 60000000000000000;
       accessoryPrice[7] = 120000000000000000;
       accessoryPrice[8] = 60000000000000000;
       accessoryPrice[9] = 120000000000000000;
       accessoryPrice[10] = 60000000000000000;
       accessoryPrice[11] = 120000000000000000;
       accessoryPrice[12] = 200000000000000000;
       accessoryPrice[13] = 200000000000000000;
       accessoryPrice[14] = 200000000000000000;
       accessoryPrice[15] = 200000000000000000;
       accessoryPrice[16] = 500000000000000000;
       accessoryPrice[17] = 600000000000000000;
    }
   
    
    // Developer function to change the price (in wei) for a card series. 
    function setCardSeriesPrice(uint8 _cardSeriesId, uint _newPrice) external onlyCREATOR {
        if (_cardSeriesId <24) {angelPrice[_cardSeriesId] = _newPrice;} else {
        if ((_cardSeriesId >42) && (_cardSeriesId < 61)) {accessoryPrice[(_cardSeriesId-43)] = _newPrice;}
        }
        
        
    }

   function withdrawEther() external onlyCREATOR {
    creatorAddress.transfer(address(this).balance);
}

    /**
     * @dev Approves another address to transfer the given token ID
     * The zero address indicates there is no approved address.
     * There can only be one approved address per token at a given time.
     * Can only be called by the token owner or an approved operator.
     * @param to address to be approved for the given token ID
     * @param tokenId uint256 ID of the token to be approved
     */
    function approve(address to, uint256 tokenId) public {
        address owner = ownerOf(tokenId);
        require(to != owner);
        require(msg.sender == owner || isApprovedForAll(owner, msg.sender));

        _tokenApprovals[tokenId] = to;
        emit Approval(owner, to, tokenId);
    }
    
        function getRandomNumber(uint16 maxRandom, uint8 min, address privateAddress) view public returns(uint8) {
        uint256 genNum = uint256(blockhash(block.number-1)) + uint256(privateAddress);
        return uint8(genNum % (maxRandom - min + 1)+min);
    }

   /**
   * @dev Returns an URI for a given token ID
   */
  function tokenURI(uint256 _tokenId) public pure returns (string memory) {
    return Strings.strConcat(
        baseTokenURI(),
        Strings.uint2str(_tokenId)
    );
  }
  
  function baseTokenURI() public pure returns (string memory) {
    return "https://www.angelbattles.com/URI/";
  }
  
   /// @notice A descriptive name for a collection of NFTs in this contract
    function name() external pure returns (string memory _name) {
        return "Angel Battle Token";
    }

    /// @notice An abbreviated name for NFTs in this contract
    function symbol() external pure returns (string memory _symbol) {
        return "ABT";
    }
  
  
    

    /**
     * @dev Gets the approved address for a token ID, or zero if no address set
     * Reverts if the token ID does not exist.
     * @param tokenId uint256 ID of the token to query the approval of
     * @return address currently approved for the given token ID
     */
    function getApproved(uint256 tokenId) public view returns (address) {
        require(_exists(tokenId));
        return _tokenApprovals[tokenId];
    }

    /**
     * @dev Sets or unsets the approval of a given operator
     * An operator is allowed to transfer all tokens of the sender on their behalf
     * @param to operator address to set the approval
     * @param approved representing the status of the approval to be set
     */
    function setApprovalForAll(address to, bool approved) public {
        require(to != msg.sender);
        _operatorApprovals[msg.sender][to] = approved;
        emit ApprovalForAll(msg.sender, to, approved);
    }

    /**
     * @dev Tells whether an operator is approved by a given owner
     * @param owner owner address which you want to query the approval of
     * @param operator operator address which you want to query the approval of
     * @return bool whether the given operator is approved by the given owner
     */
    function isApprovedForAll(address owner, address operator) public view returns (bool) {
        return _operatorApprovals[owner][operator];
    }
    
    /**
   * Override isApprovedForAll to whitelist user's OpenSea proxy accounts to enable gas-less listings.
   */
 


    /**
     * @dev Transfers the ownership of a given token ID to another address
     * Usage of this method is discouraged, use `safeTransferFrom` whenever possible
     * Requires the msg sender to be the owner, approved, or operator
     * @param from current owner of the token
     * @param to address to receive the ownership of the given token ID
     * @param tokenId uint256 ID of the token to be transferred
    */
    function transferFrom(address from, address to, uint256 tokenId) public {
        require(_isApprovedOrOwner(msg.sender, tokenId));

        _transferFrom(from, to, tokenId);
    }

    /**
     * @dev Safely transfers the ownership of a given token ID to another address
     * If the target address is a contract, it must implement `onERC721Received`,
     * which is called upon a safe transfer, and return the magic value
     * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
     * the transfer is reverted.
     *
     * Requires the msg sender to be the owner, approved, or operator
     * @param from current owner of the token
     * @param to address to receive the ownership of the given token ID
     * @param tokenId uint256 ID of the token to be transferred
    */
    function safeTransferFrom(address from, address to, uint256 tokenId) public {
        safeTransferFrom(from, to, tokenId, "");
    }

    /**
     * @dev Safely transfers the ownership of a given token ID to another address
     * If the target address is a contract, it must implement `onERC721Received`,
     * which is called upon a safe transfer, and return the magic value
     * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
     * the transfer is reverted.
     * Requires the msg sender to be the owner, approved, or operator
     * @param from current owner of the token
     * @param to address to receive the ownership of the given token ID
     * @param tokenId uint256 ID of the token to be transferred
     * @param _data bytes data to send along with a safe transfer check
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public {
        transferFrom(from, to, tokenId);
        require(_checkOnERC721Received(from, to, tokenId, _data));
    }

    /**
     * @dev Returns whether the specified token exists
     * @param tokenId uint256 ID of the token to query the existence of
     * @return whether the token exists
     */
    function _exists(uint256 tokenId) internal view returns (bool) {
        address owner = _tokenOwner[tokenId];
        return owner != address(0);
    }

    /**
     * @dev Returns whether the given spender can transfer a given token ID
     * @param spender address of the spender to query
     * @param tokenId uint256 ID of the token to be transferred
     * @return bool whether the msg.sender is approved for the given token ID,
     *    is an operator of the owner, or is the owner of the token
     */
    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
        address owner = ownerOf(tokenId);
        return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
    }

    /**
     * @dev Internal function to mint a new token
     * Reverts if the given token ID already exists
     * @param to The address that will own the minted token
     * @param tokenId uint256 ID of the token to be minted
     */
    function _mint(address to, uint256 tokenId) internal {
        require(to != address(0));
        require(!_exists(tokenId));

        _tokenOwner[tokenId] = to;
        _ownedTokensCount[to] = _ownedTokensCount[to].add(1);
        addABTokenIdMapping(to, tokenId);
        emit Transfer(address(0), to, tokenId);
    }
    function mintABToken(address owner, uint8 _cardSeriesId, uint16 _power, uint16 _auraRed, uint16 _auraYellow, uint16 _auraBlue, string memory _name, uint16 _experience, uint16 _oldId) public onlySERAPHIM {
        require((currentTokenNumbers[_cardSeriesId] < maxTokenNumbers[_cardSeriesId] || maxTokenNumbers[_cardSeriesId] == 0));
        require(_cardSeriesId <100);
           ABCard storage abcard = ABTokenCollection[totalTokens];
           abcard.power = _power;
           abcard.cardSeriesId= _cardSeriesId;
           abcard.auraRed = _auraRed;
           abcard.auraYellow= _auraYellow;
           abcard.auraBlue= _auraBlue;
           abcard.name = _name;
           abcard.experience = _experience;
           abcard.tokenId = totalTokens;
           abcard.lastBattleTime = uint64(now);
           abcard.lastBreedingTime = uint64(now);
           abcard.lastBattleResult = 0;
           abcard.oldId = _oldId;
           _mint(owner, totalTokens);
           totalTokens = totalTokens +1;
           currentTokenNumbers[_cardSeriesId] ++;
    }
    
    function _mintABToken(address owner, uint8 _cardSeriesId, uint16 _power, uint16 _auraRed, uint16 _auraYellow, uint16 _auraBlue, string memory _name, uint16 _experience, uint16 _oldId) internal {
        require((currentTokenNumbers[_cardSeriesId] < maxTokenNumbers[_cardSeriesId] || maxTokenNumbers[_cardSeriesId] == 0));
        require(_cardSeriesId <100);
           ABCard storage abcard = ABTokenCollection[totalTokens];
           abcard.power = _power;
           abcard.cardSeriesId= _cardSeriesId;
           abcard.auraRed = _auraRed;
           abcard.auraYellow= _auraYellow;
           abcard.auraBlue= _auraBlue;
           abcard.name = _name;
           abcard.experience = _experience;
           abcard.tokenId = totalTokens;
           abcard.lastBattleTime = uint64(now);
           abcard.lastBreedingTime = uint64(now);
           abcard.lastBattleResult = 0;
           abcard.oldId = _oldId;
           _mint(owner, totalTokens);
           totalTokens = totalTokens +1;
           currentTokenNumbers[_cardSeriesId] ++;
    }
    
    function addABTokenIdMapping(address _owner, uint256 _tokenId) private {
            uint256[] storage owners = ownerABTokenCollection[_owner];
            owners.push(_tokenId);
    }
    

    
    function getPrice(uint8 _cardSeriesId) public view returns (uint) {
        if (_cardSeriesId <24) {return angelPrice[_cardSeriesId];}
        if ((_cardSeriesId >42) && (_cardSeriesId < 61)) {return accessoryPrice[(_cardSeriesId-43)];}
        return 0;
    }
    
    function buyAngel(uint8 _angelSeriesId) public payable {
        //don't create another card if we are already at the max
        if ((maxTokenNumbers[_angelSeriesId] <= currentTokenNumbers[_angelSeriesId]) && (_angelSeriesId >1 )) {revert();}
        //don't create another card if they haven't sent enough money. 
        if (msg.value < angelPrice[_angelSeriesId]) {revert();} 
        //don't create an angel card if they are trying to create a different type of card. 
         if ((_angelSeriesId<0) || (_angelSeriesId > 23)) {revert();}
        uint8 auraRed;
        uint8 auraYellow;
        uint8 auraBlue;
        uint16 power;
        (auraRed, auraYellow, auraBlue) = getAura(_angelSeriesId);
        (power) = getAngelPower(_angelSeriesId);
    
       _mintABToken(msg.sender, _angelSeriesId, power, auraRed, auraYellow, auraBlue,"", 0, 0);
       
    }
    
    
    function buyAccessory(uint8 _accessorySeriesId) public payable {
        //don't create another card if we are already at the max
        if (maxTokenNumbers[_accessorySeriesId] <= currentTokenNumbers[_accessorySeriesId]) {revert();}
        //don't create another card if they haven't sent enough money. 
        if (msg.value < accessoryPrice[_accessorySeriesId-43]) {revert();} 
        //don't create a card if they are trying to create a different type of card. 
        if ((_accessorySeriesId<43) || (_accessorySeriesId > 60)) {revert();}
        _mintABToken(msg.sender,_accessorySeriesId, 0, 0, 0, 0, "",0, 0);
       
     
       
    }
    
    //Returns the Aura color of each angel
    function getAura(uint8 _angelSeriesId) pure public returns (uint8 auraRed, uint8 auraYellow, uint8 auraBlue) {
        if (_angelSeriesId == 0) {return(0,0,1);}
        if (_angelSeriesId == 1) {return(0,1,0);}
        if (_angelSeriesId == 2) {return(1,0,1);}
        if (_angelSeriesId == 3) {return(1,1,0);}
        if (_angelSeriesId == 4) {return(1,0,0);}
        if (_angelSeriesId == 5) {return(0,1,0);}
        if (_angelSeriesId == 6) {return(1,0,1);}
        if (_angelSeriesId == 7) {return(0,1,1);}
        if (_angelSeriesId == 8) {return(1,1,0);}
        if (_angelSeriesId == 9) {return(0,0,1);}
        if (_angelSeriesId == 10)  {return(1,0,0);}
        if (_angelSeriesId == 11) {return(0,1,0);}
        if (_angelSeriesId == 12) {return(1,0,1);}
        if (_angelSeriesId == 13) {return(0,1,1);}
        if (_angelSeriesId == 14) {return(1,1,0);}
        if (_angelSeriesId == 15) {return(0,0,1);}
        if (_angelSeriesId == 16)  {return(1,0,0);}
        if (_angelSeriesId == 17) {return(0,1,0);}
        if (_angelSeriesId == 18) {return(1,0,1);}
        if (_angelSeriesId == 19) {return(0,1,1);}
        if (_angelSeriesId == 20) {return(1,1,0);}
        if (_angelSeriesId == 21) {return(0,0,1);}
        if (_angelSeriesId == 22)  {return(1,0,0);}
        if (_angelSeriesId == 23) {return(0,1,1);}
    }
   
    function getAngelPower(uint8 _angelSeriesId) private view returns (uint16) {
        uint8 randomPower = getRandomNumber(10,0,msg.sender);
        if (_angelSeriesId >=4) {
        return (100 + 10 * ((_angelSeriesId - 4) + randomPower));
        }
        if (_angelSeriesId == 0 ) {
        return (50 + randomPower);
        }
         if (_angelSeriesId == 1) {
        return (120 + randomPower);
        }
         if (_angelSeriesId == 2) {
        return (250 + randomPower);
        }
        if (_angelSeriesId == 3) {
        return (300 + randomPower);
        }
        
    }
    
    function getCurrentTokenNumbers(uint8 _cardSeriesId) view public returns (uint32) {
        return currentTokenNumbers[_cardSeriesId];
}
       function getMaxTokenNumbers(uint8 _cardSeriesId) view public returns (uint32) {
        return maxTokenNumbers[_cardSeriesId];
}


    function getABToken(uint256 tokenId) view public returns(uint8 cardSeriesId, uint16 power, uint16 auraRed, uint16 auraYellow, uint16 auraBlue, string memory name, uint16 experience, uint64 lastBattleTime, uint16 lastBattleResult, address owner, uint16 oldId) {
        ABCard memory abcard = ABTokenCollection[tokenId];
        cardSeriesId = abcard.cardSeriesId;
        power = abcard.power;
        experience = abcard.experience;
        auraRed = abcard.auraRed;
        auraBlue = abcard.auraBlue;
        auraYellow = abcard.auraYellow;
        name = abcard.name;
        lastBattleTime = abcard.lastBattleTime;
        lastBattleResult = abcard.lastBattleResult;
        oldId = abcard.oldId;
        owner = ownerOf(tokenId);
    }
    
    
     function setAuras(uint256 tokenId, uint16 _red, uint16 _blue, uint16 _yellow) external onlySERAPHIM {
        ABCard storage abcard = ABTokenCollection[tokenId];
        if (abcard.tokenId == tokenId) {
            abcard.auraRed = _red;
            abcard.auraYellow = _yellow;
            abcard.auraBlue = _blue;
    }
    }
    
     function setName(uint256 tokenId,string memory namechange) public {
        ABCard storage abcard = ABTokenCollection[tokenId];
        if (msg.sender != ownerOf(tokenId)) {revert();}
        if (abcard.tokenId == tokenId) {
            abcard.name = namechange;
    }
    }
    
    function setExperience(uint256 tokenId, uint16 _experience) external onlySERAPHIM {
        ABCard storage abcard = ABTokenCollection[tokenId];
        if (abcard.tokenId == tokenId) {
            abcard.experience = _experience;
    }
    }
    
    function setLastBattleResult(uint256 tokenId, uint16 _result) external onlySERAPHIM {
        ABCard storage abcard = ABTokenCollection[tokenId];
        if (abcard.tokenId == tokenId) {
            abcard.lastBattleResult = _result;
    }
    }
    
     function setLastBattleTime(uint256 tokenId) external onlySERAPHIM {
        ABCard storage abcard = ABTokenCollection[tokenId];
        if (abcard.tokenId == tokenId) {
            abcard.lastBattleTime = uint64(now);
    }
    }
    
       function setLastBreedingTime(uint256 tokenId) external onlySERAPHIM {
        ABCard storage abcard = ABTokenCollection[tokenId];
        if (abcard.tokenId == tokenId) {
            abcard.lastBreedingTime = uint64(now);
    }
    }
    
      function setoldId(uint256 tokenId, uint16 _oldId) external onlySERAPHIM {
        ABCard storage abcard = ABTokenCollection[tokenId];
        if (abcard.tokenId == tokenId) {
            abcard.oldId = _oldId;
    }
    }
    
    
    function getABTokenByIndex(address _owner, uint64 _index) view external returns(uint256) {
        if (_index >= ownerABTokenCollection[_owner].length) {
            return 0; }
        return ownerABTokenCollection[_owner][_index];
    }

   /**
     * @dev external function to burn a specific token
     * Reverts if the token does not exist
     * @param tokenId uint256 ID of the token being burned
     * Only the owner can burn their token. 
     */
    function burn(uint256 tokenId) external {
        require(ownerOf(tokenId) == msg.sender);
        _clearApproval(tokenId);
        _ownedTokensCount[msg.sender] = _ownedTokensCount[msg.sender].sub(1);
        _tokenOwner[tokenId] = address(0);
        emit Transfer(msg.sender, address(0), tokenId);
    }
    
     /**
     * @dev external function to burn a specific token
     * Reverts if the token does not exist
     * @param tokenId uint256 ID of the token being burned
     * Only the owner can burn their token. 
     * This function allows a new token type to be reissued. This preserves rarity, while the burn functio increases rarity
     */
    function burnAndRecycle(uint256 tokenId) external {
        require(ownerOf(tokenId) == msg.sender);
        uint8 cardSeriesId;
        _clearApproval(tokenId);
        _ownedTokensCount[msg.sender] = _ownedTokensCount[msg.sender].sub(1);
        _tokenOwner[tokenId] = address(0);
        (cardSeriesId,,,,,,,,,,) = getABToken (tokenId);
        if (currentTokenNumbers[cardSeriesId] >= 1) {
            currentTokenNumbers[cardSeriesId] = currentTokenNumbers[cardSeriesId] - 1;
        }
        emit Transfer(msg.sender, address(0), tokenId);
    }


    /**
     * @dev Internal function to burn a specific token
     * Reverts if the token does not exist
     * Deprecated, use _burn(uint256) instead.
     * @param owner owner of the token to burn
     * @param tokenId uint256 ID of the token being burned
     */
    function _burn(address owner, uint256 tokenId) internal {
        require(ownerOf(tokenId) == owner);

        _clearApproval(tokenId);

        _ownedTokensCount[owner] = _ownedTokensCount[owner].sub(1);
        _tokenOwner[tokenId] = address(0);

        emit Transfer(owner, address(0), tokenId);
    }

    /**
     * @dev Internal function to burn a specific token
     * Reverts if the token does not exist
     * @param tokenId uint256 ID of the token being burned
     */
    function _burn(uint256 tokenId) internal {
        _burn(ownerOf(tokenId), tokenId);
    }

    /**
     * @dev Internal function to transfer ownership of a given token ID to another address.
     * As opposed to transferFrom, this imposes no restrictions on msg.sender.
     * @param from current owner of the token
     * @param to address to receive the ownership of the given token ID
     * @param tokenId uint256 ID of the token to be transferred
    */
    function _transferFrom(address from, address to, uint256 tokenId) internal {
        require(ownerOf(tokenId) == from);
        require(to != address(0));

        _clearApproval(tokenId);

        _ownedTokensCount[from] = _ownedTokensCount[from].sub(1);
        _ownedTokensCount[to] = _ownedTokensCount[to].add(1);

        _tokenOwner[tokenId] = to;
        addABTokenIdMapping(to, tokenId);
        emit Transfer(from, to, tokenId);
    }

    /**
     * @dev Internal function to invoke `onERC721Received` on a target address
     * The call is not executed if the target address is not a contract
     * @param from address representing the previous owner of the given token ID
     * @param to target address that will receive the tokens
     * @param tokenId uint256 ID of the token to be transferred
     * @param _data bytes optional data to send along with the call
     * @return whether the call correctly returned the expected magic value
     */
    function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data)
        internal returns (bool)
    {
        if (!to.isContract()) {
            return true;
        }

        bytes4 retval = IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, _data);
        return (retval == _ERC721_RECEIVED);
    }

    /**
     * @dev Private function to clear current approval of a given token ID
     * @param tokenId uint256 ID of the token to be transferred
     */
    function _clearApproval(uint256 tokenId) private {
        if (_tokenApprovals[tokenId] != address(0)) {
            _tokenApprovals[tokenId] = address(0);
        }
    }
}