contract PlaybookXO is ERC721Enumerable, Ownable {
    using Strings for uint256;
    
    struct MintedAddress{
        bool isExist;
    }
    struct _canWhiteListMint{
        bool[2] _canMint; 
        bool _joined;
    }
    mapping(address => _canWhiteListMint) whitelistOrigin;
    mapping(address => MintedAddress) whitelistBranch;

    string _baseTokenURI;
    uint256 public _price = 0.06 ether;
    uint256 public presaleprice = 0.06 ether;

    bool public _paused = true;

    uint256 public _reserved =70;
    uint256 private _whitelistReserved = 404;

    uint256 public maxSupply = 777;

    uint256 public maxMintAmount = 5;
    uint256 private maxClaimable = 7;



    constructor(string memory baseURI) ERC721("PlaybookXO The Order", "PB")  {
        setBaseURI(baseURI);
    }

    function mint(address _to, uint256 _mintAmount) public payable {

        uint256 supply = totalSupply();
        require(_mintAmount > 0, "PB: Amount should be greater than 0.");
        require(supply + _mintAmount <= maxSupply - _reserved, "PB: No more NFTs to mint, decrease the quantity or check out OpenSea.");
        require(msg.sender == tx.origin, "PB: Only EOA");

        
        if(msg.sender != owner()){
            _to = msg.sender;
            require(_mintAmount <= maxMintAmount, "PB: Maximum minting amount = 5 per transaction.");
            require(msg.value >= _price * _mintAmount, "PB: Insufficient funds.");
            require(balanceOf(_to)+_mintAmount <= maxClaimable ,"PB : You can't own more than 5 tokens");
            require(!_paused, "PB: Contract paused.");
        }
        else{
            if(msg.value < _price){
                require(_reserved - _mintAmount >= 0, "PB: Admin Reserve Drained");
                _reserved = _reserved-_mintAmount;
            }
        }
        

        for(uint256 i = 1; i <= _mintAmount; i++){

            _safeMint(_to,supply + i);
        }

    }
    function whiteListMint2(bytes memory sig) public payable {

        uint256 supply = totalSupply();

        require(supply + 1 <= maxSupply - _reserved, "PB: No more NFTs to mint, decrease the quantity or check out OpenSea.");

        require(msg.value >= _price, "PB: Insufficient funds.");
        require(_whitelistReserved > 0 ,"PB : Whitelist Reserve Drained!");
        require(!whitelistOrigin[msg.sender]._joined,"PB : Whitelist can be used Twice Only!");
        require(isValidUser(msg.sender,sig,2),"PB: INVALID SIGNATURE");
        
        if (!whitelistOrigin[msg.sender]._canMint[0]){
                    _safeMint(msg.sender, supply+1);
                    whitelistOrigin[msg.sender]._canMint[0] = true;
                     _whitelistReserved--;
        }
        else if (!whitelistOrigin[msg.sender]._canMint[1]){

                    _safeMint(msg.sender, supply+1);

                    whitelistOrigin[msg.sender]._canMint[1] = true;

                    whitelistOrigin[msg.sender]._joined = true;
                     _whitelistReserved--;
        }
         
        

    }
    
    function whiteListMint1(bytes memory sig) public payable {

        uint256 supply = totalSupply();

        require(supply + 1 <= maxSupply - _reserved, "PB: No more NFTs to mint, decrease the quantity or check out OpenSea.");


        require(msg.value >= _price, "PB: Insufficient funds.");
        require(_whitelistReserved > 0 ,"PB : Whitelist Reserve Drained!");
        require(isValidUser(msg.sender,sig,1),"PB: INVALID SIGNATURE");
        require(!whitelistBranch[msg.sender].isExist,"PB : Whitelist can be used Once Only!");
        
        _safeMint(msg.sender,supply + 1);
        whitelistBranch[msg.sender].isExist = true;
        _whitelistReserved--;
        

    }
   

    function setPrice(uint256 _newPrice) public onlyOwner() {
        _price = _newPrice;
    }


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


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


    function getPrice() public view returns (uint256){
        return _price;
    }
    

    function pause(bool val) public onlyOwner() {
        _paused = val;
    }

   

    function withdrawAll() public onlyOwner() {
        require(payable(msg.sender).send(address(this).balance));
    }
     // Signature FUnctions
     
    function splitSignature(bytes memory sig) private pure returns (uint8, bytes32, bytes32){
       require(sig.length == 65);

       bytes32 r;
       bytes32 s;
       uint8 v;

       assembly {
           // first 32 bytes, after the length prefix
           r := mload(add(sig, 32))
           // second 32 bytes
           s := mload(add(sig, 64))
           // final byte (first byte of the next 32 bytes)
           v := byte(0, mload(add(sig, 96)))
       }
     
       return (v, r, s);
   }

    
    function isValidUser(address _addr,bytes memory sig,uint256 _whitelistID)public view returns (bool){
        bytes32 hash;
        if(_whitelistID == 2){
            hash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32",_addr,owner()));
        }
        else{
            hash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32",_addr));
        }
        
        uint8 v;
        bytes32 r;
        bytes32 s;

        (v, r, s) = splitSignature(sig);
        return (owner() == ecrecover(hash, v, r, s));
        //return (hash);
        
    }
    
}
function splitSignature(bytes memory sig) private pure returns (uint8, bytes32, bytes32){
   require(sig.length == 65);

   bytes32 r;
   bytes32 s;
   uint8 v;

   assembly {
       // first 32 bytes, after the length prefix
       r := mload(add(sig, 32))
       // second 32 bytes
       s := mload(add(sig, 64))
       // final byte (first byte of the next 32 bytes)
       v := byte(0, mload(add(sig, 96)))
   }
     
   return (v, r, s);


// Signature FUnctions