// SPDX-License-Identifier: MIT
// Developer: https://twitter.com/0xArtCro
pragma solidity ^0.8.15;

import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "erc721a/contracts/ERC721A.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract BoredCatzClub is Ownable, ERC721A, ReentrancyGuard {
    string private baseTokenURI;

    // Minting
    bool public paused;

    uint256 public phase1Timestamp = 1677326400;
    uint256 public phase2Timestamp = 1677931200;
    uint256 public phase3Timestamp = 1678536000;
    uint256 public phase4Timestamp = 1679140800;

    mapping(address => uint256) private oglist;
    mapping(address => bool) private whitelist;

    uint256 public maxMint = 99;
    uint256 public price2 = 0.007 ether;
    uint256 public price3 = 0.0095 ether;
    uint256 public price4 = 0.015 ether;

    // Payout
    address public immutable crodooWallet;
    address public projectWallet;
    address public nickWallet;

    // Errors
    error MintNotOpen();
    error PausedMint();
    error InsufficientPayment();
    error OverMint();
    error MintLimit();
    error FreeMintLimit();

    // Constants
    uint256 public constant MAX_SUPPLY = 9999;
    uint256 public constant LAUNCHPAD_ROYALTIES = 5;

    // Structs
    struct TokenInfo {
        uint256 id;
        uint256 royalties;
    }

    struct MintInfo {
        bool paused;
        uint256 supply;
        uint256 maxSupply;
        uint256 maxMint;
        uint256 phase1Timestamp;
        uint256 phase2Timestamp;
        uint256 phase3Timestamp;
        uint256 phase4Timestamp;
    }

    constructor(
        string memory uri_,
        address projectWallet_,
        address nickWallet_,
        address crodooWallet_
    ) ERC721A("Bored Catz Club", "BCC") {
        baseTokenURI = uri_;
        projectWallet = projectWallet_;
        nickWallet = nickWallet_;
        crodooWallet = crodooWallet_;
    }

    // Owner
    // - Setup
    function setProjectWallet(address projectWallet_) public onlyOwner {
        projectWallet = projectWallet_;
    }

    function setBaseTokenURI(string memory uri) public onlyOwner {
        baseTokenURI = uri;
    }

    function togglePause() public onlyOwner {
        paused = !paused;
    }

    function addWhitelist(address[] memory addresses_) external onlyOwner {
        for (uint256 i = 0; i < addresses_.length; ) {
            whitelist[addresses_[i]] = true;
            unchecked {
                ++i;
            }
        }
    }

    function addOglist(
        address[] memory addresses_,
        uint256[] memory amount_
    ) external onlyOwner {
        require(addresses_.length == amount_.length);
        for (uint256 i = 0; i < addresses_.length; ) {
            oglist[addresses_[i]] = amount_[i];
            unchecked {
                ++i;
            }
        }
    }

    function setTimestamps(
        uint256 phase1Timestamp_,
        uint256 phase2Timestamp_,
        uint256 phase3Timestamp_,
        uint256 phase4Timestamp_
    ) external onlyOwner {
        phase1Timestamp = phase1Timestamp_;
        phase2Timestamp = phase2Timestamp_;
        phase3Timestamp = phase3Timestamp_;
        phase4Timestamp = phase4Timestamp_;
    }

    function setMaxMint(uint256 maxMint_) external onlyOwner {
        maxMint = maxMint_;
    }

    function setPrice(
        uint256 price2_,
        uint256 price3_,
        uint256 price4_
    ) external onlyOwner {
        price2 = price2_;
        price3 = price3_;
        price4 = price4_;
    }

    // - Team minting
    function teamMint(uint256 amount, address to) external onlyOwner {
        uint currentSupply = totalSupply();

        if (currentSupply + amount > MAX_SUPPLY) revert OverMint();
        _mint(to, amount);
    }

    // - Team Funds
    function _payoutCroDoo(uint256 _amount) private {
        (bool success, ) = payable(crodooWallet).call{
            value: _amount,
            gas: 50000
        }("");
        require(success, "Cannot payout CroDoo");
    }

    function _payoutNick(uint256 _amount) private {
        (bool success, ) = payable(nickWallet).call{value: _amount, gas: 50000}(
            ""
        );
        require(success, "Cannot payout CroDoo");
    }

    function _payout(uint256 _amount) private {
        (bool success, ) = payable(projectWallet).call{
            value: _amount,
            gas: 50000
        }("");
        require(success, "Cannot payout");
    }

    // External
    function freeMint(uint256 amount) external nonReentrant {
        if (paused) revert PausedMint();
        if (block.timestamp < phase1Timestamp) revert MintNotOpen();
        if (amount > freeMintCount(_msgSender())) revert FreeMintLimit();

        uint currentSupply = totalSupply();
        if (currentSupply + amount > MAX_SUPPLY) revert OverMint();

        _mint(_msgSender(), amount);
        oglist[_msgSender()] -= amount;
    }

    function mint(uint256 amount) external payable nonReentrant {
        if (paused) revert PausedMint();
        if (!isWhitelisted(_msgSender()) && block.timestamp < phase3Timestamp)
            revert MintNotOpen();
        if (block.timestamp < phase2Timestamp) revert MintNotOpen();
        if (amount > maxMint) revert MintLimit();

        uint256 totalPrice = mintPrice() * amount;
        uint currentSupply = totalSupply();

        if (currentSupply + amount > MAX_SUPPLY) revert OverMint();
        if (msg.value != totalPrice) revert InsufficientPayment();

        _mint(_msgSender(), amount);

        uint256 royalties = (totalPrice * LAUNCHPAD_ROYALTIES) / 100;
        uint256 payout = totalPrice - royalties - royalties;

        _payout(payout);
        _payoutCroDoo(royalties);
        _payoutNick(royalties);
    }

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

    function tokenURI(
        uint _tokenId
    ) public view virtual override returns (string memory) {
        require(
            _exists(_tokenId),
            "ERC721Metadata: URI query for nonexistent token"
        );
        string memory _tokenURI = string(
            abi.encodePacked(baseTokenURI, Strings.toString(_tokenId), ".json")
        );

        return _tokenURI;
    }

    // Getters
    function getMintInfo() external view returns (MintInfo memory) {
        return
            MintInfo(
                paused,
                totalSupply(),
                MAX_SUPPLY,
                maxMint,
                phase1Timestamp,
                phase2Timestamp,
                phase3Timestamp,
                phase4Timestamp
            );
    }

    function freeMintCount(address _address) public view returns (uint256) {
        return oglist[_address];
    }

    function isWhitelisted(address _address) public view returns (bool) {
        return whitelist[_address];
    }

    function mintPrice() public view returns (uint256) {
        if (block.timestamp >= phase4Timestamp) return price4;
        if (block.timestamp >= phase3Timestamp) return price3;

        return price2;
    }

    // Override
    function _startTokenId() internal view virtual override returns (uint256) {
        return 1;
    }
}
[{"inputs":[{"internalType":"string","name":"uri_","type":"string"},{"internalType":"address","name":"projectWallet_","type":"address"},{"internalType":"address","name":"nickWallet_","type":"address"},{"internalType":"address","name":"crodooWallet_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ApprovalCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"ApprovalQueryForNonexistentToken","type":"error"},{"inputs":[],"name":"BalanceQueryForZeroAddress","type":"error"},{"inputs":[],"name":"FreeMintLimit","type":"error"},{"inputs":[],"name":"InsufficientPayment","type":"error"},{"inputs":[],"name":"MintERC2309QuantityExceedsLimit","type":"error"},{"inputs":[],"name":"MintLimit","type":"error"},{"inputs":[],"name":"MintNotOpen","type":"error"},{"inputs":[],"name":"MintToZeroAddress","type":"error"},{"inputs":[],"name":"MintZeroQuantity","type":"error"},{"inputs":[],"name":"OverMint","type":"error"},{"inputs":[],"name":"OwnerQueryForNonexistentToken","type":"error"},{"inputs":[],"name":"OwnershipNotInitializedForExtraData","type":"error"},{"inputs":[],"name":"PausedMint","type":"error"},{"inputs":[],"name":"TransferCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"TransferFromIncorrectOwner","type":"error"},{"inputs":[],"name":"TransferToNonERC721ReceiverImplementer","type":"error"},{"inputs":[],"name":"TransferToZeroAddress","type":"error"},{"inputs":[],"name":"URIQueryForNonexistentToken","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"fromTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"toTokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"ConsecutiveTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"LAUNCHPAD_ROYALTIES","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_SUPPLY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"addresses_","type":"address[]"},{"internalType":"uint256[]","name":"amount_","type":"uint256[]"}],"name":"addOglist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"addresses_","type":"address[]"}],"name":"addWhitelist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"crodooWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"freeMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"freeMintCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMintInfo","outputs":[{"components":[{"internalType":"bool","name":"paused","type":"bool"},{"internalType":"uint256","name":"supply","type":"uint256"},{"internalType":"uint256","name":"maxSupply","type":"uint256"},{"internalType":"uint256","name":"maxMint","type":"uint256"},{"internalType":"uint256","name":"phase1Timestamp","type":"uint256"},{"internalType":"uint256","name":"phase2Timestamp","type":"uint256"},{"internalType":"uint256","name":"phase3Timestamp","type":"uint256"},{"internalType":"uint256","name":"phase4Timestamp","type":"uint256"}],"internalType":"struct BoredCatzClub.MintInfo","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"isWhitelisted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"mintPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nickWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"phase1Timestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"phase2Timestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"phase3Timestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"phase4Timestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"price2","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"price3","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"price4","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"projectWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"uri","type":"string"}],"name":"setBaseTokenURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"maxMint_","type":"uint256"}],"name":"setMaxMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"price2_","type":"uint256"},{"internalType":"uint256","name":"price3_","type":"uint256"},{"internalType":"uint256","name":"price4_","type":"uint256"}],"name":"setPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"projectWallet_","type":"address"}],"name":"setProjectWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"phase1Timestamp_","type":"uint256"},{"internalType":"uint256","name":"phase2Timestamp_","type":"uint256"},{"internalType":"uint256","name":"phase3Timestamp_","type":"uint256"},{"internalType":"uint256","name":"phase4Timestamp_","type":"uint256"}],"name":"setTimestamps","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"to","type":"address"}],"name":"teamMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"togglePause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]