contract First is ERC721,ReentrancyGuard {
  address admin_address;
  uint32 public num_minted;
  
  //after data is written and verified, lock()
  // can be called once to permanently lock the contract
  // (no further updates permitted)
  bool public locked;
  bool public paused;  

  bytes[32] public t1; //text data
  //pointers to strings in t1
  uint16[32][32] public p1;
  uint16[32] public n1;   

  modifier requireAdmin() {
    require(admin_address == msg.sender,"Requires admin privileges");
    _;
  }  

  //transfers contract balance to GiveDirectly.org
  function donate() public payable requireAdmin {
    payable(0xc7464dbcA260A8faF033460622B23467Df5AEA42).transfer(address(this).balance);
  }
  
  function calc_pointers(uint16 cid) internal  {
    uint16 k=0;
    p1[cid][k] = 0;
    for (uint i=0;i<t1[cid].length;i++) {
      if (t1[cid][i]=='|') {
	p1[cid][++k] = uint16(i+1);
      }
    }
    n1[cid] = k;
  }

  function owner() public view virtual returns (address) {
    return admin_address;
  }

  constructor() ERC721("First", "FIRST") {
    admin_address = msg.sender;
    paused = true;
    for (uint16 i=0;i<32;i++) {
      t1[i] = "||";
      calc_pointers(i);
    }
  }

  /* Lock contract permanently, preventing any further writes */
  function lock() public requireAdmin {
    locked=true;
  }

  //pause or unpause
  function setPaused(bool m) public requireAdmin {
    paused = m;
  }
    
  /* Returns randomly generated 'first' description, returning string */
  function getString(uint256 id) public view returns (string memory) {
    return string(generate(id));
  }
  
  /* Returns randomly generated 'first' description, returning bytes */
  function generate(uint256 id) public view returns (bytes memory) {
    require(id > 0 && id < 1000000);

    //generate a sequence of 25 pseudo random numbers, seeded by id
    uint32[25] memory seq;
    uint256 r = id + 20000;
    uint16 sp = 0;
    for (uint i=0;i<25;i++) {
      r = r * 16807 % 2147483647; //advance PRNG
      seq[i] = uint32(r);
    }

    bytes memory h = "The first ";
    bytes memory NFT = "NFT ";    
    bytes memory s = new bytes(512); //max length
    uint p = 0;
    uint16 o = 0;
    uint16 f = 0;
    uint16 slot = 0;
    for (uint i=0;i<h.length;i++) s[p++] = h[i];

    f = uint16(seq[sp++] % 100);
    if (f < 50) {
      //qualifier 1
      slot = 2;
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];
    }

    f = uint16(seq[sp++] % 100);
    if (f < 32) {
      //generative art category

      slot = 4; 
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];
	
      bytes memory str = "generative ";
      for (uint i=0;i<str.length;i++) s[p++] = str[i];

      uint16 f2 = uint16(seq[sp++] % 100);
      if (f2 < 50) {	
	slot = 7; 
	o = uint16(seq[sp++] % n1[slot]);
	for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];
      }
	
      slot = 10; 
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];
    } else if (f < 50) {
      //PFP

      uint16 f2 = uint16(seq[sp++] % 100);
      if (f2 < 50) {	
	slot = 9; 
	o = uint16(seq[sp++] % n1[slot]);
	for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];
	p--;
	bytes memory str = "-derivative ";
	for (uint i=0;i<str.length;i++) s[p++] = str[i];
      }
	
      slot = 3; 
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];

      slot = 12; 
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];

    } else if (f < 54) {
      // 4% with none
    } else {
      //general category
      slot = 0; 
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];
    }
      
    for (uint i=0;i<NFT.length;i++) s[p++] = NFT[i];

    f = uint16(seq[sp++] % 100);      
    if (f < 50) {      
      slot = 1; //which chain
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];
    }
      
    f = uint16(seq[sp++] % 110);

    if (f < 5) {
      //special stuff
      slot = 13;
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];
    } else if (f < 15) {
      //featured in
      bytes memory str = "to be featured ";
      for (uint i=0;i<str.length;i++) s[p++] = str[i];
      
      slot = 14; 
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];	
    } else if (f < 23) {
      //action by a 'dao vote'
      slot = 22;
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];

      slot = 26;
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];

    } else if (f < 27) {
      // 4% with none	
    } else if (f < 40) {
      //action by a person
      slot = 8; 
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];

      slot = 5;  
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];	
    } else if (f < 50) {
      //airdropped
      bytes memory str = "to be airdropped to ";
      for (uint i=0;i<str.length;i++) s[p++] = str[i];
	
      slot = 9; 
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];

      slot = 19;  
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];	
    } else if (f < 60) {
      //stakable/burnable for x
      slot = 15;
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];
	
      slot = 6; 
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];
    } else if (f < 63) {
      //redeemable for x
      bytes memory str = "redeemable ";
      for (uint i=0;i<str.length;i++) s[p++] = str[i];
	
      slot = 25;
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];
    } else if (f < 67) {
      //other special stuff
      slot = 27;
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];	
    } else if (f < 80) {
      //with on-chain
      slot = 16;
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];

      slot = 17;
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];

      slot = 18;
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];
    } else if (f < 83) {
      //that changes over x
      slot = 20;
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];

      slot = 21;
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];
    } else if (f < 87) {
      //sold on an x price ramp
      bytes memory str = "to be sold ";
      for (uint i=0;i<str.length;i++) s[p++] = str[i];
      slot = 23;
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];
    } else if (f < 92) {
      //made using 100% programming language
      bytes memory str = "made entirely with ";
      for (uint i=0;i<str.length;i++) s[p++] = str[i];
      slot = 24;
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];
    } else if (f < 99) {
      //inspired by
      bytes memory str = "inspired by ";
      for (uint i=0;i<str.length;i++) s[p++] = str[i];
      slot = 28;
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];
    } else if (f < 110) {
      bytes memory str = "to be sold at ";
      for (uint i=0;i<str.length;i++) s[p++] = str[i];
      
      slot = 11; 
      o = uint16(seq[sp++] % n1[slot]);
      for (uint i=p1[slot][o];t1[slot][i] != '|';i++) s[p++] = t1[slot][i];

      uint16 f2 = uint16(seq[sp++] % 100);
      if (f2 < 20) {	
	bytes memory str2 = "for a record price ";
	for (uint i=0;i<str2.length;i++) s[p++] = str2[i];
      } else if (f2 < 40) {	
	bytes memory str2 = "for a pittance ";
	for (uint i=0;i<str2.length;i++) s[p++] = str2[i];
      }      
    }

    s[p-1] = '.';
    s[p] = '0';

    bytes memory s2 = new bytes(p);
    for (uint i=0;i<p;i++) s2[i] = s[i];
    return s2;
  }

  /* Upload a chunk of data */
  function setT1(uint16 cid, string calldata s) public requireAdmin {
    require(!locked,"Can't change data after locked");
    require(cid < 32);
    t1[cid] = bytes(s);
    calc_pointers(cid);
  }

  function tokenURI(uint256 tokenId) public view override returns (string memory) {
    string memory output = '<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350"><style>.base { fill: white; font-family: Courier New; font-size: 18px; }</style><rect width="100%" height="100%" fill="black" />';
    
    uint256 j = 25;
    bytes memory b = generate(tokenId);
    //calculate word wrapping 
    uint i = 0;
    uint e = 0;    
    uint ll = 30; //max length of each line
    
    while (true) {
      e = i + ll;
      if (e >= b.length) {
	e = b.length;
      } else {
	while (b[e] != ' ' && e > i) { e--; }
      }
      //splice the line in
      bytes memory line = new bytes(e-i);
      for (uint k = i; k < e; k++) {
	line[k-i] = b[k];
      }

      output = string(abi.encodePacked(output,'<text class="base" x="15" y = "',toString(j),'">',line,'</text>'));
      if (j > 200) break;
      
      j += 22;
      if (e >= b.length) break; //finished
      i = e + 1;
    }
    
    output = string(abi.encodePacked(output,'</svg>'));

    string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "First NFT #', toString(tokenId), '", "description": "First satirical on-chain generative text NFT about NFT firsts. Don\'t sleep on this historic collection.", "image": "data:image/svg+xml;base64,', Base64.encode(bytes(output)), '"}'))));
    output = string(abi.encodePacked('data:application/json;base64,', json));

    return output;
  }
  
    function claim(uint256 tokenId) public payable nonReentrant {
      require(!paused,"Currently paused");      
      require(!locked,"Can't mint after locked");
      
      require(tokenId > 0 && tokenId < 1000000, "Token ID invalid");
      require(num_minted < 5000,"All have been claimed.");

      uint price = 10000000000000000; //0.01 ETH
      require(msg.value>=10000000000000000, "Must send minimum value to purchase!");

      _mint(_msgSender(), tokenId);
      num_minted++;
    }
    
    function toString(uint256 value) internal pure returns (string memory) {
    // Inspired by OraclizeAPI's implementation - MIT license
    // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

        if (value == 0) {
            return "0";
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }

}
function setPaused(bool m) public requireAdmin {
  paused = m;
}

//pause or unpause