contract TwentyOneMillion is ERC1155Supply, Ownable {

    struct PathRange {
        string path;
        uint256 rangeStart;
        uint256 rangeEnd;
        string extension;
    }

    PathRange[] private ranges;

    struct RoyaltyInfo {
        address recipient;
        uint24 amount;
    }

    RoyaltyInfo private royalties;  
    uint256 public minimumPrice; 

    constructor() ERC1155("") {
         royalties = RoyaltyInfo(msg.sender, uint24(250));    
         minimumPrice = 10**17; // 0.1 ether     
    }

    function setURI(string memory newuri) public onlyOwner {
        _setURI(newuri);
    }

    function changeRangeAtIndex(uint256 index, string calldata path, uint256 rangeStart, uint256 rangeEnd, string calldata extension) public onlyOwner {
        require(rangeStart <= rangeEnd, "Invalid path range (start > end)");
        require(bytes(extension).length > 0 && bytes(path).length > 0, "Invalid extension or path.");
        require(index <= ranges.length, "Invalid path index (doesn't exist)");

        ranges[index].path = path;
        ranges[index].rangeStart = rangeStart;
        ranges[index].rangeEnd = rangeEnd;
        ranges[index].extension = extension;
    }

    function addRanges(string[] calldata paths, uint256[] calldata rangeStarts, uint256[] calldata rangeEnds, string[] calldata extensions) public onlyOwner {        
        require (paths.length == rangeStarts.length && paths.length == rangeEnds.length && paths.length == extensions.length, "Quantity of elements don't match.");

        for (uint256 i = 0; i < paths.length; i++) {
            require(bytes(extensions[i]).length > 0 && bytes(paths[i]).length > 0, "Invalid extension or path.");            
            require(rangeStarts[i] <= rangeEnds[i], "Invalid path range (start > end).");

            ranges.push(PathRange(paths[i], rangeStarts[i], rangeEnds[i], extensions[i]));    
        }        
    }

    function uri(uint256 _tokenId) override public view returns (string memory) {

        for (uint256 i = 0; i < ranges.length; ++i) {       
            if (_tokenId >= ranges[i].rangeStart && _tokenId <= ranges[i].rangeEnd) {
                return string(  
                    abi.encodePacked(ranges[i].path, Strings.toString(_tokenId), ranges[i].extension)
                );                
            }             
        }

        return "";
    }

    function mint(address account, uint256 id, uint256 amount, bytes memory data) public onlyOwner {
        require(amount == 1, "Max mint: 1");
        require(exists(id) == false, "Token already minted");
        
        _mint(account, id, amount, data);
    }

    function buyerMint(address account, uint256 year, uint256 month, uint256 day) public payable {        
        require(msg.value >= minimumPrice, "Minimum price not met");
        require(year >= 2009 && year < 2150, "Year not correct");
        require(month >= 1 && month <= 12, "Month not correct");
        require(day >= 1 && day <= 31, "Day not correct");

        uint256 id = year * 10000 + month * 100 + day;
        require(exists(id) == false, "Token already minted");
        _mint(account, id, 1, new bytes(0x00));
    }

    function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) public onlyOwner {           
        require(ids.length > 0, "Invalid quantity of ids passed (0).");

        for (uint256 i = 0; i < ids.length; ++i) {            
            require(exists(ids[i]) == false, "Token already minted");
            require(amounts[i] == 1, "Max mint: 1");
        }

        _mintBatch(to, ids, amounts, data);
    }

    function canMint(uint256[] memory ids) public view returns (bool) {
        for (uint256 i = 0; i < ids.length; ++i) {  
            if (exists(ids[i])) {
                return false;
            }
        }
        
        return true;        
    }

    function getRanges() public view returns (PathRange[] memory) {
        return ranges;
    }

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

    /// @dev Sets token royalties
    /// @param recipient recipient of the royalties
    /// @param value percentage (using 2 decimals: 10000 = 100%, 0 = 0)
    function setRoyalties(address recipient, uint256 value) public onlyOwner {
        require(value <= 10000, 'ERC2981Royalties: Too high');
        royalties = RoyaltyInfo(recipient, uint24(value));
    }

    /// @notice Called with the sale price to determine how much royalty is owed and to whom.
    /// @param salePrice - sale price of the asset specified by id
    /// @return receiver - address of who should be sent the royalty payment
    /// @return royaltyAmount - the royalty payment amount for the sale price
    function royaltyInfo(uint256, uint256 salePrice) external view returns (address receiver, uint256 royaltyAmount) {        
        receiver = royalties.recipient;
        royaltyAmount = (salePrice * royalties.amount) / 10000;        
    }

    function setMinimumPrice(uint256 price) public onlyOwner {
        minimumPrice = price;
    }

    function withdraw() public onlyOwner {
        payable(owner()).transfer(address(this).balance);            
    }
}
// ids should be the dates we have in the format yyyyMMdd: 20090902 etc...
function setRoyalties(address recipient, uint256 value) public onlyOwner {
    require(value <= 10000, 'ERC2981Royalties: Too high');
    royalties = RoyaltyInfo(recipient, uint24(value));
}

/// @dev Sets token royalties
/// @param recipient recipient of the royalties
/// @param value percentage (using 2 decimals: 10000 = 100%, 0 = 0)