contract BabyDeluxe is ERC721Enumerable, Ownable {
  using Strings for uint256;
  using Address for address;

  struct Conf {
        uint16 supply;
        uint16 _reserved;
        uint16 maxBabies;
        uint64 price;
    }
   
    address private controller;
    address public fundsWallet;
    bool public mintEnabled = false;
    bool public whitelistMintEnabled = false;
    uint8 public perMint = 10;
    uint16 public perWhitelistAccount = 5;
    uint16 public perAccount = 300;
    uint256 public imagesHash;
    string private defaultURI;
    string private baseURI;
    string private metaURI;
    mapping (address => bool) public whitelist;

    

    mapping(uint256 => uint256) private metaHashes;

    Conf private conf;

    event updateMetaHash(address, uint256, uint256);

    modifier isController(address sender) {
        require(
            sender != address(0) && (sender == owner() || sender == controller)
        );
        _;
    }

    /**
     * @notice Setup ERC721 and initial config
     */
    constructor(
        string memory name,
        string memory symbol,
        string memory _defaultURI,
        address _fundsWallet
    ) ERC721(name, symbol) {
        require(_fundsWallet != address(0), "Zero address error");
        fundsWallet = _fundsWallet;
        conf = Conf(0, 100, 3500, 50000000000000000);
        defaultURI = _defaultURI;
    }

    /**
     * @notice Send ETH to owners wallet 
     */
    function ownerWithdraw() public onlyOwner {
        uint256 balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }

    /**
     * @notice send contract eth to fundswallet 
     */
    function fundsWithdraw() public onlyOwner {
        uint256 balance = address(this).balance;
        payable(fundsWallet).transfer(balance);
    }

    /**
     * @notice Send reserved babies 
     * @param _to address to send reserved nfts to.
     * @param _amount number of nfts to send 
     */
    function fetchTeamReserved(address _to, uint16 _amount)
        public
        onlyOwner
    {
        require( _to !=  address(0), "Zero address error");
        require( _amount <= conf._reserved, "Exceeds reserved babies supply");
        uint16 supply = conf.supply;
        unchecked {
          for (uint8 i = 0; i < _amount; i++) {
              _safeMint(_to, supply++);
          }
        }
        conf.supply = supply;
        conf._reserved -= _amount;
    }

    /**
     * @notice Bring new Babies into the world.
     * @param amount Number of Babies to mint.
     * @dev Utilize unchecked {} and calldata for gas savings.
     */
    function mint(uint256 amount) public payable {
        require(mintEnabled, "Minting is disabled.");
        require(
            conf.supply + amount <= conf.maxBabies - conf._reserved,
            "Amount exceeds maximum supply of Babies."
        );
        require(
            balanceOf(msg.sender) + amount <= perAccount,
            "Amount exceeds current maximum mints per account."
        );
        require(
            amount <= perMint,
            "Amount exceeds current maximum Babies per mint."
        );
        require(
            conf.price * amount <= msg.value,
            "Ether value sent is not correct."
        );

        uint16 supply = conf.supply;
        unchecked {
            for (uint16 i = 0; i < amount; i++) {
                _safeMint(msg.sender, supply++);
            }
        }
        conf.supply = supply;
    }

    // Whitelist functions

    /**
     * @notice add addresses to whitelist in bulk.
     * @param addresses Arrary of whitelisted addresses
     * @dev Only authorized accounts.
     */
    function bulkWhitelist(address[] memory addresses) public onlyOwner {
        for(uint i=0; i < addresses.length; i++){
            address addr = addresses[i];
            if(whitelist[addr] != true && addr != address(0)){
                whitelist[addr] = true;
            }
        }
    }

    /**
     * @notice Whitelist Bring new Babies into the world.
     * @param amount Number of Babies to mint.
     * @dev Utilize unchecked {} and calldata for gas savings.
     */
    function whitelistMint(uint256 amount) public payable {
        require(whitelistMintEnabled, "Whitelist Minting is disabled.");
        require( whitelist[msg.sender] == true, "Only whitelist can mint" );
        require(
            conf.supply + amount <= conf.maxBabies - conf._reserved,
            "Amount exceeds maximum supply of Babies."
        );
        require(
            balanceOf(msg.sender) + amount <= perWhitelistAccount,
            "Amount exceeds current maximum mints per account."
        );
        require(
            amount <= perMint,
            "Amount exceeds current maximum Babies per mint."
        );
        require(
            conf.price * amount <= msg.value,
            "Ether value sent is not correct."
        );

        uint16 supply = conf.supply;
        unchecked {
            for (uint16 i = 0; i < amount; i++) {
                _safeMint(msg.sender, supply++);
            }
        }
        conf.supply = supply;
    }

    /**
     * @notice Toggle white list mint status.
     * @dev Only authorized accounts.
     */
    function toggleWhitelistMintEnabled() public onlyOwner {
        whitelistMintEnabled = !whitelistMintEnabled;
    }

    /**
     * @notice Set price.
     * @param newPrice new minting price
     * @dev Only authorized accounts.
     */
    function setPrice(uint64 newPrice) public onlyOwner {
        conf.price = newPrice;
    }

    /**
     * @notice Set funds wallet.
     * @param _fundsWallet new funds wallet address 
     * @dev Only authorized accounts.
     */
    function setFundsWallet(address _fundsWallet) public onlyOwner {
        require(_fundsWallet != address(0), "Zero address error");
        fundsWallet = _fundsWallet;
    }

    /**
     * @notice Set meta hash for token.
     * @param id Token id.
     * @param _hash Hash value.
     * @dev Only authorized accounts.
     */
    function setMetaHash(uint256 id, uint256 _hash)
        public
        isController(msg.sender)
    {
        require(_exists(id), "Token does not exist.");
        metaHashes[id] = _hash;
        emit updateMetaHash(msg.sender, id, _hash);
    }

    /**
     * @notice Return mint price.
     */
    function getMintPrice() public view returns (uint64) {
        return conf.price;
    }

    /**
     * @notice Return meta hash for token.
     */
    function getMetaHash(uint256 id) public view returns (uint256) {
        return metaHashes[id];
    }

    /**
     * @notice Sets URI for image hashes.
     */
    function setImagesHash(uint256 _imagesHash) public onlyOwner {
        imagesHash = _imagesHash;
    }

    /**
     * @notice Toggles minting state.
     */
    function toggleMintEnabled() public onlyOwner {
        mintEnabled = !mintEnabled;
    }

    /**
     * @notice Sets max Babies per mint.
     */
    function setPerMint(uint8 _perMint) public onlyOwner {
        perMint = _perMint;
    }

    /**
     * @notice Sets max mints per account.
     */
    function setPerAccount(uint16 _perAccount) public onlyOwner {
        perAccount = _perAccount;
    }
    
    /**
     * @notice Sets max mints per whitelist account.
     */
    function setPerWhitelistAccount(uint16 _perAccount) public onlyOwner {
        perWhitelistAccount = _perAccount;
    }

    /**
     * @notice Set base URI.
     */
    function setBaseURI(string memory _baseURI) public onlyOwner {
        baseURI = _baseURI;
    }

    /**
     * @notice Set default URI.
     */
    function setDefaultURI(string memory _defaultURI) public onlyOwner {
        defaultURI = _defaultURI;
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override
        returns (string memory)
    {
        require(_exists(tokenId), "Token does not exist.");

        if (bytes(baseURI).length == 0) {
            return defaultURI;
        } else {
            return string(abi.encodePacked(baseURI, (tokenId).toString()));
        }
    }

}
function setPerMint(uint8 _perMint) public onlyOwner {
    perMint = _perMint;
}

/**
 * @notice Sets max Babies per mint.
 */