// Contract: CryptagionLaboratory
// Target function: function buyXPwithSick(uint16 _sicktokens) external
FromOwnerEnumeration)
        uint256 lastTokenId = _allTokens[lastTokenIndex];

        _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
        _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index

        // This also deletes the contents at the last position of the array
        delete _allTokensIndex[tokenId];
        _allTokens.pop();
    }
}








//Version Mainnet

//****************   MAIN CONTRACT  ****************************
//Who? A3g1X. When? The Fall before the Fall, 2021

contract CryptagionLaboratory is ERC721Enumerable{

	event NewEthogen(uint indexed ethogenID, uint16 dtypeID, uint8 rarity);

    //consts
    uint constant DEC18 =  10 ** 18;
    uint8 constant maxBaseStat = 200;

	using Counters for Counters.Counter;
	using Address for address;

	uint upgradefee = 0.001 ether;
	uint silverpackfee = 0.002 ether;

	uint legendarypackfee = 3 ether;
	uint sickpackfee = 0.2 ether;

    uint16 sickbonusbase = 400;
    uint16 sickfeebase = 1000;
    uint16 userxpgainbase = 200;

	uint16 founderpackcap1 = 800;
    uint8 founderpackcap2 = 230;
    uint8 founderpackcap3 = 80;
    
    uint16 xpDivisor = 400;
    uint8 sickburndivisor = 10;

// Base URI
    string private _baseURIextended;
    string private _contractURI;

  	Counters.Counter private _tokenIdTracker;
 
	address payable private owner;

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }
	modifier onlyOwnerOf(uint _ethogenID) {
        require(msg.sender == ownerOf(_ethogenID));
        _;
	}

//main erc20 currency SICK
    ERC20Burnable public sicktoken;
//dtypes - types of ethogens - owner can add new types with rarity caps
	struct dtype{
  		
		uint16 raritycap1;
		uint16 raritycap2;
		uint16 raritycap3;
		uint8 raritycap4;
		uint8 raritycap5;
        uint8 baseinfect;
		uint8 basemort;
        uint8 baseresist;
        uint8 basestealth;
        uint8 classtype;
        uint8 legendarymaxcap;
	}
	
	dtype[] public dtypes;

//main token type: ethogen 
	struct Ethogen {

        uint birthday;
        uint birthblock;
        uint16 dtype;
        uint16 xp;
        uint8 rarity;
		uint8 level;
        uint8 baseinfect;
		uint8 basemort;
        uint8 baseresist;
        uint8 basestealth;

	}
	
	Ethogen[] public ethogens;

//additional stats 1:1 with Ethogen
	struct EthogenStats {

        uint8 modinfect;
		uint8 modmort;
        uint8 modresist;
        uint8 modstealth;
        uint8 boostinfect;
		uint8 boostmort;
        uint8 boostresist;
        uint8 booststealth;
        uint8 mutation;        
        uint8 trait;
        uint8 special;
	}
	
	EthogenStats[] public ethogenstats;

//user stats
	struct User {

        uint incubating1;
        uint incubating1date;
        uint incubating2;
        uint incubating2date;
        uint incubating3;
        uint incubating3date;
        uint16 xp;
		uint8 level;
	}

    mapping (address => User) public users;

//constructor  
	constructor()  ERC721('Cryptagion Ethogen', 'ETHOGEN') {  

        _baseURIextended = "https://lab.cryptagion.com/tokenmeta/getmetadata.php?tokenid=";
        _contractURI = "https://lab.cryptagion.com/tokenmeta/contractmetadata";
  
 		owner = payable(msg.sender);
        setCurrency(0x3aA22ff4781D61FD3dbb820e0e2D9533bf908d5C);
        
        //initial types
        addType(92,56,45,181,14,6);
        addType(107,16,38,132,21,8);
        addType(55,62,152,43,41,10);
        addType(74,157,63,31,35,6);
        addType(130,41,86,78,12,8);
        addType(56,77,133,87,41,6);
        addType(142,26,21,173,22,8);
        addType(72,64,98,95,33,10);

        //mint a common as #0
        mintRandom(62,1,1);
        mintRandom(5,5,5);
	}

//set and return URIs
 function setBaseURI(string memory baseURI_) external onlyOwner() {
        _baseURIextended = baseURI_;
    }
 function _baseURI() internal view virtual override returns (string memory) {
        return _baseURIextended;
    }
 function setContractURI(string memory contractURI_) external onlyOwner() {
        _contractURI = contractURI_;
    }
  function contractURI() public view returns (string memory) {
        return _contractURI;
    }

//************************* SETTERS ******************************************
//this sets contract address of Cryptagion (SICK)
	function setCurrency(address _token) public onlyOwner {
       sicktoken = ERC20Burnable(_token);
    }
//add new ethogen type
	function addType ( uint8 _baseinfect, uint8 _basemort,  uint8 _baseresist, uint8 _basestealth, uint8 _classtype,uint8 _legendarycap) public onlyOwner{

    	require (_legendarycap <=20, " Max is 20");
    	require (_basemort <=maxBaseStat && _baseinfect <=maxBaseStat && _basestealth <=maxBaseStat && _baseresist <=maxBaseStat, " Max Base Stat");
	
	    //proportion of mint caps in relation to legendary
        uint16 commoncap = uint16(_legendarycap) * 150;
	    uint16 uncommoncap = uint16(_legendarycap) * 70;
		uint16 rarecap = uint16(_legendarycap) * 20;
		uint8 epiccap = uint8(_legendarycap) * 5;
	
        dtypes.push(dtype(commoncap,uncommoncap,rarecap,epiccap,_legendarycap,_baseinfect,_basemort,_baseresist,  _basestealth, _classtype,_legendarycap));
	}
//overwrite ethogen type in case of f**k ups
	function overwriteType (uint8 _baseinfect, uint8 _basemort, uint8 _baseresist, uint8 _basestealth, uint8 _classtype,uint _overwriteindex) external onlyOwner{

       require (_basemort <=maxBaseStat && _baseinfect <=maxBaseStat && _basestealth <=maxBaseStat && _baseresist <=maxBaseStat, " Max Base Stat");

       dtypes[_overwriteindex].baseresist = _baseresist;
       dtypes[_overwriteindex].basestealth = _basestealth;
       dtypes[_overwriteindex].baseinfect = _baseinfect;
       dtypes[_overwriteindex].basemort = _basemort;
       dtypes[_overwriteindex].classtype = _classtype;
	}

//************************* THE MINT ******************************************
//mint random card with weighted rarity chances if _raritymin is 0, otherwise set the rarity
	function mintRandom(uint _randomadd,uint8 _raritymin, uint8 _raritymax) private {

        uint randNonce = 3 + _randomadd;
        uint16 randomtype;
    	uint8 randomweight;
        uint8 finalrarity;

        //200 tries to get one that's not capped out.. just prevent endless loop
        while (randNonce < _randomadd + 200){
            
    	    randomtype = uint16(uint(keccak256(abi.encodePacked(block.number, msg.sender, randNonce))) % dtypes.length );
            randNonce++;

    	    if(_raritymin == 0){
        	    
        	    randomweight = uint8(uint(keccak256(abi.encodePacked(block.number + _tokenIdTracker.current() , msg.sender, randNonce))) % 100  + 1);

                if(_raritymax == 2)
                {
                    if (randomweight < 66){finalrarity = 1;}
                    else {finalrarity = 2;}
                    
                }
                else if(_raritymax == 3)
                {
                    if (randomweight < 60){finalrarity = 1;}
                    else if (randomweight < 90){finalrarity = 2;}
                    else {finalrarity = 3;}
                }
    	    }
            else{
                finalrarity = _raritymin;
            }
    	    //particular mintcap?
    	    if(checkmintcaps(randomtype,finalrarity)){
    	             _mintEthogen(msg.sender, randomtype, finalrarity);
    	            return;
    	    }
            randNonce++;
        }
        //massive fail       
        revert("No Cap, Try again")  ;
	}

//mint new ethogen
	function _mintEthogen(address _to, uint16 _dtypeID, uint8 _rarity) private {

	    uint16 tmpfactor;

        //set base stats based on rarity
		if(_rarity == 1){tmpfactor = 1000;}
		else if(_rarity == 2){tmpfactor = 1030;}
		else if(_rarity == 3){tmpfactor = 1100;}
		else if(_rarity == 4){tmpfactor = 1180;}
		else if(_rarity == 5){tmpfactor = 1275;}
		else {revert("Bad Rarity");}

        //the real minting
        uint256 newNftTokenId = _tokenIdTracker.current();
       
        _mint(_to, newNftTokenId);
  
        //percents increase based on rarity
        uint8 finalinfect =  uint8((uint32(dtypes[_dtypeID].baseinfect) * uint32(tmpfactor))/1000);
        uint8 finalmort =  uint8((uint32(dtypes[_dtypeID].basemort) * uint32(tmpfactor))/1000);
        uint8 finalresist =  uint8((uint32(dtypes[_dtypeID].baseresist) * uint32(tmpfactor))/1000);
        uint8 finalstealth =  uint8((uint32(dtypes[_dtypeID].basestealth) * uint32(tmpfactor))/1000);
        
        //main Ethogen
        ethogens.push(Ethogen(block.timestamp,block.number,_dtypeID, 0, _rarity, 0,finalinfect,finalmort,finalresist,finalstealth));

        //random shizen
        uint8 randommutation = uint8(uint(keccak256(abi.encodePacked(block.number + _tokenIdTracker.current() + 11, msg.sender))) % 255  + 1);
        uint8 randomtrait = uint8(uint(keccak256(abi.encodePacked(block.timestamp + _tokenIdTracker.current() + 5, msg.sender))) % 255  + 1);
        uint8 randomspecial = uint8(uint(keccak256(abi.encodePacked(block.timestamp + _tokenIdTracker.current() + 3, msg.sender))) % 255  + 1);

        //EthogenStats 1:1 with Ethogens
        ethogenstats.push(EthogenStats(1,1,1,1,0,0,0,0,randommutation,randomtrait,randomspecial));

        _tokenIdTracker.increment();
		
        //decrement specific value in mintcaps mapping and raritycaps array
		if(_rarity == 1){dtypes[_dtypeID].raritycap1--;}
		if(_rarity == 2){dtypes[_dtypeID].raritycap2--;}
		if(_rarity == 3){dtypes[_dtypeID].raritycap3--;}
		if(_rarity == 4){dtypes[_dtypeID].raritycap4--;}
		if(_rarity == 5){dtypes[_dtypeID].raritycap5--;}

        //emit the event 	
		emit NewEthogen(newNftTokenId, _dtypeID, _rarity);
    }
    
//mint a specific ethogen
	function mintOwner(address _to, uint16 _dtypeID, uint8 _rarity) external onlyOwner {
    	 require (checkmintcaps(_dtypeID,_rarity), "Cap reached");
		 _mintEthogen(_to, _dtypeID, _rarity);
	}	    
    
//************************* SET FEES ******************************************
//set the upgradefee
	function setUpgradeFee(uint _fee) external onlyOwner {
		upgradefee = _fee;
	}
//set the silver fee
	function setSilverpackFee(uint _fee) external onlyOwner {
		silverpackfee = _fee;
	}
//set the legendary fee
	function setLegendarypackFee(uint _fee) external onlyOwner {
		legendarypackfee = _fee;
	}
//set the sickpack fee
	function setSickpackFee(uint _fee) external onlyOwner {
		sickpackfee = _fee;
	}	
//set the userxpgainbase
	function setUserXPgainBase(uint16 _newval) external onlyOwner {
		userxpgainbase = _newval;
	}	
//set the sickbonusbase
	function setSickBonusBase(uint16 _newval) external onlyOwner {
		sickbonusbase = _newval;
	}	
//set the sickfeebase
	function setSickFeeBase(uint16 _newval) external onlyOwner {
		sickfeebase = _newval;
	}	
//set the sickburndivisor
	function setSickBurnDivisor(uint8 _newval) external onlyOwner {
		sickburndivisor = _newval;
	}	
//set the xpDivisor
	function setXpDivisor(uint16 _newval) external onlyOwner {
		xpDivisor = _newval;
	}	

//************************* PAYABLES ******************************************
//buy a pack
	function buyCardPack(uint8 _packtype) external payable {
		require((msg.value == silverpackfee || msg.value == 10 * silverpackfee || msg.value == 100 * silverpackfee || msg.value == sickpackfee || msg.value == legendarypackfee ) ," Fee must match one listed");

        uint16 sickgain;
        uint16 xpgain;
        
        if(msg.value == silverpackfee && _packtype == 1){
            //first uncom
    		mintRandom(13,2,2);
    		mintRandom(17,1,1);

    		if(founderpackcap1 > 0){
    		    
    		    mintRandom(3,1,1);
    		    sickgain = sickbonusbase * 2;
                xpgain = userxpgainbase * 2;
    		    
    		    founderpackcap1 --;
    		    
    		}
            else{
    		    sickgain = sickbonusbase;
        	    xpgain = userxpgainbase;
            }

        }
        else if(msg.value == silverpackfee * 10 && _packtype == 2){
            //first rare
    		mintRandom(7,3,3);
    		mintRandom(4,2,2);

            if(founderpackcap2 > 0){
                
                mintRandom(13,0,2);
    		    sickgain = sickbonusbase * 5;
        	    xpgain = userxpgainbase * 3;

    		    founderpackcap2 --;
    		}
            else{
                sickgain = sickbonusbase * 2;
                xpgain = userxpgainbase * 2;
            }

        }
        else if(msg.value == silverpackfee * 100 && _packtype == 3){
        
            //first epic
    		mintRandom(24,4,4);
    		mintRandom(12,0,3);

            if(founderpackcap3 > 0){
                
    		    mintRandom(23,0,2);
    		    sickgain = sickbonusbase * 10;
                xpgain = userxpgainbase * 6;
 
    		    founderpackcap3 --;
    		}
            else{
    		    sickgain = sickbonusbase * 5;
                xpgain = userxpgainbase * 4;
            }
        }
        else if(msg.value == sickpackfee && _packtype == 200){
        
    		mintRandom(9,0,2);
    		sickgain = sickbonusbase * 10;
            xpgain = userxpgainbase * 2;
        }
        else if(msg.value == legendarypackfee && _packtype == 250){
        
    		mintRandom(7,5,5);
      		mintRandom(11,4,4);
    		mintRandom(5,3,3);

    		sickgain = sickbonusbase * 20;
            xpgain = userxpgainbase * 10;
        }
        else if(msg.value == legendarypackfee && _packtype == 253){
        
    		mintRandom(13,5,5);
    		sickgain = sickbonusbase * 10;
            xpgain = userxpgainbase * 5;
        }
        else{
             revert("Wrong Amount ");
        }
        //give Sick, burn Sick, gain XP
  		sicktoken.transfer(msg.sender,sickgain * DEC18);
		sicktoken.burn((sickgain * DEC18 ) / sickburndivisor);
    	useraddxp(uint16(xpgain));
    }