contract TheSadCats is ERC721, ERC721Enumerable, Ownable, ReentrancyGuard {
  using SafeMath for uint8;
  using SafeMath for uint256;
  using Strings for string;

  uint public contractState = 0;
  // 0 - closed
  // 1 - opened only for holders
  // 2 - opened for all

  string public baseTokenURI;
  address public sgbAddress;
  uint256 public _maxBatchSize = 20;
  uint public catPrice = 0.05 ether;
  uint public giveawayLeft = 100;
  uint public targetSupply = 7000;
  uint public lastTokenId = 0;

  /*
   * Set up the basics
   *
   * @dev It will NOT be ready to start sale immediately upon deploy
   */
  constructor(string memory metadataLocation, address sgbContractAddress) ERC721("TheSadCats", "TheSadCats") {
    sgbAddress = sgbContractAddress;
    baseTokenURI = metadataLocation;
  }

  function _setAddr(address addr) public onlyOwner {
    sgbAddress = addr;
  }
  /*
   * Get the tokens owned by _owner
   */
  function tokensOfOwner(address _owner) public view returns(uint256[] memory ) {
    uint256 tokenCount = balanceOf(_owner);
    if (tokenCount == 0) {
      // Return an empty array
      return new uint256[](0);
    } else {
      uint256[] memory result = new uint256[](tokenCount);
      uint256 index;
      for (index = 0; index < tokenCount; index++) {
        result[index] = tokenOfOwnerByIndex(_owner, index);
      }
      return result;
    }
  }


  function isSgbHolder(address _owner) public view returns (bool) {
    SadGirlsBarClient sgb = SadGirlsBarClient(sgbAddress);
    return sgb.balanceOf(_owner) > 0;
  }


  /* Minting started here... */
  function mint(uint256 numTokens, address to) external payable nonReentrant {
    // Check we're is online...
    require(contractState > 0, "Sales not started");

    if (contractState == 1) {
      require(isSgbHolder(_msgSender()), "Sales are open to holders only");
    }

    // ...and not exceed limit
    require(totalSupply() < targetSupply, "We've got all");
    // ...and not try to get more tokens than allowed...
    require(numTokens > 0 && numTokens <= _maxBatchSize, "Too much");
    // ...and not try to get more tokens than TOTALLY allowed...
    require(totalSupply().add(numTokens) <= targetSupply.sub(giveawayLeft), "Can't get more than 10000 NFTs");
    // ...and we have enough money for that.
    require(msg.value >= catPrice.mul(numTokens),
      "Not enough ETH for transaction");

    // mint all of these tokens
    uint _lastTokenId = lastTokenId;
    address mintTo = address(0) == to ? _msgSender() : to;

    for (uint i = 0; i < numTokens; i++) {
      _lastTokenId++;
      _safeMint(mintTo, _lastTokenId);
    }
    lastTokenId = _lastTokenId;
  }


  /* Allow to mint tokens for giveaways at any time (but limited count) */
  function mintGiveaway(uint256 numTokens, address to) public onlyOwner {
    require(totalSupply().add(numTokens) <= targetSupply.sub(giveawayLeft), "Only 100 tokens max");
    uint _lastTokenId = lastTokenId;
    for (uint i = 0; i < numTokens; i++) {
      _lastTokenId++;
      _safeMint(to, _lastTokenId);
    }
    giveawayLeft = giveawayLeft.sub(numTokens);
    lastTokenId = _lastTokenId; 
  }


  function _beforeTokenTransfer(address from, address to, uint256 tokenId)
  internal
  override(ERC721, ERC721Enumerable)
  {
    super._beforeTokenTransfer(from, to, tokenId);
  }

  function supportsInterface(bytes4 interfaceId)
  public
  view
  override(ERC721, ERC721Enumerable)
  returns (bool)
  {
    return super.supportsInterface(interfaceId);
  }

  function _setBaseURI(string memory baseURI) internal virtual {
    baseTokenURI = baseURI;
  }

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

  function tokenURI(uint256 tokenId) public view override(ERC721) returns (string memory) {
    string memory _tokenURI = super.tokenURI(tokenId);
    return bytes(_tokenURI).length > 0 ? string(abi.encodePacked(_tokenURI, ".json")) : "";
  }

  // Administrative zone
  function setBaseURI(string memory baseURI) public onlyOwner {
    _setBaseURI(baseURI);
  }

  function setMaxBatchSize(uint256 maxBatchSize) public onlyOwner {
    _maxBatchSize = maxBatchSize;
  }

  function setPrice(uint256 newPrice) public onlyOwner {
    require(newPrice > 0);
    catPrice = newPrice;
  }

  function setContractState(uint256 newContractState) public onlyOwner {
    require(newContractState >= 0 && newContractState <=2);
    contractState = newContractState;
  }
  
  function reduceSupply(uint256 newSupply) public onlyOwner {
    require(newSupply < targetSupply);
    require(newSupply > lastTokenId.add(giveawayLeft));
    targetSupply = newSupply;
  }

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


}
function tokensOfOwner(address _owner) public view returns(uint256[] memory ) {
    uint256 tokenCount = balanceOf(_owner);
    if (tokenCount == 0) {
      // Return an empty array
      return new uint256[](0);
    } else {
      uint256[] memory result = new uint256[](tokenCount);
      uint256 index;
      for (index = 0; index < tokenCount; index++) {
        result[index] = tokenOfOwnerByIndex(_owner, index);
      }
      return result;
    }
  }
/*
 * Get the tokens owned by _owner
 */