contract SecretSocietyofOddFellows is ERC721Enumerable, Ownable {
  using Strings for uint256;
  using SafeMath for uint256;
  string private baseURI;
  address public collaborator;
  bool public isSaleStarted=false;
  string public notRevealedUri;
  uint256 public preSaleCost = 0.07 ether;
  uint256 public generalSaleCost = 0.08 ether;
  uint256 public maxSupply = 10011;
  uint256 public nftPerAddressLimitOnPreSale = 25;
  uint256 public nftPerAddressLimitOnGeneralSale = 25;
  bool public paused = false;
  bool public revealed = false;
  uint256 public generalSaleTime;
  mapping(address => uint256) public addressMintedPresaleBalance;
  mapping(address => uint256) public addressMintedGeneralsaleeBalance;


  constructor(
    string memory _name,
    string memory _symbol,
    string memory _initBaseURI,
    string memory _initNotRevealedUri,
    address _collaborator
  ) ERC721(_name, _symbol) payable{
    setBaseURI(_initBaseURI);
    setNotRevealedURI(_initNotRevealedUri);
    collaborator = _collaborator;
  }

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


    function mintAsOwner(uint _mintAmount) public {
        if(collaborator != msg.sender && owner() != msg.sender) revert("Caller is not the owner or collaborator");
        uint256 supply = totalSupply();
        require(supply + _mintAmount <= maxSupply, "Exceeds maximum Originators supply");
        
        for (uint256 i = 1; i <= _mintAmount; i++) {
            _safeMint(msg.sender, supply + i);
        }
    }

    function mint(uint _mintAmount) public payable {
        require(isSaleStarted,"sale is not started");
        require(!paused, "the contract is paused");
        uint256 supply = totalSupply();
        require(supply + _mintAmount <= maxSupply, "Exceeds maximum Originators supply");       
        
        if(block.timestamp < generalSaleTime) {
            require(msg.value >= preSaleCost * _mintAmount, "Insufficient funds");
            uint256 ownerMintedCount = addressMintedPresaleBalance[msg.sender];
            require(ownerMintedCount + _mintAmount <= nftPerAddressLimitOnPreSale, "Max NFTs per address exceeded for pre-sale.");
            for (uint256 i = 1; i <= _mintAmount; i++) {
                addressMintedPresaleBalance[msg.sender]++;
                _safeMint(msg.sender, supply + i);
            }
        }
        else{
            
            uint256 ownerMintedCount = addressMintedGeneralsaleeBalance[msg.sender];
            require(msg.value >= generalSaleCost * _mintAmount, "Insufficient funds");
            require(ownerMintedCount + _mintAmount <= nftPerAddressLimitOnGeneralSale, "Max NFTs per address exceeded for general-sale.");
            for (uint256 i = 1; i <= _mintAmount; i++) {
                addressMintedGeneralsaleeBalance[msg.sender]++;
                _safeMint(msg.sender, supply + 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;
  }

  function getMintFees(uint _mintAmount) public view returns(uint256) {
        if(block.timestamp < generalSaleTime) {
            return preSaleCost * _mintAmount;
        }else{
            return generalSaleCost * _mintAmount;
        }
  }
    
  function tokenURI(uint256 tokenId) public view virtual override returns (string memory){
      
    require(_exists(tokenId),"ERC721Metadata: URI query for nonexistent token");
    
    if(revealed == false) {
        return string(abi.encodePacked(notRevealedUri, tokenId.toString()));
    }

    string memory currentBaseURI = _baseURI();
    return string(abi.encodePacked(currentBaseURI, tokenId.toString()));
  }

  function  getMintedCount(address user) public view returns(uint256) {
      return addressMintedGeneralsaleeBalance[user].add(addressMintedPresaleBalance[user]);
  }

  function reveal(bool _reveal) public onlyOwner {
      revealed = _reveal;
  }
  
  function startPresale() public onlyOwner {
      isSaleStarted = true;
      generalSaleTime = block.timestamp + 72 hours;
  }
  
  function stopPresale() public onlyOwner {
      isSaleStarted=false;
      generalSaleTime = 0;
  }

  function startGeneralsale() public onlyOwner {
      isSaleStarted = true;
  }


  function stopGeneralsale() public onlyOwner {
      isSaleStarted = false;
  }
  
  function setGeneralSaleNftcount(uint256 _limit) public onlyOwner {
     nftPerAddressLimitOnGeneralSale = _limit;
  }
  
  function setPreSaleNftcount(uint256 _limit) public onlyOwner {
    nftPerAddressLimitOnPreSale = _limit;
  }
  
  function setGeneralSaleCost(uint256 _newCost) public onlyOwner {
    generalSaleCost = _newCost;
  }

  function setPresaleCost(uint256 _newCost) public onlyOwner {
    preSaleCost = _newCost;
  }


  function setCollaborator(address _collaborator) public onlyOwner {
    collaborator = _collaborator;
  }



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

  function setNotRevealedURI(string memory _notRevealedURI) public onlyOwner {
    notRevealedUri = _notRevealedURI;
  }
  
  function pause(bool _state) public onlyOwner {
    paused = _state;
  }
  
  function withdraw() public onlyOwner {
        uint balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }
}
function _baseURI() internal view virtual override returns (string memory) {
    return baseURI;
  }
// internal