contract CursedVegiemon is ERC721Enumerable, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;
    
    bool public isUpgradeActive = false;
    bool public isMintActive = false;
    bool public isSaleActive = false;
    
    uint256 public itemPrice;
    
    uint256 public _reserved_mint = 2000;
    uint256 public _reserved_sale = 2000;
    uint256 public _reserved_giveaway = 50;
    
    string private baseURI;
    string private upgradeURI;
    
    // withdraw addresses
    address t1 = 0xc40C2Db69DF9A7480d7d55E13c8DB55b63805DFd;
    address t2 = 0xeFcc0f7892bFe2F70011F422DA65AA431Fe0Be50;
    address burnAddress = 0x000000000000000000000000000000000000dEaD;

    //vegiemon
    address public addressVegiemon;
    Vegiemon public ctxVegiemon;
    
    mapping (uint256 => bool) public VegiemonTokens;
    mapping (uint256 => bool) public CursedVegiemonUpgrade;
    
    struct UnclaimedItems {
      uint unclaimedItemCount;
      uint256[] unclaimedItemIds;
    }
  
    constructor () ERC721("Cursed Vegiemon", "CVGM") 
    {
        baseURI = "https://vegiemon.me/cursedvegiemon/tokens/";
        upgradeURI = "https://vegiemon.me/cursedvegiemon_upgrade/tokens/";
        itemPrice = 50000000000000000; // 0.05 ETH
        setupVegiemon(0x2FB5B875a85713cBA312c097D16838C242caDaec);
        giveAway(t1, 10);
        giveAway(t2, 10);
    }

	function getItemPrice() public view returns (uint256) 
	{
		return itemPrice;
	}
	
	function getReservedMint() public view returns (uint256) 
	{
		return _reserved_mint;
	}
	
	function getReservedSale() public view returns (uint256) 
	{
		return _reserved_sale;
	}
	
    function isVegiemonTokenContains(uint256 id) view public returns (bool){
        return VegiemonTokens[id];
    }
	
    function walletOfOwner(address _owner) public view returns(uint256[] memory) {
        uint256 tokenCount = balanceOf(_owner);

        uint256[] memory tokensId = new uint256[](tokenCount);
        for(uint256 i; i < tokenCount; i++) {
            tokensId[i] = tokenOfOwnerByIndex(_owner, i);
        }
        return tokensId;
    }
    
    function vegiemonWalletOfOwner(address _owner) public view returns (uint256[] memory)
    {
        require(addressVegiemon != address(0),                                  "vegiemon contract address need be set");
        return ctxVegiemon.walletOfOwner(_owner);
    }
    
    function getNumberOfVegiemonToken(address _owner) public view returns (UnclaimedItems memory)
    {
        uint256 tc = ctxVegiemon.balanceOf(_owner);
        UnclaimedItems memory result = UnclaimedItems(0, new uint256[](tc));

        for(uint256 i; i < tc; i++) 
        {
            uint256 tokenid = ctxVegiemon.tokenOfOwnerByIndex(_owner, i);
            if(VegiemonTokens[tokenid] == false)
            {
                result.unclaimedItemIds[result.unclaimedItemCount] = tokenid;
                result.unclaimedItemCount += 1;
            }
        }

        return result;
    }

    /*
    * Mint
    */
    function mint(uint numberOfMints) public
    {
        address from = msg.sender;
        require(addressVegiemon != address(0),                                  "vegiemon contract address need be set");
        require(isMintActive,                                                   "cursed vegiemon mint hasn't started yet");
        UnclaimedItems memory ucs = getNumberOfVegiemonToken(from);
        require(ucs.unclaimedItemCount >= numberOfMints * 5,                    "vegiemon tokens not enough to mint!");
        require(_reserved_mint - numberOfMints >= 0,                            "Maximum amount already minted.");
        require(numberOfMints <= 20,                                            "You cant mint more than 20 at a time.");
        require(numberOfMints > 0,                                              "gas fee for nothing, vegiemon don't accept this request."); //(づ￣ ³￣)づ

        uint index = 0;
        for(uint i = 0; i < numberOfMints; i++)
        {
            index = i * 5;
            VegiemonTokens[ucs.unclaimedItemIds[index ]] = true;
            VegiemonTokens[ucs.unclaimedItemIds[index + 1]] = true;
            VegiemonTokens[ucs.unclaimedItemIds[index + 2]] = true;
            VegiemonTokens[ucs.unclaimedItemIds[index + 3]] = true;
            VegiemonTokens[ucs.unclaimedItemIds[index + 4]] = true;

            _safeMint(from, _tokenIds.current());
            _tokenIds.increment();
        }

        _reserved_mint = _reserved_mint - numberOfMints;
    }
    
    function checkVegiemonIds(address from,uint256[] memory ids) public view returns (bool)
    {
        for(uint256 i; i < ids.length; i++) 
        {
            uint256 tokenid = ids[i];
            if(VegiemonTokens[tokenid] == true || ctxVegiemon.ownerOf(tokenid) != from)
            {
                return false;
            }
        }

        return true;
    }
    
    /*
    * Mint
    */
    function mint2(uint256[] memory ids) public
    {
        address from = msg.sender;
        require(addressVegiemon != address(0),                                  "vegiemon contract address need be set");
        require(isMintActive,                                                   "cursed vegiemon mint hasn't started yet");
        uint numberOfMints = ids.length / 5;
        require(_reserved_mint - numberOfMints >= 0,                            "Maximum amount already minted.");
        require(numberOfMints <= 20,                                            "You cant mint more than 20 at a time.");
        require(numberOfMints > 0,                                              "gas fee for nothing, vegiemon don't accept this request."); //(づ￣ ³￣)づ
        require(checkVegiemonIds(from, ids),                                    "Vegiemon Ids is not token owner or someone used");
        
        uint256 index = 0;
        uint256 count = 0;
        for(uint i = 0; i < numberOfMints; i++)
        {
            index = i * 5;
            uint256 v1 = ids[index];
            uint256 v2 = ids[index + 1];
            uint256 v3 = ids[index + 2];
            uint256 v4 = ids[index + 3];
            uint256 v5 = ids[index + 4];
            if(ctxVegiemon.ownerOf(v1) == from && ctxVegiemon.ownerOf(v2) == from && ctxVegiemon.ownerOf(v3) == from && ctxVegiemon.ownerOf(v4) == from && ctxVegiemon.ownerOf(v5) == from
            && VegiemonTokens[v1] == false && VegiemonTokens[v2] == false && VegiemonTokens[v3] == false && VegiemonTokens[v4] == false && VegiemonTokens[v5] == false)
            {
                VegiemonTokens[v1] = true;
                VegiemonTokens[v2] = true;
                VegiemonTokens[v3] = true;
                VegiemonTokens[v4] = true;
                VegiemonTokens[v5] = true;
                _safeMint(from, _tokenIds.current());
                _tokenIds.increment();
                count += 1;
            }
        }

        _reserved_mint = _reserved_mint - count;
    }
    
    /*
    * sale
    */
    function sale(uint numberOfMints)
        public payable
    {
        require(isSaleActive,                                               "cursed vegiemon sale hasn't started yet");
        require(_reserved_sale - numberOfMints >= 0,                        "Maximum amount already minted.");
        require(msg.value >= itemPrice * numberOfMints,                     "insufficient ETH");
        require(numberOfMints <= 10,                                        "You cant mint more than 10 at a time.");
        require(numberOfMints > 0,                                          "gas fee for nothing, vegiemon don't accept this request."); //(づ￣ ³￣)づ
        
        for(uint i = 0; i < numberOfMints; i++)
        {
            _safeMint(msg.sender, _tokenIds.current());
            _tokenIds.increment();
        }
        
        _reserved_sale = _reserved_sale - numberOfMints;
    }
    
    /*
    * giveAway
    */
    function giveAway(address _to, uint256 _amount) public onlyOwner
    {
        require(_amount <= _reserved_sale,          "exceeds reserved sale supply");
        require(_amount <= _reserved_giveaway,      "exceeds reserved giveaway supply");
        require(_amount > 0,                        "giveAway nothing.");
        
        for(uint256 i; i < _amount; i++)
        {
            uint256 newItemId = _tokenIds.current();
            _safeMint(_to, newItemId);
            _tokenIds.increment();
        }
        _reserved_giveaway = _reserved_giveaway - _amount;
        _reserved_sale = _reserved_sale - _amount;
    }
    
    /*
    * upgrade
    */
    function upgrade(uint256 v1, uint256 v2, uint256 v3, uint256 cv1) public
    {
        address from = msg.sender;
        require(isUpgradeActive,                                               "cursed vegiemon upgrade hasn't started yet");
        require(addressVegiemon != address(0),                                  "vegiemon contract address need be set");
        require(ctxVegiemon.isApprovedForAll(from, address(this)),              "vegiemon contract address need be Approved");
        require(ctxVegiemon.ownerOf(v1) == from,                                "vegiemon: use of token1 that is not own");
        require(ctxVegiemon.ownerOf(v2) == from,                                "vegiemon: use of token2 that is not own");
        require(ctxVegiemon.ownerOf(v3) == from,                                "vegiemon: use of token3 that is not own");
        require(ownerOf(cv1) == from,                                           "cursed vegiemon: use of token3 that is not own");
        require(v1 != v2 && v1 != v3 && v2 != v3,                               "vegiemon ids need be unique");
        require(CursedVegiemonUpgrade[cv1] == false,                            "cursed vegiemon must be the one that hasn't been upgraded.");
        
        burnVegiemon(from, v1);
        burnVegiemon(from, v2);
        burnVegiemon(from, v3);
        
        upgradeCursedVegiemon(cv1, true);
    }
    
    function tokenURI(uint256 tokenId) public view override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

        string memory uri = CursedVegiemonUpgrade[tokenId] ? upgradeURI : _baseURI();
        return bytes(uri).length > 0 ? string(abi.encodePacked(uri, toString(tokenId))) : "";
    }

    function _baseURI() internal view override returns (string memory) 
    {
        return baseURI;
    }
    
    /**
     * @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);
    }

    /*Owner*/
    function setupVegiemon(address vegiemon) public payable onlyOwner
    {
        addressVegiemon = vegiemon;
        ctxVegiemon = Vegiemon(payable(vegiemon));
    }
        
    function burnVegiemon(address from, uint256 vegiemon) public onlyOwner
    {
        ctxVegiemon.transferFrom(from, burnAddress, vegiemon);
    }

    function setVegiemonToken(uint256 id, bool val) public onlyOwner {
        VegiemonTokens[id] = val;
    }
    
    function upgradeCursedVegiemon(uint256 id, bool val) public onlyOwner {
        CursedVegiemonUpgrade[id] = val;
    }

    function setMintActive(bool val) public onlyOwner 
    {
        isMintActive = val;
    }
    
    function setSaleActive(bool val) public onlyOwner 
    {
        isSaleActive = val;
    }
    
    function setUpgradeActive(bool val) public onlyOwner 
    {
        isUpgradeActive = val;
    }
  
    function setBaseURI(string memory uri) public onlyOwner 
    {
        baseURI = uri;
    }
    
    function setUpgradeURI(string memory uri) public onlyOwner 
    {
        upgradeURI = uri;
    }

    function setItemPrice(uint256 _price) public onlyOwner 
    {
		itemPrice = _price;
	}
    
    function cashOut() public onlyOwner 
    {
        uint256 _each = address(this).balance / 2;
        require(payable(t1).send(_each));
        require(payable(t2).send(_each));
    }
}
function setupVegiemon(address vegiemon) public payable onlyOwner
{
    addressVegiemon = vegiemon;
    ctxVegiemon = Vegiemon(payable(vegiemon));
}

/*Owner*/