contract VerbsToken is IVerbsToken, Ownable, ERC721Enumerable {
    using Strings for uint256;
    
    event Mint(
        address indexed _to,
        uint256 indexed _tokenId
    );

    // Price and maximum number of Verbs
    uint256 public price = 20000000000000000;
    uint256 public mint_limit= 30;

    // Store custom descriptions for Verbs
    mapping (uint => string) public customDescription; 

    // The Verbs token URI descriptor
    IVerbsDescriptor public descriptor;

    // The Verbs token seeder
    IVerbsSeeder public seeder;

    // Whether the descriptor can be updated
    bool public isDescriptorLocked;

    // Whether the seeder can be updated
    bool public isSeederLocked;

    // The Verb seeds
    mapping(uint256 => IVerbsSeeder.Seed) public seeds;

    // The internal Verb ID tracker
    uint256 public currentVerbId = 0;

    string public script = "";
    string public projectBaseURI = "";
    mapping(uint256 => bytes32[]) internal tokenIdToHashes;
    
    NounsContract public nounsContract;
    mapping(uint256 => bool) public nounClaimed;
    
    ERC721Enumerable public kinesisContract;
    mapping(uint256 => bool) public tokenIdClaimed;



    // OpenSea's Proxy Registry
    IProxyRegistry public immutable proxyRegistry;

    // Withdraw Addresses
    address kinesis;

    // Sale Status

    bool public active =false;
    bool public nounClaimingOpen =true;
    bool public kinesisClaimingOpen =true;
    bool public publicSaleOpen =false;
    uint256 public numClaimed = 0;
    uint256 public numNounsClaimed = 0;
    /**
     * @notice Require that the descriptor has not been locked.
     */
    modifier whenDescriptorNotLocked() {
        require(!isDescriptorLocked, 'Descriptor is locked');
        _;
    }

    /**
     * @notice Require that the seeder has not been locked.
     */
    modifier whenSeederNotLocked() {
        require(!isSeederLocked, 'Seeder is locked');
        _;
    }

    function setProjectScript(string memory _script) onlyOwner external {
        script = _script;
    }

    function updateProjectBaseURI(string memory _newBaseURI) onlyOwner external {
        projectBaseURI = _newBaseURI;
    }
    
    constructor() ERC721('Verbs', 'Verbs') {
        descriptor = IVerbsDescriptor(0x0Cfdb3Ba1694c2bb2CFACB0339ad7b1Ae5932B63);
        seeder = IVerbsSeeder(0xCC8a0FB5ab3C7132c1b2A0109142Fb112c4Ce515);
        nounsContract = NounsContract(0x9C8fF314C9Bc7F6e59A9d9225Fb22946427eDC03);
        proxyRegistry = IProxyRegistry(0xa5409ec958C83C3f309868babACA7c86DCB077c1);
        kinesis = msg.sender;
        kinesisContract = ERC721Enumerable(0xeb113c5d09bfc3a7b27A75Da4432FB3484f90c6A);
    }

    function toggleActive() external onlyOwner {
        active=!active;
    }
    function toggleKinesisClaim() external onlyOwner {
        kinesisClaimingOpen=!kinesisClaimingOpen;
    }
    function toggleNounClaim() external onlyOwner {
        nounClaimingOpen=!nounClaimingOpen;
    }
    function togglePublicSale() external onlyOwner {
        publicSaleOpen=!publicSaleOpen;
    }

    function updateKinesisAddress(address payable _kinesisAddress) public onlyOwner {
        kinesis = _kinesisAddress;
    }
    function showTokenHashes(uint _tokenId) public view returns (bytes32[] memory){
        return tokenIdToHashes[_tokenId];
    }
    /**
     * @notice Override isApprovedForAll to whitelist user's OpenSea proxy accounts to enable gas-less listings.
     */
    function isApprovedForAll(address owner, address operator) public view override(IERC721, ERC721) returns (bool) {
        // Whitelist OpenSea proxy contract for easy trading.
        if (proxyRegistry.proxies(owner) == operator) {
            return true;
        }
        return super.isApprovedForAll(owner, operator);
    }

    /// @notice Claim Verb for given Kinesis token
    /// @param tokenId The tokenId of the Kinesis NFT
    function claimById(uint256 tokenId) external {
        // Check that the msgSender owns the token that is being claimed
        require(
            active, "Project not active!"
        );
        require(
            _msgSender() == kinesisContract.ownerOf(tokenId),
            "MUST_OWN_TOKEN_ID"
        );
        require(
            kinesisClaimingOpen, "Kinesis claiming is closed!"
        );
        require(
            !tokenIdClaimed[tokenId],
            "TOKEN_ALREADY_CLAIMED"
        );
        tokenIdClaimed[tokenId] = true;
        _claim(_msgSender());
    }
    
    /// @notice Claim Verb for all Kinesis tokens owned by the sender
    function claimAllForOwner() external {
        require(
            active, "Project not active!"
        );
        require(
            kinesisClaimingOpen, "Kinesis claiming is closed!"
        );
        uint256 tokenBalanceOwner = kinesisContract.balanceOf(_msgSender());
        // Checks
        require(tokenBalanceOwner > 0, "NO_TOKENS_OWNED");
        uint256 tokenId;

        for (uint256 i = 0; i < tokenBalanceOwner; i++) {
            tokenId = kinesisContract.tokenOfOwnerByIndex(_msgSender(), i);
            require(
                !tokenIdClaimed[tokenId],
                "TOKEN_ALREADY_CLAIMED"
            );
            tokenIdClaimed[tokenId] = true;
            _claim(_msgSender());
        }
    }
    function claimForNoun(uint256 nounId) external {      
        require(
            active, "Project not active!"
        );

        require(
            nounClaimingOpen, "Noun claiming is closed!"
        );

        require(
            _msgSender() == nounsContract.ownerOf(nounId),
            "MUST_OWN_NOUN_ID"
        );

        // Check that Verb has not already been claimed for a given tokenId
        require(
            !nounClaimed[nounId],
            "TOKEN_ALREADY_CLAIMED_FOR_NOUN"
        );
        nounClaimed[nounId] = true;
        uint256 verbId =currentVerbId;
        currentVerbId = currentVerbId + 1;
        
        bytes32 hash = keccak256(abi.encodePacked(verbId, block.number, msg.sender));
        tokenIdToHashes[verbId].push(hash);

        (uint48 background,uint48 body,uint48 accessory,uint48 head,uint48 glasses) = nounsContract.seeds(nounId);
        seeds[verbId] = IVerbsSeeder.Seed(background, body, accessory, head, glasses);
        
        numNounsClaimed = numNounsClaimed + 1;
        _safeMint(msg.sender, verbId);
    }
    /// @dev Internal function to mint Verb upon claiming
    function _claim(address tokenOwner) internal {
        uint256 verbId = currentVerbId;
        currentVerbId = currentVerbId + 1;
        numClaimed = numClaimed + 1;
        _mintTo(tokenOwner, verbId);
    }
    /**
     * @notice Mint Verbs to sender
     */
    function mint(uint256 num_tokens) public override payable {

        require (active,"sale not active");

        require (publicSaleOpen, "public sale not open");

        require (num_tokens<=mint_limit,"minted too many");

        require(msg.value>=num_tokens*price,"not enough ethers sent");

        payable(kinesis).transfer(msg.value);
        
        for (uint256 x=0;x<num_tokens;x++)
        {
            _mintTo(msg.sender, currentVerbId);
            currentVerbId = currentVerbId + 1;
        }
    }
    
    /**
     * @notice Mint a Verb with `VerbId` to the provided `to` address
     */
    function _mintTo(address to, uint256 VerbId) internal returns (uint256) {
        bytes32 hash = keccak256(abi.encodePacked(VerbId, block.number, msg.sender));
        seeds[VerbId] = seeder.generateSeed(VerbId, descriptor);
        tokenIdToHashes[VerbId].push(hash);
        _safeMint(to, VerbId);
        emit Mint(to, VerbId);
        return VerbId;
    }

    function updatePricePerTokenInWei(uint256 _pricePerTokenInWei) onlyOwner external {
        price = _pricePerTokenInWei;
    }

    /**
     * @notice Burn a Verb.
     */
    function burn(uint256 VerbId) public override onlyOwner {
        _burn(VerbId);
        emit VerbBurned(VerbId);
    }

    /**
     * @notice A distinct Uniform Resource Identifier (URI) for a given asset.
     * @dev See {IERC721Metadata-tokenURI}.
     */
    function tokenURI(uint256 _tokenId) public view virtual override returns (string memory) {
        require(_exists(_tokenId), 'VerbsToken: URI query for nonexistent token');
        return string(abi.encodePacked(projectBaseURI, Strings.toString(_tokenId)));
    }

    function getImageData(uint256 tokenId) public view returns (string memory) {
        require(_exists(tokenId), 'VerbsToken: URI query for nonexistent token');
        string memory VerbId = tokenId.toString();
        string memory name = string(abi.encodePacked('Verb ', VerbId)); 
        string memory description=string(abi.encodePacked('Verb ', VerbId, ' is an animated homage to the Noun, created by grant.oe for the Kinesis Art platform'));
        return descriptor.genericDataURI(name, description, seeds[tokenId]);
    }

    /**
     * @notice Set the token URI descriptor.
     * @dev Only callable by the owner when not locked.
     */
    function setDescriptor(IVerbsDescriptor _descriptor) external override onlyOwner whenDescriptorNotLocked {
        descriptor = _descriptor;
    }

    /**
     * @notice Lock the descriptor.
     * @dev This cannot be reversed and is only callable by the owner when not locked.
     */
    function lockDescriptor() external override onlyOwner whenDescriptorNotLocked {
        isDescriptorLocked = true;
    }

    /**
     * @notice Set the token seeder.
     * @dev Only callable by the owner when not locked.
     */
    function setSeeder(IVerbsSeeder _seeder) external override onlyOwner whenSeederNotLocked {
        seeder = _seeder;
    }

    /**
     * @notice Lock the seeder.
     * @dev This cannot be reversed and is only callable by the owner when not locked.
     */
    function lockSeeder() external override onlyOwner whenSeederNotLocked {
        isSeederLocked = true;
    }

    function setNounsContract(NounsContract nouns) external override onlyOwner {
        nounsContract = nouns;
    }
    
}
function lockDescriptor() external override onlyOwner whenDescriptorNotLocked {
    isDescriptorLocked = true;
}

/**
 * @notice Lock the descriptor.
 * @dev This cannot be reversed and is only callable by the owner when not locked.
 */