contract GangstaRatsMafia is ERC721, Ownable {

  using Strings for uint256;

  string public baseURI;
  string public secondBaseURI;
  string public notRevealedUri;

  uint128 public wlEarlySaleCost = 0.04 ether;
  uint128 public earlySaleCost = 0.055 ether;
  uint128 public preSaleCost = 0.055 ether;
  uint128 public saleCost = 0.068 ether;
  uint16 public earlySaleMaxSupply = 300;
  uint16 public maxSupply = 10000;
  uint16 public currentSupply = 0;
  uint8 public maxMintAmount = 10;
  
  bool public paused = true;
  bool public bossPaused = true;
  bool public revealed = false;
  bool public bossRevealed = false;
  bool public preSaleActive = true;
  
  address[] public whitelistedAddresses;
  address[] public freeMintAddresses;
  mapping(address => uint16) public whitelistedAlreadyMintedBalance;
  mapping(address => uint16) public freeMintAlreadyMintedBalance;


  constructor(
    // string memory _initBaseURI
  ) ERC721("GangstaRatsMafia", "GRM") {
    setBaseURI("ipfs://QmQFaTnTE6VVNwoFqpEkTvvonWijKrh7NRXsZn8R2QHHBj/");
    setNotRevealedURI("ipfs://QmQFaTnTE6VVNwoFqpEkTvvonWijKrh7NRXsZn8R2QHHBj/hidden.json");
  }

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

  // public
  function bossMint(uint8 _mintAmount) public payable {
    require(!bossPaused);
    require(_mintAmount > 0);
    require(_mintAmount <= maxMintAmount);
    require(currentSupply + _mintAmount < earlySaleMaxSupply);

    if (msg.sender != owner()) {
        if(isWhitelisted(msg.sender)){
            require(msg.value >= wlEarlySaleCost * _mintAmount);
            require(whitelistedAlreadyMintedBalance[msg.sender] < 1);
            whitelistedAlreadyMintedBalance[msg.sender]++;
        }else{
            require(msg.value >= earlySaleCost * _mintAmount);
        }
    }

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

  function mint(uint8 _mintAmount) public payable{
    require(!paused);
    require(_mintAmount > 0);
    require(_mintAmount <= maxMintAmount);
    require(currentSupply + _mintAmount < maxSupply);

    if (msg.sender != owner()) {
        if(preSaleActive){
            if(isWhitelisted(msg.sender)){
                require(msg.value >= preSaleCost * _mintAmount);
                require(whitelistedAlreadyMintedBalance[msg.sender] < 2);
                whitelistedAlreadyMintedBalance[msg.sender]++;
            }else{
                return;
            }
        }else{
            if(isFreeMint(msg.sender)){
                require(freeMintAlreadyMintedBalance[msg.sender] == 0);
                freeMintAlreadyMintedBalance[msg.sender]++;
            }else{
                require(msg.value >= saleCost * _mintAmount);
            }
        }
    }

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

  function isWhitelisted(address _user) public view returns (bool){
      for(uint32 i = 0; i < whitelistedAddresses.length; i++){
        if(whitelistedAddresses[i] == _user){
            return true;
        }
      } 
      return false;
  }

  function isFreeMint(address _user) public view returns (bool){
      for(uint32 i = 0; i < freeMintAddresses.length; i++){
        if(freeMintAddresses[i] == _user){
            return true;
        }
      } 
      return false;
  }


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

    string memory currentBaseURI;
    if(tokenId <= earlySaleMaxSupply){
      if(!bossRevealed) {
        return notRevealedUri;
      }
      currentBaseURI = baseURI;
    }else{
      if(!revealed) {
        return notRevealedUri;
      }
      currentBaseURI = secondBaseURI;
    }
    return bytes(currentBaseURI).length > 0
        ? string(abi.encodePacked(currentBaseURI, tokenId.toString(), ".json"))
        : "";
  }

  function checkWalletOfOwner(address _owner) public view returns (uint256){
    uint256 ownerTokenCount = balanceOf(_owner);
    return ownerTokenCount;
  }

  //only owner
  function reveal() public onlyOwner {
      revealed = true;
  }

  function bossReveal() public onlyOwner {
      bossRevealed = true;
  }

  function setFreeMintUsers(address[] calldata _users) public onlyOwner{
    delete freeMintAddresses;
    freeMintAddresses = _users;
  }

  function setWhitelistedUsers(address[] calldata _users) public onlyOwner{
    delete whitelistedAddresses;
    whitelistedAddresses = _users;
  }
  
  function setWlEarlySaleCost(uint128 _newCost) public onlyOwner {
    wlEarlySaleCost = _newCost;
  }

  function setEarlySaleCost(uint128 _newCost) public onlyOwner {
    earlySaleCost = _newCost;
  }

  function setPreSaleCost(uint128 _newCost) public onlyOwner {
    preSaleCost = _newCost;
  }

  function setSaleCost(uint128 _newCost) public onlyOwner {
    saleCost = _newCost;
  }

  function setMaxMintAmount(uint8 _newmaxMintAmount) public onlyOwner {
    maxMintAmount = _newmaxMintAmount;
  }

  function setErlySaleMaxSupply(uint16 _earlySaleMaxSupply) public onlyOwner {
    earlySaleMaxSupply = _earlySaleMaxSupply;
  }
  
  function setNotRevealedURI(string memory _notRevealedURI) public onlyOwner {
    notRevealedUri = _notRevealedURI;
  }

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

  function setSecondBaseURI(string memory _secondBaseURI) public onlyOwner {
    secondBaseURI = _secondBaseURI;
  }

  function pause(bool _state) public onlyOwner {
    paused = _state;
  }

  function setBossPaused(bool _state) public onlyOwner {
    bossPaused = _state;
  }

  function setPreSaleActive(bool _state) public onlyOwner {
    preSaleActive = _state;
  }

  function withdraw() public payable onlyOwner {
    (bool success, ) = payable(msg.sender).call{value: address(this).balance}("");
    require(success);
  }
}
function reveal() public onlyOwner {
    revealed = true;
}

//only owner