contract BullsIncNFT is ERC721Enumerable, Ownable, ReentrancyGuard {
  using Strings for uint256;

  modifier onlyTeam() {
        bool team = false;
        for (uint x = 0; x<teamAddresses.length;x++){
            if (teamAddresses[x]==msg.sender){
                team = true;
            }
        }
        require(team, "Only Team");
    _;
    }

  address [] public teamAddresses;

  string baseURI;
  string basePromoURI;
  string public baseExtension = ".json";
  uint256 public cost = 0.1 ether;
  uint256 public baseSupply = 0;
  uint256 public maxBaseSupply = 4344;
  uint256 public promoSupply = 0;
  uint256 public maxPromoSupply = 100;
  uint256 public maxMintAmount = 10;

  bool public whitelistPhase = true;
  mapping (address=>uint256) public addressToMintedAmount;

  bool public paused = false;
  bool public revealed = false;
  string public notRevealedUri;

  constructor(
    string memory _initBaseURI,
    string memory _initNotRevealedUri,
    string memory _initBasePromoURI
  ) ERC721("Bulls.Inc", "BINC") {
    setBaseURI(_initBaseURI);
    setNotRevealedURI(_initNotRevealedUri);
    setBasePromoURI(_initBasePromoURI);
    teamAddresses.push(msg.sender);
  }

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

  function _basePromoURI() internal view virtual returns (string memory) {
    return basePromoURI;
  }

  // public
  function mint(uint256 _mintAmount) public payable nonReentrant {
   
    require(!paused , "Minting paused.");
    require(_mintAmount > 0, "Can't mint 0");
    require(_mintAmount <= maxMintAmount, "Exceeds mint limit." );
    require(baseSupply + _mintAmount <= maxBaseSupply , "Cannot exceed max supply.");

    if (msg.sender != owner()) {
      require(msg.value == cost * _mintAmount, "Incorrect value sent");
      if (whitelistPhase){
        require(addressToMintedAmount[msg.sender]+_mintAmount<=maxMintAmount, "Exceeds address limit.");
      }
    }

    addressToMintedAmount[msg.sender]+=_mintAmount;
    
    uint256 initialBaseSupply = baseSupply;
    baseSupply+=_mintAmount;

    for (uint256 i = 1; i <= _mintAmount; i++) {
      _safeMint(msg.sender, initialBaseSupply + i);
    }

  }

  function walletOfOwner(address _owner)
    public
    view
    returns (uint256[] memory)
  {
    uint256 ownerTokenCount = balanceOf(_owner);
    uint256[] memory tokenIds = new uint256[](ownerTokenCount);
    for (uint256 i; i < ownerTokenCount; i++) {
      tokenIds[i] = tokenOfOwnerByIndex(_owner, i);
    }
    return tokenIds;
  }

  // alias for compatibility
  function tokensOfOwner(address _owner)
    public
    view
    returns (uint256[] memory)
  {
    return walletOfOwner(_owner);
  }

  function tokenURI(uint256 tokenId)
    public
    view
    virtual
    override
    returns (string memory)
  {
    require(
      _exists(tokenId),
      "ERC721Metadata: URI query for nonexistent token"
    );
    
    if(revealed == false) {
        return notRevealedUri;
    }

    if (tokenId<=maxBaseSupply){
        string memory currentBaseURI = _baseURI();
        return bytes(currentBaseURI).length > 0
            ? string(abi.encodePacked(currentBaseURI, tokenId.toString(), baseExtension))
            : "";
    }else{
         string memory currentBaseURI = _basePromoURI();
         return bytes(currentBaseURI).length > 0
            ? string(abi.encodePacked(currentBaseURI, tokenId.toString(), baseExtension))
            : "";
    }

  }

  //only team
  function mint_promo(uint256 _mintAmount, address _recipient) public onlyTeam nonReentrant {
    require(_mintAmount > 0, "Can't mint 0");
    require(promoSupply + _mintAmount <= maxPromoSupply, "Promos exhausted.");

    uint256 initialSupply = promoSupply;
    promoSupply+=_mintAmount;
    
    for (uint256 i = 1; i <= _mintAmount; i++) {
      _safeMint(_recipient, maxBaseSupply + initialSupply +i);
    }

  }

  // only owner
  function reveal() public onlyOwner {
      revealed = true;
  }
  
  function setCost(uint256 _newCost) public onlyOwner {
    cost = _newCost;
  }

  function setMaxMintAmount(uint256 _newmaxMintAmount) public onlyOwner {
    maxMintAmount = _newmaxMintAmount;
  }
  
  function setNotRevealedURI(string memory _notRevealedURI) public onlyOwner {
    notRevealedUri = _notRevealedURI;
  }

  function setBaseURI(string memory _newBaseURI) public onlyOwner {
    baseURI = _newBaseURI;
  }

  function setBasePromoURI(string memory _newBasePromoURI) public onlyOwner {
    basePromoURI = _newBasePromoURI;
  }

  function setBaseExtension(string memory _newBaseExtension) public onlyOwner {
    baseExtension = _newBaseExtension;
  }

  function setTeamAddresses(address[] memory _team) public onlyOwner {
    teamAddresses = _team;
  }

  function setWhitelistPhase(bool _state) public onlyOwner {
    whitelistPhase = _state;
  }

  function pause(bool _state) public onlyOwner {
    paused = _state;
  }
 
  function withdraw() public onlyOwner {
    (bool os, ) = payable(owner()).call{value: address(this).balance}("");
    require(os);
  }
}
function mint(uint256 _mintAmount) public payable nonReentrant {
   
  require(!paused , "Minting paused.");
  require(_mintAmount > 0, "Can't mint 0");
  require(_mintAmount <= maxMintAmount, "Exceeds mint limit." );
  require(baseSupply + _mintAmount <= maxBaseSupply , "Cannot exceed max supply.");

  if (msg.sender != owner()) {
    require(msg.value == cost * _mintAmount, "Incorrect value sent");
    if (whitelistPhase){
      require(addressToMintedAmount[msg.sender]+_mintAmount<=maxMintAmount, "Exceeds address limit.");
    }
  }

  addressToMintedAmount[msg.sender]+=_mintAmount;
    
  uint256 initialBaseSupply = baseSupply;
  baseSupply+=_mintAmount;

  for (uint256 i = 1; i <= _mintAmount; i++) {
    _safeMint(msg.sender, initialBaseSupply + i);
  }

}

// public