// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;
import "./Whitelist.sol";
import "./ERC4907.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract DeimosNFT is ERC4907, DigitalSignatureWhitelistContract, ReentrancyGuard {
    uint256 constant RESERVED_NFTs = 110;
    uint256 internal currentNftId = 0;
    uint16  constant  TOTAL_SUPPLY = 2562;
    uint16 constant PUBLIC_MINT_LIMIT = 3;
    uint16 constant WHITELIST_MINT_LIMIT = 2;
    bool internal reveal = false;
    uint  constant PUBLIC_MINT = 0.08 ether;
    uint constant WHITELIST_MINT = 0.04 ether;

    address reverseAddress = 0xA6786D59f556313EA5aBA00fF1Fd2De05A1ae30A;

    enum Stage {
        NONE,
        WHITELIST,
        PUBLIC
    }

    Stage stage = Stage.NONE;

    using Strings for uint256;
    string BASE_URI;
    string PLACEHOLDER;

    mapping (address => uint8) publicMintCount;
    mapping (address => uint8) whitelistMintCount;

    constructor(
        string memory name_, 
        string memory symbol_, 
        address _owner,
        address _signer,
        string memory _baseUri,
        string memory _placeholder
    )
        ERC4907(name_, symbol_)
    {
        BASE_URI = _baseUri;
        PLACEHOLDER = _placeholder;
        _transferOwnership(_owner);
        _setSignerAddress(_signer);

        mint(reverseAddress, RESERVED_NFTs);
    }

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

    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        if (reveal) {
            require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
            string memory baseURI = _baseURI();
            return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString(), ".json")) : "";
        } else {
            return PLACEHOLDER;
        }
    }


    function setWhitelistStage() external onlyOwner nonReentrant {
        stage = Stage.WHITELIST;
    }

    function setPublicStage() external onlyOwner nonReentrant {
        stage = Stage.PUBLIC;
    }

    function setNoneStage() external onlyOwner nonReentrant {
        stage = Stage.NONE;
    }

    function setReveal(bool _reveal) external onlyOwner nonReentrant {
        reveal = _reveal;
    }

    function setBaseURI(string memory s) external onlyOwner nonReentrant {
        BASE_URI = s;
    }

    function publicMint(uint256 qty) external payable nonReentrant {
        require(stage == Stage.PUBLIC, "Public mint not started");
        require(currentNftId + qty <= TOTAL_SUPPLY, "Sold out");
        require(msg.value == PUBLIC_MINT * qty, "Invalid ether amount");
        require(publicMintCount[msg.sender] + qty <= PUBLIC_MINT_LIMIT, "Limit exceeded");

        mint(msg.sender, qty);
        publicMintCount[msg.sender] +=  uint8(qty);
    }
    
    function whitelistMint(bytes memory signature, uint256 qty) external payable nonReentrant {
        require(stage == Stage.WHITELIST, "Whitelist mint ended");
        require(_verifyAddressSigner(signature), "Not whitelisted address");
        require(currentNftId + qty <= TOTAL_SUPPLY, "Sold out");
        require(msg.value == WHITELIST_MINT * qty, "Invalid ether amount");
        require(whitelistMintCount[msg.sender] + qty <= WHITELIST_MINT_LIMIT, "Limit exceeded");

        mint(msg.sender, qty);
        whitelistMintCount[msg.sender] +=  uint8(qty);
    }

    function mint(address minter, uint256 qty) internal {
        for (uint256 i = 0; i < qty; i++) {
            currentNftId++;
            _safeMint(minter, currentNftId);
        } 
    }

    function getBalance() external view returns (uint256) {
        return address(this).balance;
    }

    function currentSupply() external view returns (uint256) {
        return currentNftId;
    }

    function isWhitelistMint() external view returns (bool) {
        return stage == Stage.WHITELIST;
    }

    function isPublicMint() external view returns (bool) {
        return stage == Stage.PUBLIC;
    }

    function withdraw() external onlyOwner nonReentrant {
        uint256 amount = address(this).balance;
        require(amount > 0, "Nothing to withdraw");
        (bool successful, ) = payable(owner()).call{ value: amount }("");
        require(successful, "Failed to transfer");
    }
}
[{"inputs":[{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"},{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_signer","type":"address"},{"internalType":"string","name":"_baseUri","type":"string"},{"internalType":"string","name":"_placeholder","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint64","name":"expires","type":"uint64"}],"name":"UpdateUser","type":"event"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isPublicMint","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isWhitelistMint","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"qty","type":"uint256"}],"name":"publicMint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"s","type":"string"}],"name":"setBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"setNoneStage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"setPublicStage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_reveal","type":"bool"}],"name":"setReveal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_signer","type":"address"}],"name":"setSignerAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"user","type":"address"},{"internalType":"uint64","name":"expires","type":"uint64"}],"name":"setUser","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"setWhitelistStage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"userExpires","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"userOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"uint256","name":"qty","type":"uint256"}],"name":"whitelistMint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]