contract MissCryptoClub is ERC721Enumerable, Ownable {

    using Strings for uint256;

    // base URI
    string _baseTokenURI;
    // Price of each Miss
    uint256 private _price = 0.04 ether;

    // Maximum amount of Miss in existance 
    uint256 public constant _max_miss = 10000;
    // Reserved Miss for grant 
    uint256 public _max_grant_miss = 50;
    // Reserved Miss for give away 
    uint256 public _max_reserved_miss = 100;
    // Reserved Miss for discount 
    uint256 public _max_discount_miss = 500;

    // Pause sales
    bool public _paused = true;
    // Pause grant
    bool public _grant_paused = true;

    // Cool cat contract address
    address internal coolcat;
    // Fame Lady Squad contract address
    address internal famelady;
    // World of Women contract address
    address internal wow;
    // Save grant token in order to not re-grant for same coolcat/famelady/wow token
    mapping (uint256 => bool) public coolcatMints;
    mapping (uint256 => bool) public fameladyMints;
    mapping (uint256 => bool) public wowMints;
    // Save address of granter in order to allow one grant for coolcat/famelady/wow owner
    mapping (address => bool) public grantMints;

    // team addresses
    address mcc_crypto = 0x9EFf64424F2FeDb6B4D74771Ddb81C5742FE2c2c;
    address luke_crypto = 0x799CD72D86A6AA46876C2689A13637Cbe6ae89da;
    address kristen_crypto = 0x3E4FE6d222F63064b5f61486625594597a091B9a;
    

    constructor(string memory tokenURI, address _coolcatAddr, address _fameladyAddr, address _wowAddr) 
    ERC721("MissCryptoClub", "MCC") {
        setBaseURI(tokenURI);
        coolcat = _coolcatAddr;
        famelady = _fameladyAddr;
        wow = _wowAddr;

        // team mint first 3 Miss
        _safeMint( mcc_crypto, 0);
        _safeMint( luke_crypto, 1);
        _safeMint( kristen_crypto, 2);
    }

    /**
    * @dev Mint Miss
    */
    function mint(uint256 numberOfMiss) public payable {
        uint256 supply = totalSupply();
        require( !_paused,                              "Sale paused" );
        require( msg.value >= _price * numberOfMiss,             "Ether sent is not correct" );
        require( supply + numberOfMiss < _max_miss - _max_grant_miss - _max_discount_miss - _max_reserved_miss,      "Exceeds maximum Miss supply" );
        require(numberOfMiss > 0, "You cannot mint 0 Miss.");
        require(numberOfMiss <= 20, "You are not allowed to buy this many miss at once.");

        for(uint256 i; i < numberOfMiss; i++){
            _safeMint( msg.sender, supply + i );
        }
    }

    /**
    * @dev Grant Miss
    */
    function grantMiss() external {
        require(!_grant_paused, "Grant is not started.");
        require(grantMints[msg.sender] == false, "Already grant with this address");
        require(1 <= _max_grant_miss, "Exceeds grant Miss supply");
        
        uint256 supply = totalSupply();

        uint256 coolcatNums = CoolCats(coolcat).balanceOf(msg.sender);
        uint256 fameladyNums = FameLadySquad(famelady).balanceOf(msg.sender);
        uint256 wowNums = WorldOfWomen(wow).balanceOf(msg.sender);
        require(coolcatNums > 0 || fameladyNums > 0 || wowNums > 0, "Need to have one of them in order to have a grant token!");
        
        if(coolcatNums > 0) {
            for(uint i =0 ; i<coolcatNums; i++){
                uint256 coolcatTokenId = CoolCats(coolcat).tokenOfOwnerByIndex(msg.sender, i);
                require(coolcatMints[coolcatTokenId]==false, "This coolcatTokenId has minted before");
                coolcatMints[coolcatTokenId] = true;
            }
        }

        if(fameladyNums > 0) {
            for(uint i =0 ; i<fameladyNums; i++){
                uint256 fameladyTokenId = FameLadySquad(famelady).tokenOfOwnerByIndex(msg.sender, i);
                require(fameladyMints[fameladyTokenId]==false, "This fameladyTokenId has minted before");
                fameladyMints[fameladyTokenId] = true;
            }
        }

        if(wowNums > 0) {
            for(uint i =0 ; i<wowNums; i++){
                uint256 wowTokenId = WorldOfWomen(wow).tokenOfOwnerByIndex(msg.sender, i);
                require(wowMints[wowTokenId]==false, "This wowTokenId has minted before");
                wowMints[wowTokenId] = true;
            }
        }
        grantMints[msg.sender] = true;
        _safeMint(msg.sender, supply);
        _max_grant_miss -= 1;
    }


    /**
    * @dev Give away reserved Miss for events and gaming (Callable by owner only)
    */
    function giveAway(address _to, uint256 _amount) external onlyOwner() {
        require(  _amount <= _max_reserved_miss , "Exceeds reserved Miss supply" );

        uint256 supply = totalSupply();
        for(uint256 i; i < _amount; i++){
            _safeMint( _to, supply + i );
        }

        _max_reserved_miss -= _amount;
    }

    
    /**
    * @dev Discount Mint (Callable by owner only)
    */
    function discountMint(address _to, uint256 _amount) external onlyOwner() {
        require(  _amount <= _max_discount_miss , "Exceeds Discount Miss supply" );

        uint256 supply = totalSupply();
        for(uint256 i; i < _amount; i++){
            _safeMint( _to, supply + i );
        }

        _max_discount_miss -= _amount;
    }
    
    function _baseURI() internal view virtual override returns (string memory) {
        return _baseTokenURI;
    }

    /**
    * @dev Change the base URI when we move IPFS (Callable by owner only)
    */
    function setBaseURI(string memory baseURI) public onlyOwner {
        _baseTokenURI = baseURI;
    }

    /**
    * @dev Get all tokens of a owner provided
    */
    function getTokensOfOwner(address _owner) public view returns(uint256[] memory) {
        uint256 tokenCount = balanceOf(_owner);

        uint256[] memory tokensId = new uint256[](tokenCount);
        for(uint256 i; i < tokenCount; i++){
            tokensId[i] = tokenOfOwnerByIndex(_owner, i);
        }
        return tokensId;
    }

    /**
    * @dev Set Price if need to discount (Callable by owner only)
    */
    function setPrice(uint256 _newPrice) public onlyOwner {
        _price = _newPrice;
    }

    function getPrice() public view returns (uint256){
        return _price;
    }

    function pause(bool val) public onlyOwner {
        _paused = val;
    }

    function grantPause(bool val) public onlyOwner {
        _grant_paused = val;
    }

    /**
    * @dev Withdraw ether from this contract (Callable by owner only)
    */
    function withdraw() onlyOwner public {
        uint256 _balance = address(this).balance;
        require(payable(luke_crypto).send(_balance));
    }
}
/**
 Miss Crypto Club Contract
 */
function getTokensOfOwner(address _owner) public view returns(uint256[] memory) {
    uint256 tokenCount = balanceOf(_owner);

    uint256[] memory tokensId = new uint256[](tokenCount);
    for(uint256 i; i < tokenCount; i++){
        tokensId[i] = tokenOfOwnerByIndex(_owner, i);
    }
    return tokensId;
}

/**
 * @dev Get all tokens of a owner provided
 */