contract FirstClassTaz is ERC721, Ownable {
    using Strings for uint256;
    using Counters for Counters.Counter;

    Counters.Counter private _totalSupply;
    uint256 private _presaleSupply = 0;

    string private baseURI;
    string public baseExtension = ".json";
    string public notRevealedUri;

    uint256 public preSaleCost = 0.05 ether;
    uint256 public cost = 0.08 ether;
    uint256 public maxSupply = 10000;
    uint256 public preSaleMaxSupply = 2000;
    uint256 public maxMintAmountPresale = 10;
    uint256 public maxMintAmount = 20;
    uint256 public nftPerAddressLimitPresale = 10;
    uint256 public nftPerAddressLimit = 100;
    uint256 public preSaleDate = 1642971600;
    uint256 public preSaleEndDate = 1643058000;
    uint256 public publicSaleDate = 1643144400;

    bool public paused = false;
    bool public revealed = false;
    
    bytes32 private rootHash;
    mapping(address => uint256) public addressMintedBalance;

    address public wallet1 = 0x64231DB161C803ABcaA948821a06F35Ed5F02C20;
    address public wallet2 = 0x379e7CC29d36a45F85D8393FB83614D2A60B4f30;
    address public wallet3 = 0x9648c97e4372b13Ff532091F5d5aCdeDA85CE9eC;

    constructor(string memory _initNotRevealedUri) ERC721("First Class Taz", "FCT") {
        setNotRevealedURI(_initNotRevealedUri);
    }

    // MODIFIERS
    modifier notPaused {
        require(!paused, "the contract is paused");
        _;
    }

    modifier presaleStarted {
        require(block.timestamp >= preSaleDate, "presale has not started yet");
        _;
    }

    modifier saleStarted {
        require(block.timestamp >= publicSaleDate, "sale has not started yet");
        _;
    }

    modifier minimumMintAmount(uint256 _mintAmount) {
        require(_mintAmount > 0, "need to mint at least 1 NFT");
        _;
    }

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

    // PRESALE MINT
    function presaleMint(uint256 _mintAmount, bytes32[] calldata _proof) public payable notPaused presaleStarted minimumMintAmount(_mintAmount) {
        uint256 supply = totalSupply();
        uint256 ownerMintedCount = addressMintedBalance[msg.sender];

        require(block.timestamp <= preSaleEndDate, "presale is finished");
        require(ownerMintedCount + _mintAmount <= nftPerAddressLimitPresale, "max NFT per address exceeded for presale");
        require(msg.value >= preSaleCost * _mintAmount, "insufficient funds");
        require(_mintAmount <= maxMintAmountPresale,"max mint amount per transaction exceeded");
        require(_presaleSupply + _mintAmount <= preSaleMaxSupply,"max NFT presale limit exceeded");

        bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
        require(MerkleProof.verify(_proof, rootHash, leaf), "invalid proof");

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

    // MINT
    function mint(uint256 _mintAmount) public payable notPaused saleStarted minimumMintAmount(_mintAmount) {
        uint256 supply = totalSupply();
        uint256 ownerMintedCount = addressMintedBalance[msg.sender];

        require(ownerMintedCount + _mintAmount <= nftPerAddressLimit,"max NFT per address exceeded");
        require(msg.value >= cost * _mintAmount, "insufficient funds");
        require(_mintAmount <= maxMintAmount,"max mint amount per transaction exceeded");
        require(supply + _mintAmount <= maxSupply, "max NFT limit exceeded");

        for (uint256 i = 1; i <= _mintAmount; i++) {
            addressMintedBalance[msg.sender]++;
            _totalSupply.increment();
            _safeMint(msg.sender, supply + i);
        }
    }
    
    function gift(uint256 _mintAmount, address destination) public onlyOwner {
        require(_mintAmount > 0, "need to mint at least 1 NFT");
        uint256 supply = totalSupply();
        require(supply + _mintAmount <= maxSupply, "max NFT limit exceeded");

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

    // PUBLIC VIEWS
    function totalSupply() public view returns (uint256) {
        return _totalSupply.current();
    }

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

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

    function getCurrentCost() public view returns (uint256) {
        if (block.timestamp < preSaleEndDate) {
            return preSaleCost;
        } else {
            return cost;
        }
    }

    // ONLY OWNER VIEWS
    function getBaseURI() public view onlyOwner returns (string memory) {
        return baseURI;
    }

    function getContractBalance() public view onlyOwner returns (uint256) {
        return address(this).balance;
    }

    // ONLY OWNER SETTERS
    function setRootHash(bytes32 _rootHash) public onlyOwner {
        rootHash = _rootHash;
    }

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

    function pause(bool _state) public onlyOwner {
        paused = _state;
    }
    
    function setNftPerAddressLimitPreSale(uint256 _limit) public onlyOwner {
        nftPerAddressLimitPresale = _limit;
    }

    function setNftPerAddressLimit(uint256 _limit) public onlyOwner {
        nftPerAddressLimit = _limit;
    }

    function setPresaleCost(uint256 _newCost) public onlyOwner {
        preSaleCost = _newCost;
    }

    function setCost(uint256 _newCost) public onlyOwner {
        cost = _newCost;
    }
    
    function setmaxMintAmountPreSale(uint256 _newMaxMintAmount) public onlyOwner {
        maxMintAmountPresale = _newMaxMintAmount;
    }

    function setMaxMintAmount(uint256 _newMaxMintAmount) public onlyOwner {
        maxMintAmount = _newMaxMintAmount;
    }

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

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

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

    function setPresaleMaxSupply(uint256 _newPresaleMaxSupply) public onlyOwner {
        preSaleMaxSupply = _newPresaleMaxSupply;
    }

    function setMaxSupply(uint256 _maxSupply) public onlyOwner {
        maxSupply = _maxSupply;
    }

    function setPreSaleDate(uint256 _preSaleDate) public onlyOwner {
        preSaleDate = _preSaleDate;
    }

    function setPreSaleEndDate(uint256 _preSaleEndDate) public onlyOwner {
        preSaleEndDate = _preSaleEndDate;
    }

    function setPublicSaleDate(uint256 _publicSaleDate) public onlyOwner {
        publicSaleDate = _publicSaleDate;
    }

    function withdraw() public onlyOwner {
        uint256 balance = address(this).balance;

        uint256 amount1 = (balance * 0.2 ether) / 1 ether;
        uint256 amount2 = (balance * 0.05 ether) / 1 ether;
        uint256 amount3 = (balance * 0.25 ether) / 1 ether;
        uint256 amount4 = balance - amount1 - amount2 - amount3;
        
        payable(msg.sender).transfer(amount4);
        payable(wallet1).transfer(amount1);
        payable(wallet2).transfer(amount2);
        payable(wallet3).transfer(amount3);
    }
}
function setRootHash(bytes32 _rootHash) public onlyOwner {
    rootHash = _rootHash;
}

// ONLY OWNER SETTERS