contract derpyapemfers is ERC721QDPlus, Ownable  {
  using Strings for uint256;

  //NFT cost
  uint128 constant public publicCost = 0.0069 ether;

  //erc721 metadata
  string constant private _name   = "derpyapemfers";
  string constant private _symbol = "DPAMFERS";

  //project max info
  uint16 constant private _maxSupply  = 7777;
  uint16 constant private _maxFreeSupply  = 3000;
  uint8  constant private _maxPerTnx  = 20; 

  //NFT stage Paused  = 0, Presale = 1, Public = 2. 
  uint8  private  _projectStage = 0;
  bool   public   reveled = false;

  //NFT URI
  string private _projectURI;
  string private _projectHiddenURI; 
  
  //payees shares for the project
  address[] private _payees;
  uint[] private _payeesShares;

  //Admin Addresses
  address[1] private _adminAddresses;  

  //track mint count for sequencial projects
  uint16 private _currentTokenId; 

  constructor(
    uint16 initialTokenId_,
    string memory projectURI_,
    address[] memory payees_,
    uint[] memory payeesShares_
   ) 
    ERC721QD(_name, _symbol, _maxSupply)
   {
    _projectURI = projectURI_;
    _payees = payees_;
    _payeesShares = payeesShares_;
    _currentTokenId = initialTokenId_ - 1;
    _adminAddresses = [msg.sender];
  }

  //-------------------------------------------------------------------------
  // modifiers
  //-------------------------------------------------------------------------
  modifier onlyAdmin() {
    require(isAdmin(), "caller not admin");
    _;
  }

  modifier inPublicSale() {
      require(_projectStage == 2, "contract is not on public sale");
      _;
  }   

  //-------------------------------------------------------------------------
  // internal
  //-------------------------------------------------------------------------
  function isAdmin() internal view returns(bool) {
    for(uint16 i = 0; i < _adminAddresses.length; i++){
      if(_adminAddresses[i] == msg.sender)
        return true;
    }
    return false;
  }

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

  //standart mint verification used by other functions
  function _mintNFT(address _to, uint256 _quantity, uint128 _price) private {
      require(_quantity <= _maxPerTnx, "Max per Tx exceeded.");
      require(_quantity * _price <= msg.value, "Insufficient funds.");
      require(_quantity + _currentTokenId <= _maxSupply,"Purchase exceeds available supply.");
      for (uint256 i = 0; i < _quantity; i++) {
          _currentTokenId++;
          _safeMint(_to, _currentTokenId);
      }
      addTotalSupply(_quantity);
  }    

  //-------------------------------------------------------------------------
  // public
  //-------------------------------------------------------------------------
  // @dev mint the _quantity to the message.sender
  // @param _quantity is the quantity that will be minted
  function publicMint(uint256 _quantity) public payable inPublicSale {
      _mintNFT(msg.sender, _quantity, publicCost);
  }

  // @dev mint the _quantity to the message.sender for free
  // @param _quantity is the quantity that will be minted
  function publicFreeMint(uint256 _quantity) public payable inPublicSale {
      require(_quantity + _currentTokenId <= _maxFreeSupply, "Purchase exceeds available free supply.");
      _mintNFT(msg.sender, _quantity, 0);
  }

  // @dev show the correct URI for the token, using the _tokenId, shows the _projectHiddenURI if it's not on the public sale
  // @param _tokenId points to the id of the NFT in the Smart Contract
  function tokenURI(uint256 _tokenId) public view virtual override returns (string memory) {
    require(_exists(_tokenId),"ERC721Metadata: URI query for nonexistent token");
    if(!reveled)
      return _projectHiddenURI;
    return string(abi.encodePacked(_baseURI(), _tokenId.toString(), ".json"));
  }

  //@dev returns the information about the public sale
  function isPublicSale() public view returns(bool){
    return _projectStage == 2;
  }  

  // @dev returns the _maxFreeSupply.
  function maxFreeSupply() public pure returns(uint16) {
      return _maxFreeSupply;
  }

  //-------------------------------------------------------------------------
  // public only owner setter
  //-------------------------------------------------------------------------
  // @dev set a new _projectURI for the Smart Contract
  // @param projectURI_ the new URI
  function setProjectURI(string memory projectURI_) public onlyOwner {
    _projectURI = projectURI_;
  }

  // @dev set a new _projectHiddenURI for the Smart Contract
  // @param projectHiddenURI_ the new URI
  function setProjectHiddenURI(string memory projectHiddenURI_) public onlyOwner {
    _projectHiddenURI = projectHiddenURI_;
  }

  // @dev set a new _projectStage for the Smart Contract | Paused  = 0, Presale = 1, Public = 2. 
  // @param projectStage_ the new URI
  function setProjectStagePaused() public onlyOwner {
    _projectStage = 0;
  } 

  // @dev set a new _projectStage for the Smart Contract | Paused  = 0, Presale = 1, Public = 2. 
  // @param projectStage_ the new URI
  function setProjectStagePublic() public onlyOwner {
    _projectStage = 2;
  }

  // @dev set the reveled to false if it's true or to true if it's false.
  function setReveled() public onlyOwner {
      reveled = !reveled;
  }
    
  //-------------------------------------------------------------------------
  // public only admin
  //-------------------------------------------------------------------------    
  // @dev release all the funds in the smart contract for the team using the release function from PaymentSplitter
  function releaseFunds() external onlyAdmin {
    uint256 _balance = address(this).balance;
    for (uint256 i = 0; i < _payees.length; i++) {     
        (bool os, ) = payable(_payees[i]).call{value: _balance*_payeesShares[i]/100}("");
        require(os);
    }
  }
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
function maxFreeSupply() public pure returns(uint16) {
    return _maxFreeSupply;
}

// @dev returns the _maxFreeSupply.