contract ARTGLIXXX is ERC165, IERC2981Royalties, ERC721, Ownable {

    using Strings for uint256;

    uint public constant TOTAL_GLICPIXXX = 11500;
    bool public mintOpen;

    mapping (uint256 => uint256) public creationDates;
    mapping (uint256 => address) public creators;

    string public GENERATOR_ADDRESS;

    uint public BASE_MINT_PRICE = 100 ether; // 100 GLIX PER GENERATION

    address private GLICPIXXXVER002_ADDRESS = 0x1C60841b70821dcA733c9B1a26dBe1a33338bD43;
    address private GLIXTOKEN_ADDRESS = 0x4e09d18baa1dA0b396d1A48803956FAc01c28E88;

    IERC721 GLICPIXXXVER002 = IERC721(GLICPIXXXVER002_ADDRESS);
    IGLIX GLIXTOKEN = IGLIX(GLIXTOKEN_ADDRESS);

    mapping(address => uint256) public balanceELDEST;
    mapping(uint256 => uint256) public ARTGLIXXX_NEXT_GEN;

    mapping(address => uint256) public GLIXREWARDS;
	mapping(address => uint256) public GLIX_LAST_UPDATE;

    uint public DAILY_YIELD_RATE = 10 ether; // 10 $GLIX PER DAY
    uint public YIELD_END = block.timestamp + 315360000; // yield will go on for 10 years

    event RewardPaid(address indexed user, uint256 reward);
    event Mint(address indexed user, uint256 tokenId, uint256 Generation);

    // string public script;

    struct ProjectInfo {
        uint projectId;
        string name;
        string description;
        string license;
        string artist;
        string script;
        address royaltyReceiver;
        uint royaltyAmount;
    }

    ProjectInfo public projectInfo;

    constructor() ERC721("ARTGLIXXX GENESIS","ARTGLIXXX")  {

        setBaseURI("https://server.artglixxx.io/api/project/0/token/");
        setGeneratorAddress("https://server.artglixxx.io/generate/project/0/token/");

        projectInfo = ProjectInfo(
            0,
            "ARTGLIXXX GENESIS",
            "ARTGLIXXX GENESIS is the first badass on-chain remix collection of GLICPIXXX. Holding mouse on canvas and moving on x-axis changes the frame rate.",
            "Your ARTGLIXXX, your call. An ARTGLIXXX comes with full non-commercial/commercial rights.",
            "Berk aka Princess Camel aka Guerilla Pimp Minion God Bastard",
            "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))]}random_exp(e,a,n){return floor(map(Math.exp(map(this.random_between(0,999),0,1e3,0,n)),Math.exp(0),Math.exp(n),e,a+1))}}function hue2rgb(e,a,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?e+6*(a-e)*n:n<.5?a:n<2/3?e+(a-e)*(2/3-n)*6:e}function changeHue(e,a,n,r){var t=rgbToHSL(e,a,n);return t.h+=r,t.h>360?t.h-=360:t.h<0&&(t.h+=360),hslToRGB(t)}function rgbToHSL(e,a,n){e/=255,a/=255,n/=255;var r=Math.max(e,a,n),t=Math.min(e,a,n),o=r-t,i=(r+t)/2;return{h:0==o?0:r==e?(a-n)/o%6*60:r==a?60*((n-e)/o+2):60*((e-a)/o+4),s:0==o?0:o/(1-Math.abs(2*i-1)),l:i}}function hslToRGB(e){var a,n,r,t=e.h,o=e.s,i=e.l,m=(1-Math.abs(2*i-1))*o,s=m*(1-Math.abs(t/60%2-1)),d=i-m/2;return t<60?(a=m,n=s,r=0):t<120?(a=s,n=m,r=0):t<180?(a=0,n=m,r=s):t<240?(a=0,n=s,r=m):t<300?(a=s,n=0,r=m):(a=m,n=0,r=s),[a=normalize_rgb_value(a,d),n=normalize_rgb_value(n,d),r=normalize_rgb_value(r,d)]}function normalize_rgb_value(e,a){return(e=Math.floor(255*(e+a)))<0&&(e=0),e}function hueShift(e,a){e.loadPixels();for(var n=0;n<e.pixels.length;n+=4){let r=changeHue(e.pixels[n],e.pixels[n+1],e.pixels[n+2],a);e.pixels[n]=r[0],e.pixels[n+1]=r[1],e.pixels[n+2]=r[2]}e.updatePixels()}function componentToHex(e){var a=e.toString(16);return 1==a.length?'0'+a:a}const rgbtohex=(e,a,n)=>'#'+componentToHex(e)+componentToHex(a)+componentToHex(n);let tokenData=window.tokenHash,seed=parseInt(tokenData.slice(0,16),16),rng=new Random(seed);var SIZE;let glicpixxxOrigin=window.glicpixxxOrigin,filenames=window.imagesList.split(',');var images=[];let params={},oncee=!1;function preload(){for(let e in filenames)images.push(loadImage(filenames[e]))}function setup(){params.GRIDS=rng.random_exp(1,16,2),params.DRAW_MODE=rng.random_exp(1,6,2),params.IMAGE_FILTER=rng.random_exp(0,7,2),5!=params.IMAGE_FILTER&&6!=params.IMAGE_FILTER||(params.HUE_DEGREE=rng.random_int(1,360)),params.ROTATE_MODE=rng.random_exp(0,3,1.5),1==params.ROTATE_MODE?params.ROTATE_N=[...new Set(Array.apply(0,new Array(rng.random_int(1,4))).map(e=>rng.random_int(1,45)))]:2!=params.ROTATE_MODE&&3!=params.ROTATE_MODE||(params.ROTATE_N=rng.random_int(1,45)),params.bgcolor=rgbtohex(rng.random_int(0,255),rng.random_int(0,255),rng.random_int(0,255)),SIZE=Math.min(window.innerHeight,window.innerWidth),createCanvas(SIZE,SIZE),background(params.bgcolor)}function draw(){noSmooth(),mouseIsPressed?frameRate(map(mouseX,0,SIZE,5,60,!0)):frameRate(60);let e=width/params.GRIDS;angleMode(DEGREES),rot=0;for(let a=0;a<params.GRIDS;a++)for(let n=0;n<params.GRIDS;n++){let r=rng.random_choice(images);rotateMe(params.ROTATE_MODE,params.ROTATE_N),applyFilter(params.IMAGE_FILTER,r),DRAW_MODE(params.DRAW_MODE,r,e,n,a)}}function DRAW_MODE(e,a,n,r,t){switch(e){case 1:imageMode(CENTER),image(a,n*r+n/2,n*t+n/2,rng.random_between(0,n),rng.random_between(0,n));break;case 2:imageMode(CORNER),image(a,n*r,n*t,rng.random_between(0,n),rng.random_between(0,n));break;case 3:imageMode(CORNERS),image(a,n*r+rng.random_between(0,n),n*t+rng.random_between(0,n),n*r+rng.random_between(0,n),n*t+rng.random_between(0,n));break;case 4:imageMode(CENTER),image(a,n*r+rng.random_between(0,n),n*t+rng.random_between(0,n),n*r+rng.random_between(0,n),n*t+rng.random_between(0,n));break;case 5:imageMode(CORNER),image(a,n*r+rng.random_between(0,n),n*t+rng.random_between(0,n),n*r+rng.random_between(0,n),n*t+rng.random_between(0,n));break;case 6:imageMode(CORNERS),image(a,rng.random_between(0,SIZE),rng.random_between(0,SIZE),rng.random_between(0,SIZE),rng.random_between(0,SIZE))}}function applyFilter(e,a,n){switch(e){case 1:0==oncee&&(a.filter(INVERT),oncee=!0);break;case 2:a.filter(INVERT);break;case 3:a.filter(POSTERIZE,4);break;case 4:a.filter(POSTERIZE,2),a.filter(INVERT);break;case 5:0==oncee&&(hueShift(a,params.HUE_DEGREE),oncee=!0);break;case 6:hueShift(a,params.HUE_DEGREE);break;case 7:a.filter(GRAY)}}function rotateMe(e,a){switch(e){case 1:rotate(rng.random_choice(a));break;case 2:rotate(rng.random_between(1,a));break;case 3:rotate(a)}}",
            0xe49381184A49CD2A48e4b09a979524e672Fdd10E, // glicpixyz.eth
            500 // %5 royalty
        );
    
    }

    /// @inheritdoc	ERC165
    function supportsInterface(bytes4 interfaceId)
        public
        view
        virtual
        override
        returns (bool)
    {
        return
            interfaceId == type(IERC2981Royalties).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    /// @inheritdoc	IERC2981Royalties
    function royaltyInfo(uint256, uint256 value)
        external
        view
        override
        returns (address receiver, uint256 royaltyAmount)
    {
        receiver = projectInfo.royaltyReceiver;
        royaltyAmount = (value * projectInfo.royaltyAmount) / 10000;
    }

    /**
     * @dev Returns the array of token IDs of an address.
     */
    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;
        }
    }

    /**
     * @dev Unique hash for token to generate the deterministic artwork.
     */
    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)));
    }
    
    /**
     * @dev Url to view generative art
     */
    function generatorAddress(uint256 tokenId) public view returns (string memory) {
        require(_exists(tokenId), "DOES NOT EXIST");
        return string(abi.encodePacked(GENERATOR_ADDRESS, tokenId.toString()));
    }

    /**
     * @dev Calculates the amount of $GLIX to be burned to mint an ARTGLIXXX.
     */
    function calculateMint(uint _id) public view returns(uint) {
        return ARTGLIXXX_NEXT_GEN[_id % TOTAL_GLICPIXXX] * BASE_MINT_PRICE;
    }

    /**
     * @dev Minting function
     */
    function generateARTGLIXXX(address _to, uint _glicpixxxId) public {
        require(mintOpen == true, "Minting isn't open");
        require(GLICPIXXXVER002.ownerOf(_glicpixxxId) == msg.sender, "Not owner of GLICPIXXX");
        uint gen = ARTGLIXXX_NEXT_GEN[_glicpixxxId];
        if (gen > 0) {
            GLIXTOKEN.burn(msg.sender, gen * BASE_MINT_PRICE);       
        }
        uint mintIndex = _glicpixxxId + (TOTAL_GLICPIXXX * gen);
        _safeMint(_to, mintIndex);
        emit Mint(_to, mintIndex, gen);
        creationDates[mintIndex] = block.number;
        creators[mintIndex] = msg.sender;
        if (gen == 0){
            updateGlixRewardOnMint(_to);
            balanceELDEST[_to]++;
        }
        ARTGLIXXX_NEXT_GEN[_glicpixxxId]++;
    }

    function min(uint256 a, uint256 b) internal pure returns (uint256) {
		return a < b ? a : b;
	}

    function exists(uint256 tokenId) external view returns (bool) {
        return _exists(tokenId);
    }

    /**
     * @dev As the function name says.
     */
    function getTokenGeneration(uint256 tokenId) external view returns (uint256) {
        require(_exists(tokenId));
        return tokenId / TOTAL_GLICPIXXX;
    }

    /**
     * @dev Returns the amount of $GLIX address can claim at the moment.
     */
    function getTotalClaimable(address _user) external view returns (uint256) {
        uint256 time = min(block.timestamp, YIELD_END);
		uint256 timerUser = GLIX_LAST_UPDATE[_user];
		if (timerUser > 0)
			return GLIXREWARDS[_user] + (balanceELDEST[_user] * DAILY_YIELD_RATE * (time - timerUser) / 86400);
        else
            return 0;
    }

	// updated_amount = (balanceELDEST(user) * base_rate * delta / 86400) + amount * initial rate
    /**
     * @dev Called inside mint function
     */
	function updateGlixRewardOnMint(address _user) internal {
		uint256 time = min(block.timestamp, YIELD_END);
		uint256 timerUser = GLIX_LAST_UPDATE[_user];
		if (timerUser > 0)
			GLIXREWARDS[_user] += (balanceELDEST[_user] * DAILY_YIELD_RATE * (time - timerUser) / 86400);
        if (timerUser != YIELD_END)
		    GLIX_LAST_UPDATE[_user] = time;
	}

	/**
     * @dev Called on transfers
     */
	function updateGlixReward(address _from, address _to, uint256 _tokenId) internal {
		if (_tokenId < TOTAL_GLICPIXXX) {
			uint256 time = min(block.timestamp, YIELD_END);
			uint256 timerFrom = GLIX_LAST_UPDATE[_from];
			if (timerFrom > 0)
				GLIXREWARDS[_from] += (balanceELDEST[_from] * DAILY_YIELD_RATE * (time - timerFrom) / 86400);
			if (timerFrom != YIELD_END)
				GLIX_LAST_UPDATE[_from] = time;
			if (_to != address(0)) {
				uint256 timerTo = GLIX_LAST_UPDATE[_to];
				if (timerTo > 0)
					GLIXREWARDS[_to] += (balanceELDEST[_to] * DAILY_YIELD_RATE * (time - timerTo) / 86400);
				if (timerTo != YIELD_END)
					GLIX_LAST_UPDATE[_to] = time;
			}
		}
	}

    /**
     * @dev Calculates the current claimable $GLIX and mints to the address
     */
    function getReward() public {
        updateGlixReward(msg.sender, address(0), 0);
		uint256 reward = GLIXREWARDS[msg.sender];
		if (reward > 0) {
			GLIXREWARDS[msg.sender] = 0;
			GLIXTOKEN.mint(msg.sender, reward);
			emit RewardPaid(msg.sender, reward);
		}
	}

    function transferFrom(address from, address to, uint256 tokenId) public override {
		updateGlixReward(from, to, tokenId);
		if (tokenId < TOTAL_GLICPIXXX)
		{
			balanceELDEST[from]--;
			balanceELDEST[to]++;
		}
		ERC721.transferFrom(from, to, tokenId);
	}

    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public override {
		updateGlixReward(from, to, tokenId);
		if (tokenId < TOTAL_GLICPIXXX)
		{
			balanceELDEST[from]--;
			balanceELDEST[to]++;
		}
		ERC721.safeTransferFrom(from, to, tokenId, _data);
	}

    // ONLYOWNER FUNCTIONS
        
    /**
     * @dev Sets URL to view the token art.
     */
    function setGeneratorAddress(string memory _address) public onlyOwner {
        GENERATOR_ADDRESS = _address;
    }

    /**
     * @dev Set base metadata URI.
     */
    function setBaseURI(string memory baseURI) public onlyOwner {
        _setBaseURI(baseURI);
    }
    
    /**
     * @dev Pause/resume NFT mints.
     */
    function toggleMint() public onlyOwner {
        mintOpen = !mintOpen;
    }

    function changeProjectName(string memory _name) public onlyOwner {
        projectInfo.name = _name;
    }

    function changeProjectDescription(string memory _description) public onlyOwner {
        projectInfo.description = _description;
    }
    
    function changeProjectArtistName(string memory _artist) public onlyOwner {
        projectInfo.artist = _artist;
    }

    function changeProjectLicense(string memory _license) public onlyOwner {
        projectInfo.license = _license;
    }
    
    function changeProjectRoyaltyReceiver(address _royaltyReceiver) public onlyOwner {
        projectInfo.royaltyReceiver = _royaltyReceiver;
    }

    function changeProjectRoyaltyAmount(uint _royaltyAmount) public onlyOwner {
        require (_royaltyAmount < 2500);
        projectInfo.royaltyAmount = _royaltyAmount;
    }

    function changeProjectScript(string memory _script) public onlyOwner {
        projectInfo.script = _script;
    }
    
}
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;
    }
}

/**
 * @dev Returns the array of token IDs of an address.
 */