contract Distortion is ERC721A, Ownable, ReentrancyGuard {



  bool public isClaimActive = false;
  uint public maxSupply = 1000;
  uint public maxFree = 200;
  uint256 public price = 0.05 ether;    
  address payable private immutable payoutAddress;


  // keep track of wallets that have claimed
  mapping(address => uint) private claimed;
  mapping(address => uint) private claimedFree;

  // randomness of block in the future (#xxxxxxxx)
  string private hashOfBlock;
  bool private hashSet = false;

  constructor(
    address payable _payoutAddress
  ) ERC721A("Distortion", "DST", 1, maxSupply) {

    require(_payoutAddress != address(0));
    payoutAddress = _payoutAddress;

  }


  /*

  ███╗░░░███╗██╗███╗░░██╗████████╗
  ████╗░████║██║████╗░██║╚══██╔══╝
  ██╔████╔██║██║██╔██╗██║░░░██║░░░
  ██║╚██╔╝██║██║██║╚████║░░░██║░░░
  ██║░╚═╝░██║██║██║░╚███║░░░██║░░░
  ╚═╝░░░░░╚═╝╚═╝╚═╝░░╚══╝░░░╚═╝░░░
  Information on 0xDistortion.com
  */

  function mint() public payable callerIsWallet {
    require(isClaimActive, "Claim is not active...");
    require(totalSupply() + 1 <= maxSupply, "We have no more supply in stock.");

    if (totalSupply() + 1 > maxFree) {
      require(price == msg.value, "Wrong ether amount. Free supply depleted.");
      require(claimed[msg.sender] + 1 <= 2, "Max 2 per wallet."); 
      claimed[msg.sender] += 1;

    } else {
      require(msg.value == 0, "Don't send ether for the free mint.");
      require(claimedFree[msg.sender] < 1, "You can only get 1 free mint.");
      claimedFree[msg.sender] += 1;
    }
    
    _safeMint(msg.sender, 1);
  }



  /*
  ██████╗░███████╗██╗░░░██╗
  ██╔══██╗██╔════╝██║░░░██║
  ██║░░██║█████╗░░╚██╗░██╔╝
  ██║░░██║██╔══╝░░░╚████╔╝░
  ██████╔╝███████╗░░╚██╔╝░░
  ╚═════╝░╚══════╝░░░╚═╝░░░
  Developer functions:
  - Dev mint
  - Dev airdrop
  - Start the sale/claim
  - Update the price of the sale
  - Update the hash/provenance for the reveal (can only be called once)
  */

  function devMint() public onlyOwner {
    require(totalSupply() + 1 <= maxSupply, "Creator cannot mint if supply has been reached.");
    _safeMint(msg.sender, 1);
  }

  function devAirdrop(address _recipient) public onlyOwner {
    require(totalSupply() + 1 <= maxSupply, "Creator cannot airdrop if supply has been reached.");
    _safeMint(_recipient, 1);
  }


  function setClaimState(bool _trueOrFalse) public onlyOwner {
    isClaimActive = _trueOrFalse;
  }

  // adding the hash of the block of the last mint to ensure randomness.
  function updateHash(string memory _hash) public onlyOwner {
    require(!hashSet, "Randomness hash can only be set once by the deployer.");
    hashOfBlock = _hash;
    hashSet = true;
  }

  function updatePrice(uint256 _price) public onlyOwner {
    price = _price;
  }



  /*

  ░██████╗░███████╗███╗░░██╗███████╗██████╗░░█████╗░████████╗██╗██╗░░░██╗███████╗
  ██╔════╝░██╔════╝████╗░██║██╔════╝██╔══██╗██╔══██╗╚══██╔══╝██║██║░░░██║██╔════╝
  ██║░░██╗░█████╗░░██╔██╗██║█████╗░░██████╔╝███████║░░░██║░░░██║╚██╗░██╔╝█████╗░░
  ██║░░╚██╗██╔══╝░░██║╚████║██╔══╝░░██╔══██╗██╔══██║░░░██║░░░██║░╚████╔╝░██╔══╝░░
  ╚██████╔╝███████╗██║░╚███║███████╗██║░░██║██║░░██║░░░██║░░░██║░░╚██╔╝░░███████╗
  ░╚═════╝░╚══════╝╚═╝░░╚══╝╚══════╝╚═╝░░╚═╝╚═╝░░╚═╝░░░╚═╝░░░╚═╝░░░╚═╝░░░╚══════╝
  Everything to do with the on-chain generation of the Distortion pieces.
  */



  string[] private colorNames = [
    'White' ,
    'Red',
    'Green',
    'Blue',
    'Gold',
    'Rose',
    'Purple'
  ]; 


  string[] private left = [
    'rgb(140,140,140)' ,
    'rgb(255, 26, 26)',
    'rgb(92, 214, 92)',
    'rgb(26, 140, 255)',
    'rgb(255, 215, 0)',
    'rgb(255, 128, 128)',
    'rgb(192, 50, 227)'
  ]; 

  string[] private right = [
    'rgb(52,52,52)' ,
    'rgb(230, 0, 0)',
    'rgb(51, 204, 51)',
    'rgb(0, 115, 230)',
    'rgb(204, 173, 0)',
    'rgb(255, 102, 102)',
    'rgb(167, 40, 199)'
  ]; 

  string[] private middleLeft = [
    'rgb(57,57,57)' ,
    'rgb(179, 0, 0)',
    'rgb(41, 163, 41)',
    'rgb(0, 89, 179)',
    'rgb(153, 130, 0)',
    'rgb(255, 77, 77)',
    'rgb(127, 32, 150)'
  ]; 

  string[] private middleRight = [
    'rgb(20,20,20)',
    'rgb(128, 0, 0)',
    'rgb(31, 122, 31)',
    'rgb(0, 64, 128)',
    'rgb(179, 152, 0)',
    'rgb(255, 51, 51)',
    'rgb(98, 19, 117)'
  ]; 
  



  string[] private frequencies = [
    '',
    '0',
    '00'
  ]; 

        
  function generateString(string memory name, uint256 tokenId, string[] memory array) internal view returns (string memory) {

    uint rand = uint(keccak256(abi.encodePacked(name, toString(tokenId)))) % array.length;
    string memory output = string(abi.encodePacked(array[rand % array.length]));
    return output;

  }


  function generateColorNumber(string memory name, uint256 tokenId) internal view returns (uint) {

    uint output;
    uint rand = uint(keccak256(abi.encodePacked(name, toString(tokenId)))) % 100;

    if (keccak256(bytes(hashOfBlock)) == keccak256(bytes(''))) {
        output = 0; //unrevealed
    } else {
      if (rand <= 15) {
        output = 1; //Red with 15% rarity.
      } else if (rand > 15 && rand <= 30) {
        output = 2; //Green with 15% rarity.
      } else if (rand > 30 && rand <= 45) {
        output = 3; //Blue with 15% rarity.
      } else if (rand > 45 && rand <= 75) {
        output = 0; //Black with 30% rarity.
      } else if (rand > 75 && rand <= 80) {
        output = 4; //Gold with 5% rarity.
      } else if (rand > 80 && rand <= 90) {
        output = 5; //Rose with 10% rarity.
      } else if (rand > 90) {
        output = 6; //Purple with 10% rarity.
      }

    }
    return output;
  }


  function generateNum(string memory name, uint256 tokenId, string memory genVar, uint low, uint high) internal view returns (string memory) {

    uint difference = high - low;
    uint randomnumber = uint(keccak256(abi.encodePacked(genVar, tokenId, name))) % difference + 1;
    randomnumber = randomnumber + low;
    return toString(randomnumber);

  }

  function generateNumUint(string memory name, uint256 tokenId, string memory genVar, uint low, uint high) internal view returns (uint) {

    uint difference = high - low;
    uint randomnumber = uint(keccak256(abi.encodePacked(genVar, tokenId, name))) % difference + 1;
    randomnumber = randomnumber + low;
    return randomnumber;

  }


  function genDefs(uint256 tokenId) internal view returns (string memory) {

    string memory output;
    string memory xFrequency = generateString("xF", tokenId, frequencies);
    string memory yFrequency = generateString("yF", tokenId, frequencies);
    string memory scale = generateNum("scale", tokenId, hashOfBlock, 10, 40);

    if (keccak256(bytes(hashOfBlock)) == keccak256(bytes(''))) {
      xFrequency = '';
      yFrequency = '';
      scale = '30';
    }

    output = string(abi.encodePacked(
      '<defs><filter id="squares" x="-30%" y="-30%" width="160%" height="160%"> <feTurbulence type="turbulence" baseFrequency="',
      '0.',
      xFrequency,
      '5 0.',
      yFrequency,
      '5',
      '" numOctaves="10" seed="" result="turbulence"> <animate attributeName="seed" dur="0.3s" repeatCount="indefinite" calcMode="discrete" values="1;2;3;4;5;6;7;8;9;1"/> </feTurbulence> <feDisplacementMap in="SourceGraphic" in2="turbulence" scale="',
      scale,
      '" xChannelSelector="R" yChannelSelector="G" /> </filter> </defs>'
      ));
    return output;

  }
      

  function genMiddle(uint256 tokenId) internal view returns (string memory) {

    string memory translate = toString(divide(generateNumUint("scale", tokenId, hashOfBlock, 10, 40), 5, 0));
    string[5] memory p;

    if (keccak256(bytes(hashOfBlock)) == keccak256(bytes(''))) {
      translate = '6';
    }

    p[0] = '<style alt="surround"> #1 { stroke-dasharray: 50,50,150 }</style> <!-- translate below is equal to scale above divided by 5 --> <!-- x starts at 5, increments by 2 | width is equal to 100 - (2*x) --> <g style="filter: url(#squares);" opacity="100%" id="1"> <g transform="translate(-';
    p[1] = translate;
    p[2] = ', -';
    p[3] = translate;
    p[4] = ')" >';

    string memory output = string(abi.encodePacked(p[0], p[1], p[2], p[3], p[4]));
    return output;

  }



  function genSquares(uint256 tokenId) internal view returns (string memory) {

    string memory output1;
    string memory output2;
    uint ringCount = generateNumUint("ringCount", tokenId, hashOfBlock, 5, 15);
    string[2] memory xywh;
    uint ringScaling = divide(25, ringCount, 0);

    if (keccak256(bytes(hashOfBlock)) == keccak256(bytes(''))) {
      ringCount = 5;
      ringScaling = 5;
    }

    for (uint i = 0; i < ringCount; i++) {    
      xywh[0] = toString(ringScaling*i + 5);
      xywh[1] = toString(100 - (ringScaling*i + 5)*2);
      output1 = string(abi.encodePacked(
          '<g style="animation: glitch 1.',
          toString(i),
          's infinite;"> <rect x="',
          xywh[0],
          '%" y="',
          xywh[0],
          '%" width="',
          xywh[1],
          '%" height="',
          xywh[1],
          '%" fill="none" stroke="',
          left[generateColorNumber("color", tokenId)],
          '" id="1" /> </g>'
          ));
      output2 = string(abi.encodePacked(output1, output2));
    }
    return output2;
          
  }


  function genEnd(uint256 tokenId) internal view returns (string memory) {

    uint colorNum = generateColorNumber("color", tokenId);
    string[13] memory p;
    p[0] = '</g> </g> <!-- END SQUARES --> <g style="animation: glitch 0.5s infinite;filter: url(#squares);"> <g transform="scale(0.50) translate(370, 287)" style="opacity:40%"> <path fill="';
    p[1] = right[colorNum];
    p[2] = '" d="M125.166 285.168l2.795 2.79 127.962-75.638L127.961 0l-2.795 9.5z"/><path fill="';
    p[3] = left[colorNum];
    p[4] = '" d="M127.962 287.959V0L0 212.32z"/><path fill="';
    p[5] = right[colorNum];
    p[6] = '" d="M126.386 412.306l1.575 4.6L256 236.587l-128.038 75.6-1.575 1.92z"/><path fill="';
    p[7] = left[colorNum];
    p[8] = '" d="M0 236.585l127.962 180.32v-104.72z"/><path fill="';
    p[9] = middleRight[colorNum];
    p[10] = '" d="M127.961 154.159v133.799l127.96-75.637z"/><path fill="';
    p[11] = middleLeft[colorNum];
    p[12] = '" d="M127.96 154.159L0 212.32l127.96 75.637z"/> </g> </g> </svg>';
    string memory output = string(abi.encodePacked(p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8]));
    output = string(abi.encodePacked(output, p[9], p[10], p[11], p[12]));
    return output;

  }


  function generateDistortion(uint256 tokenId) public view returns (string memory) {

    string memory output;
    output = string(abi.encodePacked(
        '<svg width="750px" height="750px" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="background-color: #101010;"> <!-- START BACKGROUND --> <style> @keyframes glitch { 0% {transform: translate(0px); opacity: 0.15;} 7% {transform: translate(2px); opacity: 0.65;} 45% {transform: translate(0px); opacity: 0.35;} 50% {transform: translate(-2px); opacity: 0.85;} 100% {transform: translate(0px); opacity: 0.25;} } </style> <defs> <filter id="background" x="-20%" y="-20%" width="140%" height="140%" filterUnits="objectBoundingBox" primitiveUnits="userSpaceOnUse" color-interpolation-filters="linearRGB"> <feTurbulence type="fractalNoise" baseFrequency="10" numOctaves="4" seed="1" stitchTiles="stitch" x="0%" y="0%" width="100%" height="100%" result="turbulence"> <animate attributeName="seed" dur="1s" repeatCount="indefinite" calcMode="discrete" values="1;2;3;4;5;6;7;8;9;10" /> </feTurbulence> <feSpecularLighting surfaceScale="10" specularExponent="10" lighting-color="#fff" width="100%" height="100%"> <animate attributeName="surfaceScale" dur="1s" repeatCount="indefinite" calcMode="discrete" values="10;11;12;13;14;15;14;13;12;11" /> <feDistantLight elevation="100"/> </feSpecularLighting> </filter> </defs> <g opacity="10%"> <rect width="700" height="700" fill="hsl(23, 0%, 100%)" filter="url(#background)"></rect> </g> <!-- END BACKGROUND --> <!-- START SQUARES -->',
        genDefs(tokenId),
        genMiddle(tokenId),
        genSquares(tokenId),
        genEnd(tokenId)
        ));
    return output;

  }
    
    
  function getFrequency(uint256 tokenId) internal view returns (uint) {

    uint[2] memory xy;
    string memory y = generateString("yF", tokenId, frequencies);
    string memory x = generateString("xF", tokenId, frequencies);

    if (keccak256(bytes(x)) == keccak256(bytes('0'))){
      xy[0] = 2;
    } else if (keccak256(bytes(x)) == keccak256(bytes('00'))){
      xy[0] = 1;
    } else {
      xy[0] = 3;
    }

    if (keccak256(bytes(y)) == keccak256(bytes('0'))){
      xy[1] = 2;
    } else if (keccak256(bytes(y)) == keccak256(bytes('00'))){
      xy[1] = 1;
    } else {
      xy[1] = 3;
    }
    return xy[0] * xy[1];

  }



  /**
  * @dev See {IERC721Metadata-tokenURI}.
  */
  function tokenURI(uint256 tokenId) override public view returns (string memory) {
    
    string memory ringCount = generateNum("ringCount", tokenId, hashOfBlock, 5, 15);
    string memory scale = generateNum("scale", tokenId, hashOfBlock, 10, 40);
    uint freq = getFrequency(tokenId);
    string memory unr;

    // if unrevealed
    if (keccak256(bytes(hashOfBlock)) == keccak256(bytes(''))) { scale = '0'; ringCount = '0'; unr = ' (Unrevealed)'; freq = 0;}

    string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "Distortion #', toString(tokenId), unr,
    '","attributes": [ { "trait_type": "Color", "value": "',
    colorNames[generateColorNumber("color", tokenId)],
    '" }, { "trait_type": "Distortion Scale", "value": ',
    scale,
    ' }, { "trait_type": "Rings", "value": ', 
    ringCount,
    ' }, { "trait_type": "Frequency Multiple", "value": ', 
    toString(freq),
    ' }]',
    ', "description": "Distortion is a fully hand-typed 100% on-chain art collection limited to 1,000 pieces.", "image": "data:image/svg+xml;base64,',
    Base64.encode(bytes(string(abi.encodePacked(generateDistortion(tokenId))))),
    '"}'))));
    string memory output = string(abi.encodePacked('data:application/json;base64,', json));

    return output;

  }
	
	
  /**
    * @dev Converts a `uint256` to its ASCII `string` decimal representation.
    */
  function toString(uint256 value) internal pure returns (string memory) {
    // Inspired by OraclizeAPI's implementation - MIT licence
    // 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 divide(uint a, uint b, uint precision) internal view returns ( uint) {
    return a*(10**precision)/b;
  }


  /**
  * @dev safety measure
  */
  modifier callerIsWallet() {
    require(tx.origin == msg.sender, "The caller is another contract");
    _;
  }


  function withdraw() public onlyOwner {
    uint256 balance = address(this).balance;
    Address.sendValue(payoutAddress, balance);
  }


  /**
  * @notice For the attributes to be revealed, the hash of the block of the final mint must be set so provenance can be verified.
  *
  */
  function getHash() public view returns (string memory) {
    return hashOfBlock;
  }


}
function getHash() public view returns (string memory) {
  return hashOfBlock;
}

/**
 * @notice For the attributes to be revealed, the hash of the block of the final mint must be set so provenance can be verified.
 *
 */