contract PartyBones is ERC721URIStorage, ERC721Enumerable, Ownable, Pausable {
    using Counters for Counters.Counter;
    Counters.Counter public _mintedTokens;
    uint internal constant MAX_Bones = 10300;
    uint internal ticket_price = 60000000000000000; // 0.06 ETH
    uint private prime_seed;
    
    // No more than 10,300 bones to be sold
    modifier saleIsOpen{
        require(_mintedTokens.current() < MAX_Bones, "Sale end");
        _;
    }
    
    // Modify sale price
    function change_ticket_price(uint new_price) public onlyOwner returns(uint)
    {
        ticket_price = new_price;
        return(new_price);
    }
    
    // View price
    function price() public view returns (uint256) {
        return ticket_price; 
    }
    
    string private baseURI = "";
    
    constructor () payable ERC721("PB", "Party Bones") {}
    
    mapping (uint256 => string) boneNames;
    
    mapping(address => bool) public authorised_contracts;
    
    // Contracts for Party Bone Merging authorised and removed here
    function addAuthorisedAddress(address _address) public onlyOwner {
        authorised_contracts[_address] = true;
    }
    
    function removeAuthorisedAddress(address _address) public onlyOwner {
        authorised_contracts[_address] = false;
    }
    
    // Modifier to only alow merging contracts to interact with this function
    modifier onlyAuthorised()
    {
        require(authorised_contracts[msg.sender]);
        _;
    }
    
    // set seed number for non-serialised minting
    function set_seed_Prime(uint prime_number) onlyOwner public
    {
        prime_seed = prime_number;
    }

    // returns current token minting count
    function check_mintedCount() view public returns (uint256)
    {
        return _mintedTokens.current();
    }
    
    // modify the base URI 
    function change_base_URI(string memory new_base_URI)
        onlyOwner
        public
    {
        baseURI = new_base_URI;
    }
    
    // check token's URI
    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        return super.tokenURI(tokenId);
    }
    
    // writing bone names as metadata associated with token
    function set_tokenID_description(uint256 tokenId, string memory nameofbone)
        onlyOwner
        public
    {
        boneNames[tokenId] = nameofbone;
    }
    
    function _baseURI() internal view override returns (string memory) {
        return baseURI;
    }
    
    // pause minting and merging
    function pause() public onlyOwner {
        _pause();
    }
    
    // unpause minting and merging 
    function unpause() public onlyOwner {
        _unpause();
    }
    
    // check name of a bone
    function check_bone_ID_name(uint256 tokenId)
        public
        view
        returns (string memory)
    {
        string memory nameofbone = boneNames[tokenId];
        return nameofbone;
    }
    
    
    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
        super._burn(tokenId);
    }
    
    // burn Party Bone (I wouldn't!)
    function burn(uint256 tokenid)
        public
        returns (uint256)
    {
        require(msg.sender == ownerOf(tokenid), "This wallet does not own the tokenID!");
        _burn(tokenid);
        return tokenid;
    }
    
    // admin only function for burning in case something messes up
    function burn_admin(uint256 tokenid)
        public
        onlyOwner
        returns (uint256)
    {
        _burn(tokenid);
        return tokenid;
    }
    
    // bones are burned when merged for a limb
    function burn_for_limb(uint256 tokenid)
        public
        onlyAuthorised
        whenNotPaused
        returns (uint256)
    {
        _burn(tokenid);
        return tokenid;
    }
    
    // primary sale function for the admin - for airdrops mainly and creator team
    function mint_primary_admin(address address_, uint256 numberOfTokens)
        public
        onlyOwner
        returns (uint256)
    {
        require(_mintedTokens.current() + numberOfTokens <= MAX_Bones, "This exceeds the maximum number of Party Bones on sale!");
        for(uint256 i = 0; i < numberOfTokens; i++) {
            uint256 newItemId  = (((prime_seed*_mintedTokens.current()^2))) % MAX_Bones + 1;
            _mintedTokens.increment();
            _mint(address_, newItemId);
        }
        
        return _mintedTokens.current();
    }
    
    // mint function for admin - in case something goes wrong 
    
    function mint_admin(address address_, uint256[] memory tokenIds)
        public
        onlyOwner
        returns (uint256[] memory)
    {
        for(uint256 i = 0; i < tokenIds.length; i++) {
            _mint(address_, tokenIds[i]);
        }
        
        return tokenIds;
    }
    
    // mint your Party Bone!
    function createBone(uint256 numberOfTokens)
        public
        payable
        saleIsOpen
        whenNotPaused
    {   
        require(numberOfTokens < 21, "No more than 20 Tokens at one time!");
        require(_mintedTokens.current() + numberOfTokens <= MAX_Bones, "This exceeds the maximum number of Party Bones on sale!");
        require(ticket_price * numberOfTokens <= msg.value, "Please send correct amount");
        require(msg.sender == tx.origin, "Can only mint through a wallet");
        for(uint256 i = 0; i < numberOfTokens; i++) {
            uint256 newItemId  = (((prime_seed*_mintedTokens.current()^2))) % MAX_Bones + 1;
            _mintedTokens.increment();
            _mint(msg.sender, newItemId);
        }
    }
    
    // only owner - check contract balance
    function checkContractBalance()
        public
        onlyOwner
        view
        returns (uint256)
    {
        return address(this).balance;
    }
    
    // only owner - withdraw contract balance to wallet
    function withdraw()
        public
        payable
        onlyOwner
    {
        payable(msg.sender).transfer(address(this).balance);
    }
    
    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721, ERC721Enumerable)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
    
    function _beforeTokenTransfer(address from, address to, uint256 tokenId)
        internal
        override(ERC721, ERC721Enumerable)
    {
        super._beforeTokenTransfer(from, to, tokenId);
    }
    
    // retrieve tokens by owner
    function retrieveTokens(address owner) public view returns (uint256[] memory tokens) {
        uint256 iterator = balanceOf(owner);
        uint256[] memory tokenlist = new uint256[](iterator);
        for (uint256 i = 0; i < iterator; i++){
            tokenlist[i] = tokenOfOwnerByIndex(owner, i);
        }
        return tokenlist;
    }
    
    // retrieve names of tokens by owner
    function retrieveTokenNames(address owner) external view returns (string[] memory tokens) {
        uint256 iterator = balanceOf(owner);
        string[] memory tokenlist = new string[](iterator);
        for (uint256 i = 0; i < iterator; i++){
            tokenlist[i] = check_bone_ID_name(tokenOfOwnerByIndex(owner, i));
        }
        return tokenlist;
    }
    
}
function retrieveTokens(address owner) public view returns (uint256[] memory tokens) {
    uint256 iterator = balanceOf(owner);
    uint256[] memory tokenlist = new uint256[](iterator);
    for (uint256 i = 0; i < iterator; i++){
        tokenlist[i] = tokenOfOwnerByIndex(owner, i);
    }
    return tokenlist;
}

// retrieve tokens by owner