contract ChainRings is ERC721, Ownable {
using SafeMath for uint256;
using Strings for uint256;
uint public constant MAX_RINGS = 1024;
bool public hasSaleStarted = false;
mapping (uint256 => uint256) public creationDates;
mapping (uint256 => address) public creators;

// these will be set in future
string public METADATA_PROVENANCE_HASH = "";
string public GENERATOR_ADDRESS = "https://chainrings.com/generator/";
string public IPFS_GENERATOR_ADDRESS = "";
string public SCRIPT = "";
// e.s added minter address to hash algorithm
constructor() ERC721("ChainRings","CRING")  {
setBaseURI("https://chainrings.art/api/token/");
_safeMint(msg.sender, 0);
creationDates[0] = block.number;
creators[0] = msg.sender;
SCRIPT = "class Random{constructor(e){this.seed=e}random_dec(){return this.seed^=this.seed<<13,this.seed^=this.seed>>17,this.seed^=this.seed<<5,(this.seed<0?1+~this.seed:this.seed)%1e3/1e3}random_between(e,a){return e+(a-e)*this.random_dec()}random_int(e,a){return Math.floor(this.random_between(e,a+1))}random_choice(e){return e[Math.floor(this.random_between(0,.99*e.length))]}}let s$,tokenData=window.tokenHash,seed=parseInt(tokenData.slice(0,16),16),rng=new Random(seed),palettes=['e54b4b-ffa987-f7ebe8-444140-1e1e24','a6ebc9-61ff7e-5eeb5b-62ab37-393424','95f9e3-69ebd0-49d49d-558564-564946','97f9f9-a4def9-c1e0f7-cfbae1-c59fc9','ddd1c7-c2cfb2-8db580-7e8987-4b4a67','250902-38040e-640d14-800e13-ad2831','333333-839788-eee0cb-baa898-bfd7ea','585123-eec170-f2a65a-f58549-772f1a','fbf5f3-e28413-000022-de3c4b-c42847','0fa3b1-d9e5d6-eddea4-f7a072-ff9b42','10002b-240046-5a189a-9d4edd-e0aaff','0466c8-023e7d-001845-33415c-7d8597','861657-a64253-d56aa0-bbdbb4-fcf0cc','493843-61988e-a0b2a6-cbbfbb-eabda8','031d44-04395e-70a288-dab785-d5896f','ff0a54-ff5c8a-ff85a1-fbb1bd-f7cad0','463f3a-8a817c-bcb8b1-f4f3ee-e0afa0','dd6e42-e8dab2-4f6d7a-c0d6df-eaeaea','ffd6ff-e7c6ff-c8b6ff-b8c0ff-bbd0ff','aa8f66-ed9b40-ffeedb-61c9a8-ba3b46','a57548-fcd7ad-f6c28b-5296a5-82ddf0','713e5a-63a375-edc79b-d57a66-ca6680','114b5f-456990-e4fde1-f45b69-6b2737','edf2fb-e2eafc-ccdbfd-c1d3fe-abc4ff','9cafb7-ead2ac-fe938c-e6b89c-4281a4','7bdff2-b2f7ef-eff7f6-f7d6e0-f2b5d4','ffcdb2-ffb4a2-e5989b-b5838d-6d6875','f2d7ee-d3bcc0-a5668b-69306d-0e103d','ffbe0b-fb5607-ff006e-8338ec-3a86ff','9b5de5-f15bb5-fee440-00bbf9-00f5d4','fee440-f15bb5-9b5de5-00bbf9-00f5d4','181a99-5d93cc-454593-e05328-e28976','F61067-5E239D-00F0B5-6DECAF-F4F4ED','f8f9fa-dee2e6-adb5bd-495057-212529','212529-000000-adb5bd-495057-f8f9fa'].map(e=>e.split('-').map(e=>'#'+e)),palette=rng.random_choice(palettes),viewport=Math.min(window.innerHeight,window.innerWidth),w=viewport,h=viewport,row=rng.random_int(5,30),rows=row,offset=w/row,cols=rng.random_int(row-row/3,30),radius=w/row,Rstroke=w/500,cellColors=[],cellShapes=[],total=rows*cols,maxHeight=radius*rows,t$=0,tempFrame=0,isThicc=!1,rotation$=rng.random_between(0,1);function stroke$(){return isThicc?++tempFrame>=1e3?(isThicc=!1,Math.max(Rstroke,tempFrame/10)):Math.max(Rstroke,tempFrame/10):(tempFrame=Math.abs(tempFrame-1),Math.max(Rstroke,tempFrame/10))}function setup(){s$=rng.random_between(.001,.004),frameRate(36),noiseSeed(seed),createCanvas(w,h);for(let e=0;e<row*cols;e++)cellColors[e]=rng.random_choice(palette.slice(0,palette.length-1)),cellShapes[e]=rng.random_between(0,1)}function draw(){rotation$<.4?translate(0,0-offset/2):rotation$<.6?(rotate(-PI/2),scale(-1),translate(0,-w-offset/2)):rotation$<.8?(rotate(-PI),translate(-w,-w-radius/2)):(rotate(-PI/2),translate(-w,-offset/2)),background(palette[palette.length-1]),noFill(),strokeWeight(stroke$());for(let e=0;e<cols;e++){const a=width/(cols-1)*e;let t=(t$+(cols-e)/(2*cols)+1)%1;t=Math.sin(map(cos(t*TWO_PI),-1,1,0,1)/1*(Math.PI/2));for(let f=0;f<rows;f++){const d=f/rows*t*maxHeight+offset;if(stroke(cellColors[f*e]),cellShapes[f*e]>.95)push(),fill(cellColors[f*e]),ellipse(a,d,radius),pop();else if(cellShapes[f*e]>.7){if(cellShapes[f*e]>.72){push();let e=color(rng.random_choice(palette)),a=color(rng.random_choice(palette));fill(lerpColor(e,a,Math.abs(100*sin(100*frameCount)))),noStroke(),pop()}rect(a-radius/2,d-radius/2,radius,radius,100*Math.abs(sin(frameCount/100)))}else ellipse(a,d,radius)}}(t$+=s$)>=1&&(t$=0)}function mouseClicked(){isThicc=!isThicc}";
}

function tokensOfOwner(address _owner) external view returns(uint256[] memory ) {
uint256 tokenCount = balanceOf(_owner);
if (tokenCount == 0) {
// Return an empty array
return new uint256[](0);
} else {
uint256[] memory result = new uint256[](tokenCount);
uint256 index;
for (index = 0; index < tokenCount; index++) {
result[index] = tokenOfOwnerByIndex(_owner, index);
}
return result;
}
}

function calculatePrice() public view returns (uint256) {
return 50000000000000000;
}

function mintRings(uint256 maxRINGS) public payable {
require(totalSupply() < MAX_RINGS, "Sale has already ended");
require(maxRINGS > 0 && maxRINGS <= 10, "You can claim minimum 1, maximum 10 rings");
require(totalSupply().add(maxRINGS) <= MAX_RINGS, "Exceeds MAX_RINGS");
require(msg.value >= calculatePrice().mul(maxRINGS), "Ether value sent is below the price");

for (uint i = 0; i < maxRINGS; i++) {
uint mintIndex = totalSupply();
_safeMint(msg.sender, mintIndex);
creationDates[mintIndex] = block.number;
creators[mintIndex] = msg.sender;
}
}

// ONLYOWNER FUNCTIONS

function setProvenanceHash(string memory _hash) public onlyOwner {
METADATA_PROVENANCE_HASH = _hash;
}

function setGeneratorIPFSHash(string memory _hash) public onlyOwner {
IPFS_GENERATOR_ADDRESS = _hash;
}

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

function startDrop() public onlyOwner {
hasSaleStarted = true;
}

function pauseDrop() public onlyOwner {
hasSaleStarted = false;
}

function withdrawAll() public payable onlyOwner {
require(payable(msg.sender).send(address(this).balance));
}

function tokenHash(uint256 tokenId) public view returns(bytes32){
require(_exists(tokenId), "DOES NOT EXIST");
return bytes32(keccak256(abi.encodePacked(address(this), creationDates[tokenId], creators[tokenId], tokenId)));
}

function generatorAddress(uint256 tokenId) public view returns (string memory) {
require(_exists(tokenId), "DOES NOT EXIST");
return string(abi.encodePacked(GENERATOR_ADDRESS, tokenId.toString()));
}

//e.s added this. could be useful

function IPFSgeneratorAddress(uint256 tokenId) public view returns (string memory) {
require(_exists(tokenId), "DOES NOT EXIST");
return string(abi.encodePacked(IPFS_GENERATOR_ADDRESS, tokenId.toString()));
}
}
function setProvenanceHash(string memory _hash) public onlyOwner {
METADATA_PROVENANCE_HASH = _hash;
}

// ONLYOWNER FUNCTIONS