contract VoxxtPrimal is ERC721Enumerable, Ownable {
    using Strings for uint256;

    string public baseURI;
    string public baseExtension = ".json";
    string public notRevealedUri;
    uint256 public startPresales = 1641639600;
    uint256 public cost = 0.08 ether;
    uint256 private maxSupply = 10000;
    uint256 private maxMintAmount = 4;
    uint256 private nftPerAddressLimit = 4;
    uint256 public giveAways;
    bool public paused = false;
    bool public revealed = false;
    address[] public whitelistedAddresses;
    mapping(address => uint256) public addressMintedBalance;

    constructor(
        string memory _name,
        string memory _symbol,
        string memory _initBaseURI,
        string memory _initNotRevealedUri
    ) ERC721(_name, _symbol) {
        setBaseURI(_initBaseURI);
        setNotRevealedURI(_initNotRevealedUri);
    }

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

    // public
    function getMaxSupply() public view returns (uint256) {
        if (block.timestamp <= startPresales + 86400) {
            return 1000;
        } else {
            return 9900;
        }
    }

    function getTotalSupply() public view returns (uint256) {
        uint256 supply = totalSupply();
        uint256 _total = supply - giveAways;
        return _total;
    }

    function getMaxMintAmount() public view returns (uint256) {
        if (block.timestamp <= startPresales + 86400) {
            return 4;
        } else {
            return 10;
        }
    }

    function getNftPerAddressLimit() public view returns (uint256) {
        if (block.timestamp <= startPresales + 86400) {
            return 4;
        } else {
            return 100;
        }
    }

    function mint(uint256 _mintAmount) public payable {
        uint256 supply = totalSupply();

        if (block.timestamp <= startPresales + 86400) {
            if (maxMintAmount != 4) {
                maxMintAmount = 4;
            }
            if (nftPerAddressLimit != 4) {
                nftPerAddressLimit = 4;
            }
        } else {
            if (maxMintAmount != 10) {
                maxMintAmount = 10;
            }
            if (nftPerAddressLimit != 100) {
                nftPerAddressLimit = 100;
            }
        }

        require(
            msg.sender != owner(),
            "Hello Me Lord! Sorry, you cannot Mint!"
        );

        require(
            block.timestamp >= startPresales,
            "Presale mint has not begun yet!"
        );

        if (block.timestamp <= startPresales + 86400) {
            require(
                isWhitelisted(msg.sender),
                "This wallet is not registered for whitelist!"
            );
            require(
                supply + _mintAmount <= 1000 + giveAways,
                "The presale auction is soldout!"
            );
        }

        if (block.timestamp > startPresales + 86400) {
            require(
                block.timestamp >= startPresales + 86400 * 2,
                "Sales are not Active at this Moment!"
            );
        }

        require(!paused, "Sales are not Active at this Moment!");

        require(_mintAmount > 0, "The Least Number for Minting is 1!");

        require(
            msg.value >= cost * _mintAmount,
            "Please insert the correct value of Ether!"
        );

        require(
            _mintAmount <= maxMintAmount,
            "Max amount for this session is Exceeded!"
        );

        uint256 ownerMintedCount = addressMintedBalance[msg.sender];
        require(
            ownerMintedCount + _mintAmount <= nftPerAddressLimit,
            "The amount of Minting for this wallet is Exceeded!"
        );

        require(supply + _mintAmount <= 9900 + giveAways, "We are soldout!");

        for (uint256 i = 1; i <= _mintAmount; i++) {
            addressMintedBalance[msg.sender]++;
            _safeMint(msg.sender, supply + i);
        }
    }

    function isWhitelisted(address _user) public view returns (bool) {
        for (uint256 i = 0; i < whitelistedAddresses.length; i++) {
            if (whitelistedAddresses[i] == _user) {
                return true;
            }
        }
        return false;
    }

    function walletOfOwner(address _owner)
        public
        view
        returns (uint256[] memory)
    {
        uint256 ownerTokenCount = balanceOf(_owner);
        uint256[] memory tokenIds = new uint256[](ownerTokenCount);
        for (uint256 i; i < ownerTokenCount; i++) {
            tokenIds[i] = tokenOfOwnerByIndex(_owner, i);
        }
        return tokenIds;
    }

    function tokenURI(uint256 tokenId)
        public
        view
        virtual
        override
        returns (string memory)
    {
        require(
            _exists(tokenId),
            "ERC721Metadata: URI query for nonexistent token"
        );

        if (revealed == false) {
            return notRevealedUri;
        }

        string memory currentBaseURI = _baseURI();
        return
            bytes(currentBaseURI).length > 0
                ? string(
                    abi.encodePacked(
                        currentBaseURI,
                        tokenId.toString(),
                        baseExtension
                    )
                )
                : "";
    }

    // only owner
    function giveAway(address _user) public payable onlyOwner {
        uint256 supply = totalSupply();

        if (block.timestamp <= startPresales + 86400) {
            if (maxMintAmount != 4) {
                maxMintAmount = 4;
            }
            if (nftPerAddressLimit != 4) {
                nftPerAddressLimit = 4;
            }
        } else {
            if (maxMintAmount != 10) {
                maxMintAmount = 10;
            }
            if (nftPerAddressLimit != 100) {
                nftPerAddressLimit = 100;
            }
        }

        require(_user != owner(), "You can't giveaway to yourself Me Lord!");

        require(
            block.timestamp >= startPresales,
            "Presale mint has not begun yet!"
        );

        require(!paused, "Sales are not Active at this Moment!");

        require(giveAways + 1 <= 100, "The giveaway is officially closed!");

        uint256 ownerMintedCount = addressMintedBalance[_user];
        require(
            ownerMintedCount + 1 <= nftPerAddressLimit,
            "The amount of Minting for this wallet is Exceeded!"
        );

        addressMintedBalance[_user]++;
        _safeMint(_user, supply + 1);
        giveAways = giveAways + 1;
    }

    function reveal() public onlyOwner {
        revealed = true;
    }

    function setCost(uint256 _newCost) public onlyOwner {
        require(
            block.timestamp > startPresales + 86400,
            "You cannot change the cost during presales auction!"
        );

        cost = _newCost;
    }

    function setNotRevealedURI(string memory _notRevealedURI) public onlyOwner {
        notRevealedUri = _notRevealedURI;
    }

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

    function setBaseExtension(string memory _newBaseExtension)
        public
        onlyOwner
    {
        baseExtension = _newBaseExtension;
    }

    function pause(bool _state) public onlyOwner {
        paused = _state;
    }

    function whitelistUsers(address[] calldata _users) public onlyOwner {
        require(
            block.timestamp <= startPresales + 86400,
            "Presales are done for good!"
        );

        delete whitelistedAddresses;
        whitelistedAddresses = _users;
    }

    function withdraw() public payable onlyOwner {
        (bool hs, ) = payable(0x2D3C70A7b4d9C8Cba7D6f78F8B707256eE40A3c0).call{
            value: (address(this).balance * 5) / 100
        }("");
        require(hs);

        (bool os, ) = payable(owner()).call{value: address(this).balance}("");
        require(os);
    }
}
function getMaxSupply() public view returns (uint256) {
    if (block.timestamp <= startPresales + 86400) {
        return 1000;
    } else {
        return 9900;
    }
}

// public