contract MetaArt_MemberShip is ERC721Enumerable, Ownable, ReentrancyGuard {
    using Strings for uint256;
    using Counters for Counters.Counter;
    Counters.Counter internal usertokenCounters;
    Counters.Counter internal ownertokenCounters;


    /** =========== variables ========== */

    uint256 public constant MAX_AMOUNT_USER = 8500;

    // 348 left to mint for team
    uint256 public constant MAX_AMOUNT_TEAM = 348;

    uint8 public constant ONE_TIME_MINT_MAX = 20;

    uint256 public constant MAX_AMOUNT = MAX_AMOUNT_USER + MAX_AMOUNT_TEAM;

    string private constant ORIGINAL_METADATA = 'ipfs://QmeR3AikX9wsdPtEw3d4faKT6N5Ss1bTjNzzFfRekyzfN2/originalMetaData.json';

    uint256 private constant META_PRICE = 250000000000000000; // 0.25 ETH 0.25 * 10**18 gwei

    string private baseuri_;
    
    mapping ( address => bool ) internal userMinted;

    mapping ( address => bool ) internal whiteListed;

    address payable private vault;

    constructor (address vaultAddress) ERC721("Meta.Art MemberShip", "METAART") Ownable() {
        vault = payable(vaultAddress);
    }

    /** ========== public view functions ========== */
    function getBaseUri() public view returns (string memory) {
        return baseuri_;
    }

    function getCardPrice() public pure returns (uint256) {
        return META_PRICE;
    }

    function getAccountListed( address account ) public view returns ( bool ) {
        return whiteListed[account];
    }

    function getCurrentTokenId() public view returns ( uint256 ) {
        return usertokenCounters.current();
    }

    function getVault() public view returns ( address ) {
        return vault;
    }

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

        if(bytes(baseuri_).length == 0) {
            return ORIGINAL_METADATA;
        }

        string memory base = _baseURI();
        return string(abi.encodePacked(base, tokenId.toString(), ".json"));
    }

    /** ========== main functions ========== */

    function mint() external payable nonReentrant returns (uint256)  {

        require(!userMinted[_msgSender()], "mint: you have claimed one token");
        require(usertokenCounters.current() < MAX_AMOUNT_USER, "mint: all tokens have been minted");
        // users must pay for the NFT
        require(msg.value >= META_PRICE, "mint: not enough to pay");

        usertokenCounters.increment();
        uint256 mintingTokenId = usertokenCounters.current();

        // data update
        userMinted[_msgSender()] = true;

        // mint
        _safeMint(_msgSender(), mintingTokenId);

        emit claimed(_msgSender(), mintingTokenId);

        return mintingTokenId;
    }


    function forWhiteList() external {
        
        require(whiteListed[_msgSender()] && !userMinted[_msgSender()], "forWhiteList: Sorry, you're not allowed");

        _claim(_msgSender());

        whiteListed[_msgSender()] = false;
    } 
    
    function burn(uint256 tokenId) public virtual {
        //solhint-disable-next-line max-line-length
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721Burnable: caller is not owner nor approved");
        _burn(tokenId);
    }

    /** ========== admin functions ========== */

    function setBaseUri(string memory newBaseUri) external onlyOwner {
        baseuri_ = newBaseUri;

        emit baseUpdated(baseuri_);
    }

    function addNewList(address newListedAccount) external onlyOwner {
        require(newListedAccount != address(0), "addNewList: invalid address");
        require(whiteListed[newListedAccount] == false, "addNewList: the adding account has been added");

        whiteListed[newListedAccount] = true;

        emit newListed(newListedAccount);
    }

    function maxMint(address receiver, uint256 mintAmount) external onlyOwner {
        require(mintAmount <= ONE_TIME_MINT_MAX, "ownerClaim: minting amount excceeds ONE_TIME_MINT_MAX(20)");

        for(uint256 i = 0; i < mintAmount; i++) {
            _claim(receiver);
        }
    }

    function resetVault(address newVault) external onlyOwner {
        require(newVault != address(0), "resetVault: invalid address");
        
        vault = payable(newVault);
    }

    function receiveProfit() external payable onlyOwner {
        require(vault != address(0), "receiveProfit: invalid address");
        
        vault.transfer(address(this).balance);
    }

    /** ========== internal view functions ========== */

    function _baseURI() internal view override returns (string memory) {
        return baseuri_;
    }

    /** ========== internal mutative functions ========= */

    function _claim(address receiver) internal {     
        require(receiver != address(0), "claim: invalid address");   
        
        ownertokenCounters.increment();
        uint256 mintId = ownertokenCounters.current() + MAX_AMOUNT_USER;
        require(mintId > MAX_AMOUNT_USER && mintId <= MAX_AMOUNT, "ownerClaim: all tokens have been claimed");

        // mint
        _safeMint(receiver, mintId);

        emit claimed(receiver, mintId);
    }

    /** ========== event ========== */
    event claimed ( address indexed account, uint256 indexed tokenId );

    event baseUpdated ( string newBaseUri );

    event newListed ( address indexed account );

}
function _baseURI() internal view override returns (string memory) {
        return baseuri_;
    }
/** ========== internal view functions ========== */