contract Decurly is ERC165, Context, Ownable {
    using ECDSA for bytes32;
    using Strings for uint256;

    /*
     *     bytes4(keccak256('name()')) == 0x06fdde03
     *     bytes4(keccak256('symbol()')) == 0x95d89b41
     *     bytes4(keccak256('tokenURI(uint256)')) == 0xc87b56dd
     */
    bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x5b5e139f;
    /*
     *     bytes4(keccak256('ownerOf(uint256)')) == 0x6352211e
     */
    bytes4 private constant _INTERFACE_ID_ERC721 = 0x6352211e;

    mapping(address => bool) private _minters;

    struct OwnerInfo {
        address owner;
        uint32 ttl;
        uint32 timestamp;
        bool DNSSEC;
        string domain;
    }

    mapping(uint256 => OwnerInfo) private _tokenOwners;
    mapping(address => uint256) private _defaultDomain;

    string private _name;
    string private _symbol;
    string private _baseURI;
    uint8 private _maxTrustScore;

    uint256 private _chainId;
    

    event Transfer(
        address indexed from,
        address indexed to,
        uint256 indexed tokenId
    );

    constructor(
        string memory name_,
        string memory symbol_,
        string memory baseURI_
    ) {
        _name = name_;
        _symbol = symbol_;
        _baseURI = baseURI_;
        addMinter(_msgSender());
        _maxTrustScore = 95;
        _registerInterface(_INTERFACE_ID_ERC721_METADATA);
        _registerInterface(_INTERFACE_ID_ERC721);
        fetchChainId();
    }

    /*
     * Gets the owner of a tokenId
     */
    function ownerOf(uint256 tokenId) public view virtual returns (address) {
        return _tokenOwners[tokenId].owner;
    }

    function _mint(
        string memory domain,
        address to,
        uint32 ttl,
        uint256 id,
        bool DNSSEC,
        bool setAsDefault,
        uint32 timestamp
    ) private {
        OwnerInfo memory ownerInfo = _tokenOwners[id];
        address tOwner = ownerInfo.owner;
        if (tOwner != to) {
            ownerInfo.domain = domain;
            ownerInfo.owner = to;
            if (_defaultDomain[tOwner] == id) {
                _defaultDomain[tOwner] = 0;
            }
            ownerInfo.timestamp = timestamp;
            emit Transfer(tOwner, to, id);
        }
        if (setAsDefault && _defaultDomain[to] != id) {
            _defaultDomain[to] = id;
        }
        ownerInfo.DNSSEC = DNSSEC;
        ownerInfo.ttl = ttl;
        _tokenOwners[id] = ownerInfo;
    }

    /*
     * Minting should only be possible:
     *    - Signer has minter role.
     *    - parameters are signed correctly
     *    - Ticket was not already used.
     *    - ttl is valid
     * Idea:
     * We could save some space by converting domain to a base38 encoding
     * Saving 2-Bit per domain-Character (Allowed domain Characters are [a-z] (lowercase is enough) [0-9] and [.-])
     * on a theoretical length of 256 Byte we would save two SSTORE operations
     */
    function mint(
        string memory domain,
        address to,
        uint32 ttl,
        uint256 id,
        bytes32 r,
        bytes32 s,
        uint8 v,
        bool DNSSEC,
        bool setAsDefault
    ) external payable {
        require(ttl >= block.timestamp, "Error:Old ticket");
        require(_tokenOwners[id].ttl < ttl, "Error:Already used");
        bytes32 hashed =
            keccak256(
                abi.encodePacked(
                    domain,
                    to,
                    id,
                    ttl,
                    msg.value,
                    DNSSEC,
                    setAsDefault,
                    _chainId
                )
            );
        require(_minters[hashed.recover(v, r, s)] == true, "Error:Unsigned tx");
        _mint(
            domain,
            to,
            ttl,
            id,
            DNSSEC,
            setAsDefault,
            uint32(block.timestamp)
        );
    }

    // EVM has chainId
    function fetchChainId() public onlyOwner {
        _chainId = _getChainId();
    }

    // in case there is no chainId on the EVM i have to come up with one...
    function setChainId(uint256 id) external onlyOwner {
        _chainId = id;
    }

    /*
     * To save users minting costs, the transfer of current contract holdings ist done owner
     * This saves an unneccessary transfer in the minting process.
     */
    function withdraw() external onlyOwner {
        require(payable(msg.sender).send(address(this).balance));
    }

    /*
     * Multichain....
     */
    function _getChainId() internal pure returns (uint256) {
        uint256 id;
        assembly {
            id := chainid()
        }
        return id;
    }

    /*
     * Burns domain token if you want to get rid of it
     */
    function burn(uint256 id) public {
        require(ownerOf(id) == msg.sender, "Error: you have to be Owner");
        _tokenOwners[id].owner = address(0);
        if (_defaultDomain[msg.sender] != 0) {
            delete _defaultDomain[msg.sender];
        }
        emit Transfer(msg.sender, address(0), id);
    }

    /*
     * Gets default domain for address
     */
    function getDefaultDomain(address adr)
        external
        view
        returns (string memory domain)
    {
        uint256 id = _defaultDomain[adr];
        require(adr != address(0), "Error: No default for null address");
        require(ownerOf(id) == adr, "Error: No default domain");
        return _tokenOwners[id].domain;
    }

    /*
     * Sets default domaintoken for address
     */
    function setDefaultDomainToken(uint256 id) external {
        require(ownerOf(id) == msg.sender, "Error: Not owner of domain");
        _defaultDomain[msg.sender] = id;
    }

    /*
     * Resets domaintoken for address
     */
    function resetDefaultDomainToken() external {
        _defaultDomain[msg.sender] = 0;
    }

    /*
     * Gets tokenId for a given domain (Punycoded)
     */
    function getDomainTokenId(string memory domain)
        public
        pure
        returns (uint256 tokenId)
    {
        return uint256(keccak256(abi.encodePacked(domain)));
    }

    /*
     * Gets domainstring for the token
     */
    function getDomainOfToken(uint256 tokenId)
        public
        view
        returns (string memory domain)
    {
        return _tokenOwners[tokenId].domain;
    }

    /*
     * Gets the owner wallet of the domain
     */
    function getDomainOwner(string memory domain)
        public
        view
        returns (address owner)
    {
        return ownerOf(getDomainTokenId(domain));
    }

    /*
     * Tokenname
     */
    function name() public view returns (string memory) {
        return _name;
    }

    /*
     * Tokensymbol
     */
    function symbol() public view returns (string memory) {
        return _symbol;
    }

    /*
     * TokenUri
     */
    function tokenURI(uint256 tokenId) public view returns (string memory) {
        require(
            ownerOf(tokenId) != address(0),
            "ERC721Metadata: URI query for nonexistent token"
        );
        return
            string(
                abi.encodePacked(
                    baseURI(),
                    _chainId.toString(),
                    "/",
                    tokenId.toString()
                )
            );
    }

    /*
     * FFU => SET 100 once DNSSEC verification is implemented
     */

    function setCurrentMaxTrustScore(uint8 score) external onlyOwner {
        _maxTrustScore = score;
    }

    /*
     * Gives you the current max trustScore reachable (DNSSEC is not implemented yet)
     */
    function getCurrentMaxTrustScore() public view returns (uint8) {
        return _maxTrustScore;
    }

    /*
     * See getTrustScore(uint256 id) but domain-string based
     */
    function getTrustScore(string memory domain) public view returns (uint256) {
        return getTrustScore(getDomainTokenId(domain));
    }

    /*
     * Gets a trustScore (0 - 100) for a domain
     * DNSSEC Domains are 100
     *
     * NON DNSSEC domain are 0 - 95 (Zero at day one and 90 at day 9)
     * Final value of non-DNSSEC domains is 95 after day 10.
     * If you want some security on non DNSSEC entries you might consider that in a case of a broad DNS-Spoofing attack
     * it could probably last about one to five days in normal cases.
     * If you have critical payments on chain, implement the getTrustScore accordingly!
     */
    function getTrustScore(uint256 id) public view returns (uint256) {
        require(ownerOf(id) != address(0), "Error: id has no owner");
        if (_tokenOwners[id].DNSSEC) {
            return 100;
        }
        uint256 sinceInception = block.timestamp - _tokenOwners[id].timestamp;
        uint256 daysSinceInception = sinceInception / 86400;
        if (daysSinceInception > 9) {
            return 95;
        } else {
            return daysSinceInception * 10;
        }
    }

    function setBaseURI(string memory baseURI_) public onlyOwner {
        _baseURI = baseURI_;
    }

    function baseURI() public view returns (string memory) {
        return _baseURI;
    }

    /* adds a minter role */
    function addMinter(address minter_) public onlyOwner {
        _minters[minter_] = true;
    }

    /* removes a minter role */
    function removeMinter(address minter_) public onlyOwner {
        _minters[minter_] = false;
    }
}
/**
 * @dev {ERC721} token, including:
 *
 *  - ability for holders to burn (destroy) their tokens
 *  - a minter role that allows for token minting (creation)
 *  - a pauser role that allows to stop all token transfers
 *  - token ID and URI autogeneration
 *
 * This contract uses {AccessControl} to lock permissioned functions using the
 * different roles - head to its documentation for details.
 *
 * The account that deploys the contract will be granted the minter and pauser
 * roles, as well as the default admin role, which will let it grant both minter
 * and pauser roles to other accounts.
 */
function removeMinter(address minter_) public onlyOwner {
    _minters[minter_] = false;
}

/* removes a minter role */