contract BloodShedBearsGen0 is ERC721Enum, Ownable {
    using Strings for uint256;

    uint256 constant public MAX_SUPPLY = 7000;
    uint256 constant public PASS_MINT_LIMIT = 5;
    uint256 constant public WHITELIST_LIMIT = 3;
    uint256 constant public PUBLIC_MINT_LIMIT = 5;
    uint256 public TEAM_RESERVE_AVAILABLE_MINTS = 100;
    uint256 public COST = 0.07 ether;

    bool public isMintPassMintActive;
    bool public isWhiteListActive;
    bool public isSaleActive;

    bytes32 public merkleTreeRoot;

    mapping(address => uint256) public presaleWhitelistMints;
    mapping(address => uint256) public mintPassPaidMints;
    mapping(address => uint256) public mintPassFreeMints;

    string public baseURI;

    address public mintPassAddress = 0x0000000000000000000000000000000000000000;

    constructor(
        string memory name_,
        string memory symbol_,
        string memory initBaseURI_,
        address mintPassAddress_
    ) ERC721P(name_, symbol_) {
        mintPassAddress = mintPassAddress_;
        setBaseURI(initBaseURI_);
    }

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

    function setMintPassContract(address _contractAddress) external onlyOwner {
        mintPassAddress = _contractAddress;
    }

    function setCost(uint256 cost_) external onlyOwner {
        COST = cost_;
    }

    function flipMintPassMintState() external onlyOwner {
        isMintPassMintActive = !isMintPassMintActive;
    }

    function flipWhitelistState() external onlyOwner {
        isWhiteListActive = !isWhiteListActive;
    }

    function flipSaleState() external onlyOwner {
        isSaleActive = !isSaleActive;
    }

    function setMerkleTreeRoot(bytes32 merkleTreeRoot_) external onlyOwner {
        merkleTreeRoot = merkleTreeRoot_;
    }

    function mint(uint256 mintAmount_, bytes32[] calldata proof) external payable {

        require(isSaleActive || isWhiteListActive || isMintPassMintActive, "INACTIVE");
        require(mintAmount_ > 0, "WHY");
        uint256 totalSupply = totalSupply();

        if (isMintPassMintActive) {
            uint256 passBalance = IERC1155(mintPassAddress).balanceOf(msg.sender, 0);

            require(passBalance > 0, "NO MINT PASS");

            uint256 availableFreeMints = passBalance - mintPassFreeMints[msg.sender];
            uint256 availablePaidMints = passBalance * PASS_MINT_LIMIT - passBalance - mintPassPaidMints[msg.sender];
            require(mintAmount_ <= availableFreeMints + availablePaidMints, "TOO MANY");
            require(mintAmount_ <= availableFreeMints || (msg.value >= COST * (mintAmount_ - availableFreeMints)), "NOT ENOUGH");

            if (mintAmount_ >= availableFreeMints) {
                mintPassFreeMints[msg.sender] += availableFreeMints;
            } else {
                mintPassFreeMints[msg.sender] += mintAmount_;
            }

            if (mintAmount_ > availableFreeMints) {
                mintPassPaidMints[msg.sender] += (mintAmount_ - availableFreeMints);
            }

            delete passBalance;
            delete availableFreeMints;
            delete availablePaidMints;

        } else if (isWhiteListActive) {
            require(
                _verify(_leaf(msg.sender), proof) || IERC1155(mintPassAddress).balanceOf(msg.sender, 0) > 0,
                "NOT WL"
            );

            uint256 availableMints = WHITELIST_LIMIT - presaleWhitelistMints[msg.sender];

            require(mintAmount_ <= availableMints, "TOO MANY");
            require(totalSupply + mintAmount_ <= MAX_SUPPLY, "TOO MANY");
            require(msg.value >= COST * mintAmount_ , "NOT ENOUGH");

            presaleWhitelistMints[msg.sender] += mintAmount_;

        } else {
            require(mintAmount_ <= PUBLIC_MINT_LIMIT, "WRONG AMOUNT" );
            require(totalSupply + mintAmount_ <= MAX_SUPPLY, "TOO MANY" );
            require(msg.value >= COST * mintAmount_);
        }

        for (uint256 i = 0; i < mintAmount_; i++) {
            _safeMint(msg.sender, totalSupply + i);
        }

        delete totalSupply;
    }

    function _leaf(address account) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(account));
    }

    function _verify(bytes32 _leafNode, bytes32[] memory proof) internal view returns (bool) {
        return MerkleProof.verify(proof, merkleTreeRoot, _leafNode);
    }

    function withdraw() external onlyOwner {
        require(payable(msg.sender).send(address(this).balance));
    }

    function isOwnerOfBatch(uint256[] calldata tokenIds_, address address_) external view returns (bool) {
        bool ownership = true;

        for (uint256 i = 0; i < tokenIds_.length; ++i) {
            ownership = ownership && (ownerOf(tokenIds_[i]) == address_);
        }

        return ownership;
    }

    // admin minting
    function reserve(address _to, uint256 _reserveAmount) external onlyOwner {
        uint256 supply = totalSupply();
        require(
            _reserveAmount > 0 && _reserveAmount <= TEAM_RESERVE_AVAILABLE_MINTS,
            "Not enough reserve left for team"
        );
        for (uint256 i = 0; i < _reserveAmount; i++) {
            _safeMint(_to, supply + i);
        }
        TEAM_RESERVE_AVAILABLE_MINTS -= _reserveAmount;
    }

    function tokenURI(uint256 _tokenId) external view virtual override returns (string memory) {
        require(_exists(_tokenId), "ERC721Metadata: Nonexistent token");
        string memory currentBaseURI = _baseURI();
        return bytes(currentBaseURI).length > 0	? string(abi.encodePacked(currentBaseURI, _tokenId.toString(), ".json")) : "";
    }

    function setBaseURI(string memory _newBaseURI) public onlyOwner {
        baseURI = _newBaseURI;
    }
}
function reserve(address _to, uint256 _reserveAmount) external onlyOwner {
    uint256 supply = totalSupply();
    require(
        _reserveAmount > 0 && _reserveAmount <= TEAM_RESERVE_AVAILABLE_MINTS,
        "Not enough reserve left for team"
    );
    for (uint256 i = 0; i < _reserveAmount; i++) {
        _safeMint(_to, supply + i);
    }
    TEAM_RESERVE_AVAILABLE_MINTS -= _reserveAmount;
}

// admin minting