contract RoyalEggs is ERC721Enumerable, Ownable, AccessControl {

    using Strings for uint256; 

    string baseURI;
    bytes32 public constant TEAM_ROLE = keccak256("TEAM_ROLE");
    uint256 public constant TOTAL_EGGS_SUPPLY = 8888;
    uint256 public constant max_mint_transactions = 11;
    uint256 public constant max_eggs_per_wallet = 12;
    uint256 public cost = 0.05 ether;
    uint256 public pre_mint_supply = 2000;
    uint256 public giveaway_supply = 300;

    mapping(address => bool) private _pre_sale_minters;

    bool public paused_mint = true;
    bool public paused_pre_mint = true;
    bool public revealed = false;
    string public notRevealedUri;

    // withdraw addresses
    address royaleggs_treasury;

    modifier whenMintNotPaused() {
        require(!paused_mint, "RoyalEggs: mint is paused");
        _;
    }

    modifier whenPreMintNotPaused() {
        require(!paused_pre_mint, "RoyalEggs: pre mint is paused");
        _;
    }

    modifier preMintAllowedAccount(address account) {
        require(is_pre_mint_allowed(account), "RoyalEggs: account is not allowed to pre mint");
        _;
    }

    event MintPaused(address account);

    event MintUnpaused(address account);

    event PreMintPaused(address account);

    event PreMintUnpaused(address account);

    event setPreMintRole(address account);

    event redeemedPreMint(address account);


    constructor(
        string memory _name,
        string memory _symbol,
        address _royaleggs_treasury,
        string memory _initBaseURI,
        string memory _initNotRevealedUri
    )
        ERC721(_name, _symbol)
    {   

        royaleggs_treasury = _royaleggs_treasury;
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _setupRole(TEAM_ROLE, msg.sender);

        setBaseURI(_initBaseURI);
        setNotRevealedURI(_initNotRevealedUri);

    }

    fallback() external payable { }

    receive() external payable { }

    function mint(uint256 num) public payable whenMintNotPaused(){
        uint256 supply = totalSupply();
        uint256 tokenCount = balanceOf(msg.sender);
        require( num <= max_mint_transactions,                              "RoyalEggs: Max Royal Eggs per mint exceeded ! try less " );
        require( tokenCount + num <= max_eggs_per_wallet,                   "RoyalEggs: Max Royal Eggs per wallet exceeded ! WOW Many thanks <3" );
        require( supply + num <= TOTAL_EGGS_SUPPLY - giveaway_supply,       "RoyalEggs: No More Eggs :(" );
        require( msg.value >= cost * num,                                   "RoyalEggs: Not enough to cover the transaction " );

        for(uint256 i; i < num; i++){
            _safeMint( msg.sender, supply + i );
        }
    }

    function pre_mint() public payable whenPreMintNotPaused() preMintAllowedAccount(msg.sender){
        require( pre_mint_supply > 0,         "RoyalEggs: No More Eggs for pre mint" );
        require( msg.value >= cost,            "RoyalEggs: Not enough to cover cost " );
        _pre_sale_minters[msg.sender] = false;
        pre_mint_supply -= 1;
        uint256 supply = totalSupply();
        _safeMint( msg.sender, supply);
        emit redeemedPreMint(msg.sender);
    }

    function giveAway(address _to) external onlyRole(TEAM_ROLE) {
        require(giveaway_supply > 0, "RoyalEggs: No More Free Eggs" );
        giveaway_supply -= 1;
        uint256 supply = totalSupply();
        _safeMint( _to, supply);
    }

    function setCost(uint256 _newCost) public onlyRole(TEAM_ROLE) {
        cost = _newCost;
    
    }
    
    function pauseMint() public onlyRole(TEAM_ROLE) {
        paused_mint = true;
        emit MintPaused(msg.sender);
    }

    function unpauseMint() public onlyRole(TEAM_ROLE) {
        paused_mint = false;
        emit MintUnpaused(msg.sender);
    }

    function pausePreMint() public onlyRole(TEAM_ROLE) {
        paused_pre_mint = true;
        emit PreMintPaused(msg.sender);
    }

    function unpausePreMint() public onlyRole(TEAM_ROLE) {
        paused_pre_mint = false;
        emit PreMintUnpaused(msg.sender);
    }

    function updateTreasuryWalletAddress(address _royaleggs_treasury) public onlyRole(TEAM_ROLE) {
        royaleggs_treasury = _royaleggs_treasury;
    }

    function setPreMintRoleBatch(address[] calldata _addresses) external onlyRole(TEAM_ROLE) {
        for(uint256 i; i < _addresses.length; i++){
            _pre_sale_minters[_addresses[i]] = true;
            emit setPreMintRole(_addresses[i]);
        }
    }

    function reaveal() public onlyRole(TEAM_ROLE) {
        revealed = true;
    }

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

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

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

    function withdrawAllToTreasury() public onlyRole(TEAM_ROLE) {
        uint256 _balance = address(this).balance ;
        require(_balance > 0, "RoyalEggs: No Balance");
        require(payable(royaleggs_treasury).send(_balance), "RoyalEggs: FAILED to withdraw ");
    }

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

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

    function walletOfOwner(address _owner) public view returns(uint256[] memory) {
        uint256 tokenCount = balanceOf(_owner);

        uint256[] memory tokensId = new uint256[](tokenCount);
        for(uint256 i; i < tokenCount; i++){
            tokensId[i] = tokenOfOwnerByIndex(_owner, i);
        }
        return tokensId;
    }

    function getTreasuryWallet() public view onlyRole(TEAM_ROLE) returns(address wallet) {
        return royaleggs_treasury;
    }

    function supportsInterface(bytes4 interfaceId) public view override(ERC721Enumerable, AccessControl) returns (bool) {
        return super.supportsInterface(interfaceId);
    }

    function is_pre_mint_allowed(address account) public view returns (bool) {
        return _pre_sale_minters[account];
    }
}
function _baseURI() internal view virtual override returns (string memory) {
    return baseURI;
}

// internal