contract Demonzv2 is ERC721Enumerable, Ownable {
    using Strings for uint256;
    uint256 public MAX_TOKENS = 2000;
    uint256 public MAX_PER_WALLET = 50;
    uint256 public CURRENT_TOKEN_ID = 0; // for testing 
    string public BEGINNING_URI = "https://api.cryptodemonz.com/";
    string public ENDING_URI = ".json";
    bool public ALLOW_SACRIFCE = true;

    IDemonzv1 public demonzv1;
    constructor(address _demonzv1) ERC721 ("CryptoDemonzV2", "DEMONZv2") {
        demonzv1 = IDemonzv1(_demonzv1);
    }

    modifier safeSacrificing {
        require(ALLOW_SACRIFCE, "Sacrificing has not begun yet");
        _;
    }
    event tokenSacrificed(address _sender);    

    /// @notice standard minting function
    /// @param _amount to be minted
    function mintToken(uint256 _amount) public onlyOwner {
        require(totalSupply() + _amount <= MAX_TOKENS, "Not enough NFTs left to mint");
        _batchMint(_amount, msg.sender);
    }

    /// @notice will mint demonzv2 for burning demonzv1
    /// @param _ids array of demonzv1 ids to be burned
    function burnV1(uint256[] memory _ids) external safeSacrificing {
        uint256 i = 0;
        require(_ids.length % 3 == 0 && _ids.length <= 9, "Invalid list");
        require(totalSupply() + _ids.length <= MAX_TOKENS, "Not enough NFTs left to mint");
        require(balanceOf(msg.sender) + _ids.length <= MAX_PER_WALLET, "Exceeds wallet max allowed balance");
        for (i=0; i<_ids.length; ++i) {
            require(demonzv1.ownerOf(_ids[i]) == msg.sender, "Sender is not owner");
            demonzv1.safeTransferFrom(msg.sender, address(this), _ids[i]);
            demonzv1.burnToken(_ids[i]);
           }
        _batchMint(_ids.length / 3, msg.sender);
        // no need to increment token id here
        emit tokenSacrificed(msg.sender);
    }
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        return string(abi.encodePacked(BEGINNING_URI, tokenId.toString(), ENDING_URI));
    }
    function withdraw() external onlyOwner {
        payable(msg.sender).transfer(address(this).balance);
    }

    function setBeginningURI(string memory _new_uri) external onlyOwner {
        BEGINNING_URI = _new_uri;
    }
    function setEndingURI(string memory _new_uri) external onlyOwner {
        ENDING_URI = _new_uri;
    }
    function toggleSacrifice() external onlyOwner {
        ALLOW_SACRIFCE = !ALLOW_SACRIFCE;
    }
    function changeMaxPerWalletAmount(uint256 _amount) external onlyOwner {
        MAX_PER_WALLET = _amount;
    }
    /// @notice dummy function for unit testing
    function _incrementTokenId() internal {
        ++CURRENT_TOKEN_ID;
    }

    function _batchMint(uint256 _amount, address _sender) internal {
        for (uint256 i=0; i<_amount; ++i) {
            _safeMint(_sender, totalSupply());
            _incrementTokenId();
        }
    }

    /// @notice dummy function for unit testing
    function getCurrentTokenId() view external returns (uint256) {
        return CURRENT_TOKEN_ID;
    }
    /// @dev DO NOT FORMAT THIS TO PURE, even tho compiler is asking
    function onERC721Received(
        address,
        address,
        uint256,
        bytes calldata
    ) external returns (bytes4) {
        return
            bytes4(
                keccak256("onERC721Received(address,address,uint256,bytes)")
            );
    }
}
function _incrementTokenId() internal {
    ++CURRENT_TOKEN_ID;
}

/// @notice dummy function for unit testing