// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "./ERC721A.sol";
import "hardhat/console.sol";

contract TheBearCollective is Ownable, ERC721A, ReentrancyGuard {
    using SafeMath for uint256;

    enum STAGE {
        NOT_STARTED,
        PRESALE,
        PUBLIC
    }

    struct SaleConfig {
        uint256 saleStart;
        uint256 mintPrice;
        uint256 mintLimit;
        uint256 minted;
        bytes slot;
    }

    SaleConfig public ogSaleConfig;
    SaleConfig public wlSaleConfig;
    SaleConfig public publicSaleConfig;

    address[] public airdrop;
    mapping(address => bool) public freeMintEligible;
    mapping(address => bool) public freeMintClaimed;

    address payable public devWallet =
        payable(0x40638c0528eaC13FB5931131B70D7AF295368ABb);
    bool public set = false;
    bool public paused = false;
    uint256 public presaleEndTime = 0;
    string public _baseTokenURI;

    constructor(
        string memory _name,
        string memory _symbol,
        uint256 _maxMintsPerBatch,
        uint256 _collectionSize
    ) ERC721A(_name, _symbol, _maxMintsPerBatch, _collectionSize) {}

    //@dev Set the current sale configs
    //@dev Note the slot provided to prevent conflicts, and if a new slow is provided, this clears the mapping!
    function setSaleConfigs(
        SaleConfig memory _ogSaleConfig,
        SaleConfig memory _wlSaleConfig,
        SaleConfig memory _publicSaleConfig,
        uint256 _presaleEndTime
    ) public onlyOwner {
        require(
            _ogSaleConfig.saleStart == _wlSaleConfig.saleStart,
            "Presale configs must be the same"
        );
        require(
            _presaleEndTime > _ogSaleConfig.saleStart &&
                _presaleEndTime <= _publicSaleConfig.saleStart,
            "Presale end must be between presale start and public start"
        );
        ogSaleConfig = _ogSaleConfig;
        wlSaleConfig = _wlSaleConfig;
        publicSaleConfig = _publicSaleConfig;
        presaleEndTime = _presaleEndTime;
        set = true;
    }

    //@dev SaleConfig does not directly hold the allowed mapping
    //@dev It only holds the pointer address of where the mapping is at
    //@dev Have to load it using assembly, and get the value accordingly.
    //@dev If mapping(address => bool) map and map starts at slot x
    //@dev we can find map[address] value by loading the value at keccak256(address . slot)
    function getAllowList(SaleConfig memory saleConfig, address inputAddress)
        internal
        view
        returns (bool result)
    {
        bytes32 slot = keccak256(saleConfig.slot);
        assembly {
            //Store address in free memory pointer
            mstore(0, inputAddress)
            //Store starting slot in the next 32 bits
            mstore(32, slot)
            //Compute keccak256(address.slot)
            let hash := keccak256(0, 64)
            //Value is located there and load it
            result := sload(hash)
        }
    }

    function setAllowList(
        SaleConfig memory saleConfig,
        address inputAddress,
        bool input
    ) internal {
        bytes32 slot = keccak256(saleConfig.slot);
        assembly {
            //Store address in free memory pointer
            mstore(0, inputAddress)
            //Store starting slot in the next 32 bits
            mstore(32, slot)
            //Compute keccak256(address.slot)
            let hash := keccak256(0, 64)
            //Value is located there and load it
            sstore(hash, input)
        }
    }

    function isOg(address addr) public view returns (bool) {
        return getAllowList(ogSaleConfig, addr);
    }

    function isWl(address addr) public view returns (bool) {
        return getAllowList(wlSaleConfig, addr);
    }

    //@dev Based on the current block time, we extract the current stage the sale process is at
    function getCurrentStage() public view returns (STAGE) {
        uint256 currentTime = block.timestamp;
        uint256 ogSaleTime = ogSaleConfig.saleStart;
        uint256 wlSaleTime = wlSaleConfig.saleStart;
        uint256 publicSaleTime = publicSaleConfig.saleStart;
        //ogSaleTime and wlSaleTime is the same. Ensure its below public saletime
        if (
            currentTime >= ogSaleTime &&
            currentTime >= wlSaleTime &&
            currentTime < presaleEndTime
        ) {
            return STAGE.PRESALE;
        } else if (currentTime >= publicSaleTime) {
            return STAGE.PUBLIC;
        }
        return STAGE.NOT_STARTED;
    }

    //@dev Gets the current saleConfig
    //@dev Internal since we need to return the saleconfig struct storage pointer
    function getSaleConfig(address _caller)
        internal
        view
        returns (SaleConfig storage)
    {
        STAGE stage = getCurrentStage();
        if (stage == STAGE.PRESALE) {
            bool isOgAllowed = isOg(_caller);
            bool isWlAllowed = isWl(_caller);
            require(
                isOgAllowed || isWlAllowed,
                "Minter is not in OG or WL list"
            );
            if (isOgAllowed && !isWlAllowed) {
                return ogSaleConfig;
            } else if (!isOgAllowed && isWlAllowed) {
                return wlSaleConfig;
            }
        }
        return publicSaleConfig;
    }

    //@dev Adds a list of addresses to the airdroppedAddress list
    function addAddressListToAirdrop(address[] memory addresses)
        public
        onlyOwner
    {
        for (uint256 i; i < addresses.length; i++) {
            airdrop.push(addresses[i]);
        }
    }

    //@dev Adds an address to the OG whitelist
    function addAddressListToOg(address[] memory addresses) public onlyOwner {
        for (uint256 i; i < addresses.length; i++) {
            setAllowList(ogSaleConfig, addresses[i], true);
        }
    }

    //@dev Adds an address to the WL whitelist
    function addAddressListToWl(address[] memory addresses) public onlyOwner {
        for (uint256 i; i < addresses.length; i++) {
            setAllowList(wlSaleConfig, addresses[i], true);
        }
    }

    //@dev Marks an address as being eligble fo a free first mint.
    function addAddressListToFreeMint(address[] memory addresses)
        public
        onlyOwner
    {
        for (uint256 i; i < addresses.length; i++) {
            freeMintEligible[addresses[i]] = true;
        }
    }

    //@dev Mints 1 NFT to all addresses in the airdrop
    function mintAirdrop() public onlyOwner {
        for (uint256 i; i < airdrop.length; i++) {
            _safeMint(airdrop[i], 1);
        }
        delete airdrop;
    }

    //@dev Execute the mint function
    //@dev Calculates the total cost by getting the current mint price
    //@dev If user is eligble for a free mint and has not claimed
    //@dev Reduce the total cost by 1 NFT
    function mint(uint256 quantity) public payable {
        STAGE stage = getCurrentStage();
        require(stage != STAGE.NOT_STARTED, "Mint has not started yet");
        require(!paused, "Minting is currently paused");
        require(set, "Configs has yet to be set");

        SaleConfig storage saleConfig = getSaleConfig(msg.sender);
        uint256 limit = saleConfig.mintLimit;
        uint256 totalPrice = quantity.mul(saleConfig.mintPrice);

        //If eliglble for a free mint and not claimed, reduce the total cost by 1 NFT
        if (freeMintEligible[msg.sender] && !freeMintClaimed[msg.sender]) {
            totalPrice = totalPrice.sub(saleConfig.mintPrice);
            freeMintClaimed[msg.sender] = true;
        }

        //Ensure that the total minted for the user is below the limit which is n, or n+1 if free mint eligble
        require(
            _numberMinted(msg.sender) + quantity <= limit,
            "Exceed maximum amount of mints possible"
        );
        refundIfOver(totalPrice);
        _safeMint(msg.sender, quantity);
    }

    //@Dev check if the sent amount is at least the minimum required, and then transfer to this contract
    function refundIfOver(uint256 totalPrice) private {
        require(msg.value >= totalPrice, "Need to send more ETH.");
        if (msg.value > totalPrice) {
            payable(msg.sender).transfer(msg.value - totalPrice);
        }
    }

    //@dev Get the baseURI
    //@dev Override's ERC721 _baseURI() function
    function _baseURI() internal view virtual override returns (string memory) {
        return _baseTokenURI;
    }

    //@dev Set the baseURI
    function setBaseURI(string calldata baseURI) external onlyOwner {
        _baseTokenURI = baseURI;
    }

    //@dev Return the collection size
    function getCollectionSize() external view returns (uint256) {
        return collectionSize;
    }

    //@dev Return max mint
    function getMaxBatchMint() external view returns (uint256) {
        return maxBatchSize;
    }

    function withdrawMoney() external onlyOwner nonReentrant {
        (bool success, ) = devWallet.call{value: address(this).balance}("");
        require(success, "Transfer failed.");
    }

    function setOwnersExplicit(uint256 quantity)
        external
        onlyOwner
        nonReentrant
    {
        _setOwnersExplicit(quantity);
    }

    //@dev TogglePause
    function togglePause() public onlyOwner {
        paused = !paused;
    }

    function getPaused() public view returns (bool) {
        return paused;
    }

    function numberMinted(address owner) public view returns (uint256) {
        return _numberMinted(owner);
    }

    function getAirdropAddresses() public view returns (address[] memory) {
        return airdrop;
    }
}
[{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"uint256","name":"_maxMintsPerBatch","type":"uint256"},{"internalType":"uint256","name":"_collectionSize","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"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":"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":"_baseTokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"addresses","type":"address[]"}],"name":"addAddressListToAirdrop","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"addresses","type":"address[]"}],"name":"addAddressListToFreeMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"addresses","type":"address[]"}],"name":"addAddressListToOg","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"addresses","type":"address[]"}],"name":"addAddressListToWl","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"airdrop","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"devWallet","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"freeMintClaimed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"freeMintEligible","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAirdropAddresses","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"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":"getCollectionSize","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentStage","outputs":[{"internalType":"enum TheBearCollective.STAGE","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMaxBatchMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"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":"addr","type":"address"}],"name":"isOg","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"isWl","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"quantity","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"mintAirdrop","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextOwnerToExplicitlySet","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"numberMinted","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ogSaleConfig","outputs":[{"internalType":"uint256","name":"saleStart","type":"uint256"},{"internalType":"uint256","name":"mintPrice","type":"uint256"},{"internalType":"uint256","name":"mintLimit","type":"uint256"},{"internalType":"uint256","name":"minted","type":"uint256"},{"internalType":"bytes","name":"slot","type":"bytes"}],"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":"presaleEndTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"publicSaleConfig","outputs":[{"internalType":"uint256","name":"saleStart","type":"uint256"},{"internalType":"uint256","name":"mintPrice","type":"uint256"},{"internalType":"uint256","name":"mintLimit","type":"uint256"},{"internalType":"uint256","name":"minted","type":"uint256"},{"internalType":"bytes","name":"slot","type":"bytes"}],"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":"nonpayable","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":"nonpayable","type":"function"},{"inputs":[],"name":"set","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","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":"baseURI","type":"string"}],"name":"setBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"quantity","type":"uint256"}],"name":"setOwnersExplicit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"saleStart","type":"uint256"},{"internalType":"uint256","name":"mintPrice","type":"uint256"},{"internalType":"uint256","name":"mintLimit","type":"uint256"},{"internalType":"uint256","name":"minted","type":"uint256"},{"internalType":"bytes","name":"slot","type":"bytes"}],"internalType":"struct TheBearCollective.SaleConfig","name":"_ogSaleConfig","type":"tuple"},{"components":[{"internalType":"uint256","name":"saleStart","type":"uint256"},{"internalType":"uint256","name":"mintPrice","type":"uint256"},{"internalType":"uint256","name":"mintLimit","type":"uint256"},{"internalType":"uint256","name":"minted","type":"uint256"},{"internalType":"bytes","name":"slot","type":"bytes"}],"internalType":"struct TheBearCollective.SaleConfig","name":"_wlSaleConfig","type":"tuple"},{"components":[{"internalType":"uint256","name":"saleStart","type":"uint256"},{"internalType":"uint256","name":"mintPrice","type":"uint256"},{"internalType":"uint256","name":"mintLimit","type":"uint256"},{"internalType":"uint256","name":"minted","type":"uint256"},{"internalType":"bytes","name":"slot","type":"bytes"}],"internalType":"struct TheBearCollective.SaleConfig","name":"_publicSaleConfig","type":"tuple"},{"internalType":"uint256","name":"_presaleEndTime","type":"uint256"}],"name":"setSaleConfigs","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":[],"name":"togglePause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","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":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawMoney","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"wlSaleConfig","outputs":[{"internalType":"uint256","name":"saleStart","type":"uint256"},{"internalType":"uint256","name":"mintPrice","type":"uint256"},{"internalType":"uint256","name":"mintLimit","type":"uint256"},{"internalType":"uint256","name":"minted","type":"uint256"},{"internalType":"bytes","name":"slot","type":"bytes"}],"stateMutability":"view","type":"function"}]