contract syac is ERC721, Ownable, Pausable {
    using Strings for uint256;
    uint256 public constant MAX_SUPPLY = 5555;
    uint256 public constant MINT_PRICE = 0.05 ether;
    uint256 public constant MAX_MINT_PER_TX = 5;
    uint256 constant public LEGENDARY_SUPPLY = 200;
    uint256 public totalLegendaryMinted;
    mapping(address=>uint) public totalWhiteListMinted;
    mapping(address=>uint256) public totalPublicMinted;
    mapping(address => bool) public isWhitelisted;
    mapping(address=>bool) legandaryAddress;

    mapping(bytes32 => bool) private _usedHashes;
    bool public saleStarted = false;
    bool public preSaleStarted = false;
    bool public lock = false;
    uint256 public totalSupply = 0;

    string public prefixURI;
    string public commonURI;
    
    mapping(uint256 => string) public tokenURIs;

    constructor() ERC721("Spoiled Young Ape Club", "SYAC") { 

      mintOwner();
    }

        //modifier
     modifier onlyLegandry() {
       require(legandaryAddress[msg.sender],"Only Legendary Address can call!");
        _;
    }


    //function to convert uint into string
    function uint2str(
     uint256 _i
    )
     internal
     pure
     returns (string memory str)
    {
     if (_i == 0)
     {
    return "0";
      }
    uint256 j = _i;
    uint256 length;
    while (j != 0)
    {
    length++;
    j /= 10;
    }
    bytes memory bstr = new bytes(length);
    uint256 k = length;
    j = _i;
    while (j != 0)
    {
    bstr[--k] = bytes1(uint8(48 + j % 10));
    j /= 10;
    }
    str = string(bstr);
    }
    
    //Encoded Token id
    function getTokenId(uint256 _value)public pure returns(string memory){
     string memory tokenId;
     if(_value<10){
        tokenId= string(abi.encodePacked("000", _value.toString()));
     }else if(_value<100){
       tokenId=  string(abi.encodePacked("00", _value.toString()));
     }else if(_value<1000){
       tokenId=  string(abi.encodePacked("0", _value.toString()));
     }else{
     tokenId=uint2str(_value);
    }
    return (tokenId);
    }

    function mintOwner() public onlyOwner {
        require(totalLegendaryMinted<LEGENDARY_SUPPLY,"Legendary Total Minted Reached");
        for(uint256 i=1; i <= LEGENDARY_SUPPLY ; i++){
        _safeMint(msg.sender, (totalSupply+i));
        
        }
        totalSupply +=  LEGENDARY_SUPPLY;
        totalLegendaryMinted +=  LEGENDARY_SUPPLY;
    }

    function mintLegendary(uint256 _count, address _account) external onlyLegandry {
        require(totalSupply + _count <= MAX_SUPPLY, "MAX_SUPPLY_REACHED");
        require(totalLegendaryMinted >= LEGENDARY_SUPPLY, "LEGENDARY_NOT_MINTED");
        for (uint256 i = 1; i <= _count; i++) {
            _safeMint(_account,(totalSupply + i));
        }
        totalSupply += _count;
    }

    function mintWhitelist(
        uint256 _count)
        external
        payable
    {
        require(preSaleStarted, "MINT_NOT_STARTED");
        require(totalSupply + _count <= MAX_SUPPLY, "MAX_SUPPLY_REACHED");
        require(totalLegendaryMinted >= LEGENDARY_SUPPLY, "LEGENDARY_NOT_MINTED");
        require(msg.value >= (_count * MINT_PRICE), "INVALID_ETH_SENT");
        require(_count<=3,"You can't mint more than 3");
        require(isWhitelisted[msg.sender],"Address isn't Whitelisted");
        require(totalWhiteListMinted[msg.sender]+_count<=3,"you can't mint more than 3");
        for (uint256 i = 1; i <= _count; i++) {
            _safeMint(msg.sender,(totalSupply + i));
        }
        totalSupply += _count;
        totalWhiteListMinted[msg.sender]+=_count;
        
    }



    // Public Mint
    function mint(uint256 _count) public payable {
        require(saleStarted, "MINT_NOT_STARTED");
        require(_count > 0 && _count <= MAX_MINT_PER_TX, "COUNT_INVALID");
        require(totalLegendaryMinted >= LEGENDARY_SUPPLY, "LEGENDARY_NOT_MINTED");
        require(totalSupply + _count <= MAX_SUPPLY, "MAX_SUPPLY_REACHED");
        require(msg.value >=(_count * MINT_PRICE), "INVALID_ETH_SENT");
        require(totalPublicMinted[msg.sender]+_count<=8,"Maximum NFT Limit reached");

        for (uint256 i = 1; i <= _count; i++) {
            _safeMint(msg.sender, (totalSupply + i));
        }
        totalSupply += _count;
        totalPublicMinted[msg.sender]+=_count;
    }
   function addWhitelist(address[] memory _addresses) external onlyOwner {
        for(uint i = 0; i < _addresses.length; i++) {
        isWhitelisted[_addresses[i]] = true;
        }
    }

    function removeWhitelist(address[] memory _addresses) external onlyOwner {
            for(uint i = 0; i < _addresses.length; i++) {
            isWhitelisted[_addresses[i]] = false;
            }
    }
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
        if (bytes(tokenURIs[tokenId]).length != 0) {
            return tokenURIs[tokenId];
        }
        if (bytes(commonURI).length != 0) {
            return commonURI;
        }
        return string(abi.encodePacked(prefixURI, getTokenId(tokenId)));
    }

    // ** Admin Fuctions ** //
    function withdraw(address payable _to) external onlyOwner {
        require(_to != address(0), "WITHDRAW_ADDRESS_ZERO");
        require(address(this).balance > 0, "EMPTY_BALANCE");
        _to.transfer(address(this).balance);
    }

    function setSaleStarted(bool _hasStarted) external onlyOwner {
        require(saleStarted != _hasStarted, "SALE_STARTED_ALREADY_SET");
        saleStarted = _hasStarted;
    }

        function setLegendary(address[] memory _legandaryAddress) external onlyOwner {

            for(uint8 i=0;i<_legandaryAddress.length;i++){
              legandaryAddress[_legandaryAddress[i]]=true;
    }
        }

    function setPreSaleStarted(bool _hasStarted) external onlyOwner {
        require(preSaleStarted != _hasStarted, "SALE_STARTED_ALREADY_SET");
        preSaleStarted = _hasStarted;
    }
    function lockBaseURI() external onlyOwner {
        require(!lock, "ALREADY_LOCKED");
        lock = true;
    }

    function setPrefixURI(string calldata _uri) external onlyOwner {
        require(!lock, "ALREADY_LOCKED");
        prefixURI = _uri;
        commonURI = '';
    }

    function setCommonURI(string calldata _uri) external onlyOwner {
        require(!lock, "ALREADY_LOCKED");
        commonURI = _uri;
        prefixURI = '';
    }
    function setTokenURI(string calldata _uri, uint256 _tokenId) external onlyOwner {
        require(!lock, "ALREADY_LOCKED");
        tokenURIs[_tokenId] = _uri;
    }

    function pause() external onlyOwner {
        require(!paused(), "ALREADY_PAUSED");
        _pause();
    }

    function unpause() external onlyOwner {
        require(paused(), "ALREADY_UNPAUSED");
        _unpause();
    }

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) override internal virtual {
        require(!paused(), "TRANSFER_PAUSED");
        super._beforeTokenTransfer(from, to, tokenId);
    }
}
// pragma solidity ^0.8.7;
function getTokenId(uint256 _value)public pure returns(string memory){
     string memory tokenId;
     if(_value<10){
        tokenId= string(abi.encodePacked("000", _value.toString()));
     }else if(_value<100){
       tokenId=  string(abi.encodePacked("00", _value.toString()));
     }else if(_value<1000){
       tokenId=  string(abi.encodePacked("0", _value.toString()));
     }else{
     tokenId=uint2str(_value);
    }
    return (tokenId);
    }
//Encoded Token id