contract VoxxtPrimal is ERC721I, Ownable {
    constructor() payable ERC721I("Voxxt Primal","VP") {}
    
    // Project Settings
    uint256 public mintPrice = 0.08 ether;
    uint256 public maxTokens = 10000;
    
    // Whitelist Stuff
    uint256 public whitelistAmount = 1200; // 1200 (1000 + 200) >> (1200 - 276) = 924
    uint256 public mintsPerWhitelist = 4; // 4 mints per whitelist
    mapping(address => uint256) public addressToWhitelistMinted;
    mapping(address => bool) public isWhitelisted;

    bool public whitelistMintEnabled = false; // default false
    uint256 public whitelistMintStartTime = 1641726000; // Sun Jan 09 2022 11:00:00 GMT+0000

    // Public Mint Stuff
    uint256 public maxMintsPerTx = 10; // 10 mints per tx

    bool public publicMintEnabled = false; // default false
    uint256 public publicMintStartTime; // default unset

    // Modifiers
    modifier onlySender {
        require(msg.sender == tx.origin, 
            "No smart contracts!");
        _;
    }
    modifier whitelistMinting {
        require(whitelistMintEnabled && block.timestamp >= whitelistMintStartTime,
            "Whitelist Mints are not enabled yet!");
        _;
    }
    modifier publicMinting {
        require(publicMintEnabled && block.timestamp >= publicMintStartTime,
            "Public Mints are not enabled yet!");
        _;
    }

    // Owner Administration
    function setMintPrice(uint256 mintPrice_) external onlyOwner {
        mintPrice = mintPrice_;
    }
    function setMaxTokens(uint256 maxTokens_) external onlyOwner {
        require(maxTokens_ >= totalSupply, 
            "maxTokens cannot be set lower than totalSupply!");

        maxTokens = maxTokens_;
    }
    function setWhitelistAmount(uint256 whitelistAmount_) external onlyOwner {
        whitelistAmount = whitelistAmount_;
    }
    function setMintsPerWhitelist(uint256 mintsPerWhitelist_) external onlyOwner {
        mintsPerWhitelist = mintsPerWhitelist_;
    }
    function setMaxMintsPerTx(uint256 maxMintsPerTx_) external onlyOwner {
        maxMintsPerTx = maxMintsPerTx_;
    }
    function setWhitelists(address[] calldata addresses_, bool bool_) external onlyOwner {
        for (uint256 i = 0; i < addresses_.length; i++) {
            isWhitelisted[addresses_[i]] = bool_;
        }
    }
    function setWhitelistParams(bool whitelistMintEnabled_, uint256 whitelistMintStartTime_) external onlyOwner {
        whitelistMintEnabled = whitelistMintEnabled_;
        whitelistMintStartTime = whitelistMintStartTime_;
    }
    function setPublicMintParams(bool publicMintEnabled_, uint256 publicMintStartTime_) external onlyOwner {
        publicMintEnabled = publicMintEnabled_;
        publicMintStartTime = publicMintStartTime_;
    }
    function setBaseTokenURI(string memory uri_) external onlyOwner {
        _setBaseTokenURI(uri_);
    }
    function setBaseTokenURI_EXT(string memory ext_) external onlyOwner {
        _setBaseTokenURI_EXT(ext_);
    }

    // Internal Mint 
    function _mintMany(address to_, uint256 amount_) internal {
        require(maxTokens >= totalSupply + amount_,
            "Not enough tokens remaining!");

        uint256 _startId = totalSupply + 1; // iterate from 1
        
        for (uint256 i = 0; i < amount_; i++) {
            _mint(to_, _startId + i);
        }
    }

    // Owner Mint Functions
    function ownerMint(address to_, uint256 amount_) external onlyOwner {
        _mintMany(to_, amount_);
    }
    function ownerMintToMany(address[] calldata tos_, uint256[] calldata amounts_) external onlyOwner {
        require(tos_.length == amounts_.length, 
            "Array lengths mismatch!");
            
        for (uint256 i = 0; i < tos_.length; i++) {
            _mintMany(tos_[i], amounts_[i]);
        }
    }

    // Whitelist Mint Functions
    function whitelistMint(uint256 amount_) external payable onlySender whitelistMinting {
        require(isWhitelisted[msg.sender], 
            "You are not whitelisted!");
        require(mintsPerWhitelist >= amount_,
            "Amount exceeds max mints per whitelist!");
        require(mintsPerWhitelist >= addressToWhitelistMinted[msg.sender] + amount_,
            "You don't have enough whitelist mints remaining!");
        require(msg.value == amount_ * mintPrice, 
            "Invalid amount sent!");
        require(whitelistAmount >= totalSupply + amount_,
            "Not enough whitelist mints remaining!");
        
        addressToWhitelistMinted[msg.sender] += amount_;

        _mintMany(msg.sender, amount_);
    }

    // Public Mint Functions
    function publicMint(uint256 amount_) external payable onlySender publicMinting {
        require(maxMintsPerTx >= amount_, 
            "Amount exceeds max mints per tx!");
        require(msg.value == amount_ * mintPrice, 
            "Invalid amount sent!");
        require(maxTokens >= totalSupply + amount_,
            "Not enough tokens remaining!");
        
        _mintMany(msg.sender, amount_);
    }

    // Withdraw Funds
    function _sendETH(address payable address_, uint256 amount_) internal {
        (bool success, ) = payable(address_).call{value: amount_}("");
        require(success, "Transfer failed");
    }
    function withdraw() external onlyOwner {
        uint256 _balance = address(this).balance;
        uint256 _toShare1 = (_balance * 5) / 100;
        uint256 _toShare2 = _balance - _toShare1;

        _sendETH( payable(0x2D3C70A7b4d9C8Cba7D6f78F8B707256eE40A3c0), _toShare1);
        _sendETH( payable(msg.sender), _toShare2);
    }

    // Emergency Withdraw (if all fails!)
    mapping(address => bool) public shareSigned;
    function signShare() external {
        require(msg.sender == owner 
            || msg.sender == 0x2D3C70A7b4d9C8Cba7D6f78F8B707256eE40A3c0,
            "You cannot sign!");
        
        shareSigned[msg.sender] = true;
    }
    function emergencyWithdraw() external onlyOwner {
        require(shareSigned[msg.sender] 
            && shareSigned[0x2D3C70A7b4d9C8Cba7D6f78F8B707256eE40A3c0],
            "Both parties have not agreed to unlock this function!"); // both parties must sign

        _sendETH( payable(msg.sender), address(this).balance); // send contract eth to msg.sender
    }
}
function whitelistMint(uint256 amount_) external payable onlySender whitelistMinting {
    require(isWhitelisted[msg.sender], 
        "You are not whitelisted!");
    require(mintsPerWhitelist >= amount_,
        "Amount exceeds max mints per whitelist!");
    require(mintsPerWhitelist >= addressToWhitelistMinted[msg.sender] + amount_,
        "You don't have enough whitelist mints remaining!");
    require(msg.value == amount_ * mintPrice, 
        "Invalid amount sent!");
    require(whitelistAmount >= totalSupply + amount_,
        "Not enough whitelist mints remaining!");
        
    addressToWhitelistMinted[msg.sender] += amount_;

    _mintMany(msg.sender, amount_);
}

// Whitelist Mint Functions