contract NFT is ERC721A, Ownable, ReentrancyGuard {
    using SafeMath for uint256;
    using Strings for uint256;

    bool public preSaleActive = false;
    bool public publicSaleActive = false;

    bool public paused = true;
    bool public revealed = false;

    string public NETWORK_PROVENANCE = "";
    string public notRevealedUri;

    uint256 public maxSupply; // 2500
    uint256 public maxAirDropSupply; // 100
    uint256 public currentAirDropSupply; 
    uint256 public preSalePrice; //0.05 ETH --> 50000000000000000
    uint256 public publicSalePrice; //0.05 ETH --> 50000000000000000

    uint public maxPurchase; // 2
    uint public maxPurchaseOG; // 4
    uint public publicPurchase; // 5
    string private _baseURIextended;

    mapping(address => bool) public isWhiteListed;
    mapping(address => bool) public isWhiteListedOG;

    constructor(string memory name, string memory symbol, uint256 _maxSupply, uint256 _publicPurchase, uint256 _maxPurchase, uint256 _maxPurchaseOG, uint256 _preSalePrice, uint256 _publicSalePrice, uint256 _maxAirDropSupply) ERC721A(name, symbol) ReentrancyGuard() {
        maxSupply = _maxSupply;
        preSalePrice = _preSalePrice;
        publicSalePrice = _publicSalePrice;
        publicPurchase = _publicPurchase;
        maxPurchase = _maxPurchase;
        maxPurchaseOG = _maxPurchaseOG;
        maxAirDropSupply = _maxAirDropSupply;
    }

    function preSaleMint(uint256 _amount)external payable nonReentrant{

        require(preSaleActive, "NFT:Pre-sale is not active");
        require(isWhiteListed[msg.sender], "NFT:Sender is not whitelisted");
        mint(_amount, true);
    }

    function publicSaleMint(uint256 _amount)external payable nonReentrant{
        require(publicSaleActive, "NFT:Public-sale is not active");
        mint(_amount, false);
    }

    function mint(uint256 _amount, bool _state)internal{
        require(!paused, "NFT: contract is paused");
        require(totalSupply().add(_amount) <= maxSupply, "NFT: minting would exceed total supply");
        if(publicSaleActive){
            require(balanceOf(msg.sender).add(_amount) <= publicPurchase, "NFT-Public: You can't mint any more tokens");
        }
        else{
            if(isWhiteListedOG[msg.sender]){
                require(balanceOf(msg.sender).add(_amount) <= maxPurchaseOG, "NFT-OG: You can't mint any more tokens");
            }
            else{
                require(balanceOf(msg.sender).add(_amount) <= maxPurchase, "NFT: You can't mint any more tokens");
            }
        }
        if(_state){
            require(preSalePrice.mul(_amount) <= msg.value, "NFT: Ether value sent for presale mint is not correct");
        }
        else{
            require(publicSalePrice.mul(_amount) <= msg.value, "NFT: Ether value sent for public mint is not correct");
        }
        uint mintIndex = totalSupply();
        for (uint256 ind = 1; ind <= _amount; ind++) {
            mintIndex.add(ind);
        }
         _safeMint(msg.sender, _amount);
    }

    function getTotalSupply() public view returns (uint){
        return totalSupply();
    }

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

    function setBaseURI(string calldata baseURI_) external onlyOwner {
        _baseURIextended = baseURI_;
    }


    function addWhiteListedAddresses(address[] memory _address) external onlyOwner {
        for (uint256 i = 0; i < _address.length; i++) {
            require(!isWhiteListed[_address[i]], "NFT: address is already white listed");
            isWhiteListed[_address[i]] = true;
        }
    }
    function addWhiteListedAddressesOG(address[] memory _address) external onlyOwner {
        for (uint256 i = 0; i < _address.length; i++) {
            require(!isWhiteListedOG[_address[i]], "NFT: address is already OG listed");
            isWhiteListed[_address[i]] = true;
            isWhiteListedOG[_address[i]] = true;
        }
    }

    function togglePauseState() external onlyOwner {
        paused = !paused;
    }

    function togglePreSale()external onlyOwner {
        preSaleActive = !preSaleActive;
    }

    function setPreSalePrice(uint256 _preSalePrice)external onlyOwner {
        preSalePrice = _preSalePrice;
    }

    function togglePublicSale()external onlyOwner {
        publicSaleActive = !publicSaleActive;
    }

    function setPublicSalePrice(uint256 _publicSalePrice)external onlyOwner {
        publicSalePrice = _publicSalePrice;
    }

    function airDrop(address[] memory _address)external onlyOwner{
        uint256 mintIndex = totalSupply();
        require(currentAirDropSupply + _address.length <= maxAirDropSupply, "NFT: Maximum Air Drop Limit Reached");
        require(mintIndex.add(_address.length) <= maxSupply, "NFT: minting would exceed total supply");
        for(uint256 i = 0; i < _address.length; i++){
            mintIndex.add(i);
            _safeMint(_address[i],1);
        }
        currentAirDropSupply += _address.length;
    }

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

    function withdraw() external onlyOwner {
        uint balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }
    /*     
    * Set provenance once it's calculated
    */
    function setProvenanceHash(string memory provenanceHash) external onlyOwner {
        NETWORK_PROVENANCE = provenanceHash;
    }

    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;
        }
        tokenId+=1;
        string memory currentBaseURI = _baseURI();
        return
            bytes(currentBaseURI).length > 0
                ? string(
                    abi.encodePacked(
                        currentBaseURI,
                        tokenId.toString(),
                        ".json"
                    )
                )
                : "";
    }
    
    function setNotRevealedURI(string memory _notRevealedURI) external onlyOwner {
        notRevealedUri = _notRevealedURI;
    }

}
/**
 * @title NFT contract
 * @dev Extends ERC721 Non-Fungible Token Standard basic implementation
 */
function setProvenanceHash(string memory provenanceHash) external onlyOwner {
    NETWORK_PROVENANCE = provenanceHash;
}

/*     
 * Set provenance once it's calculated
 */