contract VaporWave is ERC721Enumerable, Ownable {
  using Strings for uint256;

  string baseURI;
  string private gateway = "https://macplus.mypinata.cloud/ipfs/";
  uint256 public maxSupply = 9999;
  mapping(uint256 => uint256) private tokenMatrix;  //random
  mapping(uint256 => string) private pic; //pic hash
  mapping(uint256 => string) private text; //text
  
  // Attributes
  string[] private Bg = [
        "Blue", "Purple", "Seapunk", "Back_to_80s", "Cyberpunk", "Pink", "Colorwave"
    ];
  uint8[] private Bg_w =[32, 28, 20, 18, 14, 8, 2];
  
  string[] private P = [
        "Sunset", "City_Skyline", "Water_Container", "Fuji_Paint", "XP", 
        "Google", "Sunsetwave", "Face_life", "Glitch_XP", "Error", "Paint_Loop",
        "Missing_Texture", "Andy_Warhol_in_TDK", "PLASTIC_LOVE", "Simpsonwave", "Loop", "Loot", "TV_in_TDK"
    ];
  uint8[] private P_w =[230, 160, 140, 120, 110, 82, 80, 60, 48, 40, 38, 30, 27, 22, 17, 9, 5, 2];
  
  string[] private G = [
        "Blue_Beam_Grid", "Purple_Beam_Grid", "Red_Beam_Grid", "Pink_Beam_Grid", "Sliver_Beam_Grid", "Gold_Beam_Grid",
        "Blue_Grid", "Purple_Grid", "Pink_Grid", "Sliver_Grid", "Gold_Grid", "Rainbow_Grid", "Black_Grid"
    ];
  uint8[] private G_w =[251, 231, 203, 176, 154, 125, 104, 77, 55, 36, 15, 6, 2];

  string[] private Bd = [
        "None", "Coconut_Tree", "Blue_Tree", "Purple_Tree", "Pink_Tree", "Sliver_Tree", "Gold_Tree",
        "Blue_Column", "Purple_Column", "Pink_Column", "Sliver_Column", "Gold_Column", "Rainbow_Column"
    ];
  uint8[] private Bd_w =[251, 63, 58, 54, 48, 42, 35, 24, 16, 11, 6, 3, 1];

  string[] private S = [
        "Normal", "Gold"
    ];
  uint8[] private S_w =[251, 2];

  string[] private H = [
        "GLITCH-1", "GLITCH-2", "GLITCH-3", "GLITCH-4", "GLITCH-5", "Mosaic", "AESTHETICS",
        "Astronaut", "Laser_Eyes", "Blue_Beam", "Gameboy", "Macintosh_Plus", "UniAstronaut"
    ];
  uint8[] private H_w =[200, 198, 196, 194, 192, 153, 139, 95, 58, 39, 20, 15, 8]; 

  string[] private E = [
        "None", "Dolphin", "Coconut_Tree", "Icecream", "Ethereum", "Cursor", "Hand_Cursor",
        "Win95_CD", "Win95_FD", "BAYC_Diamond_Stud", "Green_Rupees", "BAYC_Cross", "All_Seeing_Eye",
        "CP_Earing", "Blue_Rupees", "Pokeballs", "Red_Rupees"
    ];
  uint8[] private E_w =[251, 63, 61, 57, 51, 40, 35, 27, 24, 20, 17, 15, 12, 9, 5, 2, 1];

  string[] private L = [
        "Bad_Apple", "CICADAS", "D_Station", "DOORS-95", "Internet_Obedient", 
        "SEJA", "VTARI", "Z64", "DOLCOIN", "SURAPU_CAM"
    ];
  uint8[] private L_w =[200, 180, 150, 100, 80, 60, 40, 24, 9, 3];

  string[] private D = [
        "None", "Silver_Necklace", "Gold_Necklace", "Skeleton_Necklace", "Pac_Man", 
        "Coins", "Theme_Tee", "Alien_Tee", "You_can_have_one", "Warp_Pipe", "Master_Sword"
    ];
  uint8[] private D_w =[251, 63, 52, 47, 28, 24, 17, 14, 8, 5, 1];
  
  string partsCode = '.png" width="1024" height="1024"/><image xlink:href="';

  constructor(
    string memory _name,
    string memory _symbol,
    string memory _initBaseURI
  ) ERC721(_name, _symbol) {
    setBaseURI(_initBaseURI);
  }

  // internal
  function _baseURI() internal view virtual override returns (string memory) {
    return baseURI;
  }
  
  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 wcheck(uint8[] memory w,uint256 i) internal pure returns (uint8) {
    uint8 ind=0;
    uint256 j=uint256(w[0]);
    while (j<=i) {
      ind++;
      j+=uint256(w[ind]);
    }
    return ind;
  }

  function wsum(uint8[] memory w) internal pure returns (uint256) {
    uint256 ws = 0;
    for (uint8 i = 0; i < w.length; i++) {
            ws += w[i];
        }
    return ws;
  }

  function pluck(uint256 tokenId, string memory keyPrefix, string[] memory sourceArray, uint8[] memory sourceWeight) internal pure returns (string memory) {
    uint256 rand = uint256(keccak256(abi.encodePacked(string(abi.encodePacked(keyPrefix, toString(tokenId))))));
    string memory output = sourceArray[wcheck(sourceWeight, rand % wsum(sourceWeight))];
    return output;
  }
  
  function getSVG(uint256 tokenId) internal view returns (string memory) {
    string[14] memory parts;
    parts[0] = '<svg version="1.1" preserveAspectRatio="xMinYMin meet" width="1024" height="1024" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><image xlink:href="https://ipfs.io/ipfs/';

    parts[1] = pic[tokenId];

    parts[2] = '" x="96" y="92" width="400" height="400"/><image xlink:href="';
    
    parts[3] = string(abi.encodePacked(gateway, baseURI, "/Background/", pluck(tokenId, "Background", Bg, Bg_w), partsCode));
    
    parts[4] = string(abi.encodePacked(gateway, baseURI, "/Picture/", pluck(tokenId, "Picture", P, P_w), partsCode));
    
    parts[5] = string(abi.encodePacked(gateway, baseURI, "/Ground/", pluck(tokenId, "Ground", G, G_w), partsCode));
    
    parts[6] = string(abi.encodePacked(gateway, baseURI, "/Buildings/", pluck(tokenId, "Buildings", Bd, Bd_w), partsCode));
    
    parts[7] = string(abi.encodePacked(gateway, baseURI, "/Head/", pluck(tokenId, "Head", H, H_w), "-", pluck(tokenId, "Skin", S, S_w), partsCode));
    
    parts[8] = string(abi.encodePacked(gateway, baseURI, "/Earings/", pluck(tokenId, "Earings", E, E_w), partsCode));
    
    parts[9] = string(abi.encodePacked(gateway, baseURI, "/Logo/", pluck(tokenId, "Logo", L, L_w), partsCode));
    
    parts[10] = string(abi.encodePacked(gateway, baseURI, "/Decorations/", pluck(tokenId, "Decorations", D, D_w)));
    
    parts[11] = '.png" width="1024" height="1024"/><filter id="F"><feGaussianBlur in="SourceAlpha" stdDeviation="3" result="blur" /><feOffset in="blur" dx="2" dy="2" result="offsetBlur" /><feMerge><feMergeNode in="offsetBlur" /><feMergeNode in="SourceGraphic" /></feMerge></filter><text text-anchor="end" font-family="MS Gothic, Arial Narrow, SimHei, sans-serif" font-size="64" x="984" y="260" fill="#6cffb9" filter="url(#F)">';

    parts[12] = text[tokenId];

    parts[13] = '</text></svg>';
    
    string memory output = string(abi.encodePacked(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], parts[7], parts[8]));
    output = string(abi.encodePacked(output, parts[9], parts[10], parts[11], parts[12], parts[13]));
    return output;
  }

  function getAttributes(uint256 tokenId) internal view returns (string memory) {
    string[5] memory parts;
    parts[0] = string(abi.encodePacked('", "attributes": [{"trait_type": "Background","value": "', pluck(tokenId, "Background", Bg, Bg_w), '"},{"trait_type": "Picture","value": "', pluck(tokenId, "Picture", P, P_w)));
    
    parts[1] = string(abi.encodePacked('"},{"trait_type": "Ground","value": "', pluck(tokenId, "Ground", G, G_w), '"},{"trait_type": "Buildings","value": "', pluck(tokenId, "Buildings", Bd, Bd_w)));
    
    parts[2] = string(abi.encodePacked('"},{"trait_type": "Skin","value": "', pluck(tokenId, "Skin", S, S_w), '"},{"trait_type": "Head","value": "', pluck(tokenId, "Head", H, H_w)));
    
    parts[3] = string(abi.encodePacked('"},{"trait_type": "Earings","value": "', pluck(tokenId, "Earings", E, E_w), '"},{"trait_type": "Logo","value": "', pluck(tokenId, "Logo", L, L_w)));

    parts[4] = string(abi.encodePacked('"},{"trait_type": "Decorations","value": "', pluck(tokenId, "Decorations", D, D_w), '"}]'));
    
    string memory output = string(abi.encodePacked(parts[0], parts[1], parts[2], parts[3], parts[4]));
    return output;
  }

  // public
  function create(string memory _pic, string memory _text) public payable {
    uint256 supply = totalSupply();
    require(supply < maxSupply);
    require(bytes(_text).length <= 15);

    if (msg.sender != owner()) {
      require(msg.value >= _cost(supply));
    }

    uint256 tmptokenid = _getTokenToBeMinted(supply) + 1;
    pic[tmptokenid] = _pic;
    text[tmptokenid] = _text;
    _safeMint(msg.sender, tmptokenid);
  }

  function tokenURI(uint256 tokenId) override public view returns (string memory) {
        
    require(
      _exists(tokenId),
      "ERC721Metadata: URI query for nonexistent token"
    );
        
    string memory output = Base64.encode(bytes(string(abi.encodePacked('{"name": "VaporWave#', toString(tokenId), ' - ', text[tokenId], '", "description": "VaporWave is the first ever onchain DIY collection. It allows mintors to create it with NFT IPFS hash and customized text.", "image": "data:image/svg+xml;base64,', Base64.encode(bytes(getSVG(tokenId))), getAttributes(tokenId), ', "external_url": "https://www.vaporwave.vip"}'))));
    output = string(abi.encodePacked('data:application/json;base64,', output));
    
    return output;
    
    }

  //only owner

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

  function setgateway(string memory _gateway) public onlyOwner {
    gateway = _gateway;
  }
  
  function withdraw() public payable onlyOwner {
    (bool success, ) = payable(msg.sender).call{value: address(this).balance}("");
    require(success);
  }
  
  //private
  function _cost(uint256 _supply)
        private 
        pure
        returns (uint256)
    {
        uint256 price;
        if (_supply >= 0 && _supply < 500) {
            price = 0 ether;
        }
        else if (_supply >= 500 && _supply < 2000) {
            price = 0.02 ether;
        }
        else if (_supply >= 2000 && _supply < 5000) {
            price = 0.04 ether;
        }
        else if (_supply >= 5000 && _supply < 8000) {
            price = 0.08 ether;
        }
        else if (_supply >= 8000 && _supply < 9000) {
            price = 0.16 ether;
        }
        else if (_supply >= 9000 && _supply < 9500) {
            price = 0.32 ether;
        }
        else if (_supply >= 9500 && _supply < 9900) {
            price = 0.64 ether;
        }
        else if (_supply >= 9900 && _supply < 9999) {
            price = 1.28 ether;
        }
        else {
            price = 100 ether;
        }

        return price;
    }
  
  function _getTokenToBeMinted(uint256 _totalMintedTokens)
        private
        returns (uint256)
    {
        uint256 maxIndex = maxSupply - _totalMintedTokens;
        uint256 random = _getRandomNumber(maxIndex, _totalMintedTokens);

        uint256 tokenId = tokenMatrix[random];
        if (tokenMatrix[random] == 0) {
            tokenId = random;
        }

        tokenMatrix[maxIndex - 1] == 0
            ? tokenMatrix[random] = maxIndex - 1
            : tokenMatrix[random] = tokenMatrix[maxIndex - 1];

        return tokenId;
    }

    /**
     * @dev Generates a pseudo-random number.
     */
    function _getRandomNumber(uint256 _upper, uint256 _totalMintedTokens)
        private
        view
        returns (uint256)
    {
        uint256 random = uint256(
            uint256(
                keccak256(
                    abi.encodePacked(
                        _totalMintedTokens,
                        blockhash(block.number - 1),
                        block.coinbase,
                        block.difficulty,
                        msg.sender
                    )
                )
            )
        );

        return random % _upper;
    }
  
}
function setBaseURI(string memory _newBaseURI) public onlyOwner {
  baseURI = _newBaseURI;
}

//only owner