contract DegenProject is ERC721A, Ownable, Pausable {
    uint public price;
    uint public maxSupply;
    uint public maxBatchSize = 3;
    uint public maxDLmint = 3;
    uint public maxDLRewardmint = 1;
    bytes32 public DLRoot;
    bytes32 public DLRewardRoot;
    bool public isDListOnly;
    string private _baseTokenURI;

    mapping(address => uint) public DLClaimed;
    mapping(address => uint) public DLRewardClaimed;


    constructor(uint supply, uint mintPrice, string memory baseTokenURI) ERC721A("Degen Project", "DPNFT") {
        maxSupply = supply;
        price = mintPrice;
        _baseTokenURI = baseTokenURI;
    }

    /// @dev Returns the tokenIds of the address. O(totalSupply) in complexity.
    function tokensOfOwner(address owner) external view returns (uint256[] memory) {
        unchecked {
            uint256[] memory a = new uint256[](balanceOf(owner)); 
            uint256 end = _currentIndex;
            uint256 tokenIdsIdx;
            address currOwnershipAddr;
            for (uint256 i; i < end; i++) {
                TokenOwnership memory ownership = _ownerships[i];
                if (ownership.burned) {
                    continue;
                }
                if (ownership.addr != address(0)) {
                    currOwnershipAddr = ownership.addr;
                }
                if (currOwnershipAddr == owner) {
                    a[tokenIdsIdx++] = i;
                }
            }
            return a;    
        }
    }

    function setPrice(uint mintPrice) public onlyOwner {
        price = mintPrice;
    }

    function safeMintDL(uint256 quantity, bytes32[] calldata signature) public whenNotPaused payable {
        require(((DLClaimed[msg.sender]+quantity) <= maxDLmint), "DPNFT: Quantity Exceeds Max Allowed");
        bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
        require(
            MerkleProof.verify(signature, DLRoot, leaf),
            "DPNFT: Address not whitelisted."
        );
        if (quantity > maxBatchSize)           revert IncorrectBatchSize();
        if (totalSupply() + quantity > maxSupply)     revert AmountExceedsSupply();
        if ((quantity * price) != msg.value)   revert IncorrectPayment();

        _safeMint(msg.sender, quantity);
        payable(owner()).transfer(msg.value);
        DLClaimed[msg.sender] += quantity;
    }

    function safeMintReward(bytes32[] calldata signature) public whenNotPaused {
        require(((DLRewardClaimed[msg.sender]+1) <= maxDLRewardmint), "DPNFT: Quantity Exceeds Max Allowed");
        bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
        require(
            MerkleProof.verify(signature, DLRewardRoot, leaf),
            "DPNFT: Address not whitelisted."
        );
        if (totalSupply() + 1 > maxSupply)     revert AmountExceedsSupply();

        _safeMint(msg.sender, 1);
        DLRewardClaimed[msg.sender] += 1;
    }

    function safeMint(uint256 quantity) public whenNotPaused payable {
        if (isDListOnly)  revert DListedOnly();
        if (quantity > maxBatchSize)           revert IncorrectBatchSize();
        if (totalSupply() + quantity > maxSupply)     revert AmountExceedsSupply();
        if ((quantity * price) != msg.value)   revert IncorrectPayment();

        _safeMint(msg.sender, quantity);
        payable(owner()).transfer(msg.value);
    }

    function setMaxBatchSize(uint size) public onlyOwner {
        maxBatchSize = size;
    }

    function setBaseURI(string memory baseURI) public onlyOwner {
        _baseTokenURI = baseURI;
    }

    function setDLRoot(bytes32 root) public onlyOwner {
        DLRoot = root;
    }

    function setDLRewardRoot(bytes32 root) public onlyOwner {
        DLRewardRoot = root;
    }

    function setDListStatus(bool _isDListOnly) public onlyOwner {
        isDListOnly = _isDListOnly;
    }

    function _baseURI() internal view virtual override returns (string memory) {
        return _baseTokenURI;
    }

    function _startTokenId() internal view virtual override returns (uint256) {
        return 1;
    }

    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }
}
function tokensOfOwner(address owner) external view returns (uint256[] memory) {
    unchecked {
        uint256[] memory a = new uint256[](balanceOf(owner)); 
        uint256 end = _currentIndex;
        uint256 tokenIdsIdx;
        address currOwnershipAddr;
        for (uint256 i; i < end; i++) {
            TokenOwnership memory ownership = _ownerships[i];
            if (ownership.burned) {
                continue;
            }
            if (ownership.addr != address(0)) {
                currOwnershipAddr = ownership.addr;
            }
            if (currOwnershipAddr == owner) {
                a[tokenIdsIdx++] = i;
            }
        }
        return a;    
    }
}

/// @dev Returns the tokenIds of the address. O(totalSupply) in complexity.