// File: ens/SubnameAgent.sol



// SPDX-License-Identifier: MIT

pragma solidity >=0.8.4;

interface IERC721 {    
    function ownerOf(uint tokenId) external view returns (address owner);
}

interface IERC20 {
    function balanceOf(address account) external view returns (uint);    
    function decimals() external view returns (uint);
}

interface ENS {
    // Logged when the owner of a node assigns a new owner to a subnode.
    event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner);

    // Logged when the owner of a node transfers ownership to a new account.
    event Transfer(bytes32 indexed node, address owner);

    // Logged when the resolver for a node changes.
    event NewResolver(bytes32 indexed node, address resolver);

    // Logged when the TTL of a node changes
    event NewTTL(bytes32 indexed node, uint64 ttl);

    // Logged when an operator is added or removed.
    event ApprovalForAll(
        address indexed owner,
        address indexed operator,
        bool approved
    );

    function setRecord(
        bytes32 node,
        address owner,
        address resolver,
        uint64 ttl
    ) external;

    function setSubnodeRecord(
        bytes32 node,
        bytes32 label,
        address owner,
        address resolver,
        uint64 ttl
    ) external;

    function setSubnodeOwner(
        bytes32 node,
        bytes32 label,
        address owner
    ) external returns (bytes32);

    function setResolver(bytes32 node, address resolver) external;

    function setOwner(bytes32 node, address owner) external;

    function setTTL(bytes32 node, uint64 ttl) external;

    function setApprovalForAll(address operator, bool approved) external;

    function owner(bytes32 node) external view returns (address);

    function resolver(bytes32 node) external view returns (address);

    function ttl(bytes32 node) external view returns (uint64);

    function recordExists(bytes32 node) external view returns (bool);

    function isApprovedForAll(
        address owner,
        address operator
    ) external view returns (bool);
}

uint32 constant CANNOT_UNWRAP = 1;
uint32 constant CANNOT_BURN_FUSES = 2;
uint32 constant CANNOT_TRANSFER = 4;
uint32 constant CANNOT_SET_RESOLVER = 8;
uint32 constant CANNOT_SET_TTL = 16;
uint32 constant CANNOT_CREATE_SUBDOMAIN = 32;
uint32 constant CANNOT_APPROVE = 64;
//uint16 reserved for parent controlled fuses from bit 17 to bit 32
uint32 constant PARENT_CANNOT_CONTROL = 1 << 16;
uint32 constant IS_DOT_ETH = 1 << 17;
uint32 constant CAN_EXTEND_EXPIRY = 1 << 18;
uint32 constant CAN_DO_EVERYTHING = 0;
uint32 constant PARENT_CONTROLLED_FUSES = 0xFFFF0000;
// all fuses apart from IS_DOT_ETH
uint32 constant USER_SETTABLE_FUSES = 0xFFFDFFFF;

interface INameWrapper{
    event NameWrapped(
        bytes32 indexed node,
        bytes name,
        address owner,
        uint32 fuses,
        uint64 expiry
    );

    event NameUnwrapped(bytes32 indexed node, address owner);

    event FusesSet(bytes32 indexed node, uint32 fuses);
    event ExpiryExtended(bytes32 indexed node, uint64 expiry);

    function ens() external view returns (ENS);

    function names(bytes32) external view returns (bytes memory);

    function name() external view returns (string memory);


    function supportsInterface(bytes4 interfaceID) external view returns (bool);

    function wrap(
        bytes calldata name,
        address wrappedOwner,
        address resolver
    ) external;

    function wrapETH2LD(
        string calldata label,
        address wrappedOwner,
        uint16 ownerControlledFuses,
        address resolver
    ) external returns (uint64 expires);

    function registerAndWrapETH2LD(
        string calldata label,
        address wrappedOwner,
        uint256 duration,
        address resolver,
        uint16 ownerControlledFuses
    ) external returns (uint256 registrarExpiry);

    function renew(
        uint256 labelHash,
        uint256 duration
    ) external returns (uint256 expires);

    function unwrap(bytes32 node, bytes32 label, address owner) external;

    function unwrapETH2LD(
        bytes32 label,
        address newRegistrant,
        address newController
    ) external;

    function upgrade(bytes calldata name, bytes calldata extraData) external;

    function setFuses(
        bytes32 node,
        uint16 ownerControlledFuses
    ) external returns (uint32 newFuses);

    function setChildFuses(
        bytes32 parentNode,
        bytes32 labelhash,
        uint32 fuses,
        uint64 expiry
    ) external;

    function setSubnodeRecord(
        bytes32 node,
        string calldata label,
        address owner,
        address resolver,
        uint64 ttl,
        uint32 fuses,
        uint64 expiry
    ) external returns (bytes32);

    function setRecord(
        bytes32 node,
        address owner,
        address resolver,
        uint64 ttl
    ) external;

    function setSubnodeOwner(
        bytes32 node,
        string calldata label,
        address newOwner,
        uint32 fuses,
        uint64 expiry
    ) external returns (bytes32);

    function extendExpiry(
        bytes32 node,
        bytes32 labelhash,
        uint64 expiry
    ) external returns (uint64);

    function canExtendSubnames(
        bytes32 node,
        address addr
    ) external view returns (bool);

    function canModifyName(
        bytes32 node,
        address addr
    ) external view returns (bool);

    function setResolver(bytes32 node, address resolver) external;

    function setTTL(bytes32 node, uint64 ttl) external;

    function ownerOf(uint256 id) external view returns (address owner);

    function approve(address to, uint256 tokenId) external;

    function getApproved(uint256 tokenId) external view returns (address);

    function getData(
        uint256 id
    ) external view returns (address, uint32, uint64);

    function uri(uint256 tokenId) external view returns (string memory);

    function allFusesBurned(
        bytes32 node,
        uint32 fuseMask
    ) external view returns (bool);

    function isWrapped(bytes32) external view returns (bool);

    function isWrapped(bytes32, bytes32) external view returns (bool);

    
    function setApprovalForAll(address operator, bool approved) external;
}



abstract contract Ownable {
    address internal _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    constructor () {
        address msgSender = msg.sender;
        _owner = msgSender;
        emit OwnershipTransferred(address(0), msgSender);
    }

    function owner() public view returns (address) {
        return _owner;
    }

    modifier onlyOwner() {
        require(_owner == msg.sender, "!owner");
        _;
    }

    function renounceOwnership() public virtual onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }

    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "new is 0");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}

contract SubnameAgent is Ownable {
    struct DomainInfo{
        uint8 authMask; // bit 0x111， canTransfer|canExtendExpiry
        uint8 subnameFormatRule; // 1 Customize 2, Autoincrement, 3 NFTID
        uint32 autoincreament;
        uint32 expiryTime;
        uint32 allowedSubnameCount; 
        uint32 soldSubnameCount;
        uint32 index;
        bytes32 node;
        uint price;
        address operator;
        address allowedTokenAddress;
        address[] allowedAddress;
        string description; 
    }

    struct SubnameInfo{
        string label;
        string parent;
    } 

    INameWrapper public immutable  nameWrapper;
    mapping(string=>DomainInfo) _openedDomainMap;
    uint public sellFee;
    string[] _openedDomainList;
    SubnameInfo[] _subnameList; // latest 100 
    constructor(address nameWrapperAddress) {
        nameWrapper = INameWrapper(nameWrapperAddress);
        sellFee = 1e15;
    }

    function setSellFee(uint fee)external onlyOwner{
        sellFee=fee;
    }

    function open(string calldata name, 
        string calldata description, 
        uint8 authMask,
        uint8 subnameFormatRule,
        uint32 expiryTime,
        uint32 allowedSubnameCount,
        bytes32 node,
        uint price,
        address allowedTokenAddress,
        address[] calldata allowedAddress) external returns(uint){

        
        require(nameWrapper.canExtendSubnames(node, address(this)),"Should approve subname to this contract first");
        nameWrapper.setFuses(node,1); // Locked the domain, so we can set fuses for its subnames
        _openedDomainMap[name] = DomainInfo(
            authMask,
            subnameFormatRule,
            1,
            expiryTime,
            allowedSubnameCount,
            0,
            uint32(_openedDomainList.length),
            node,
            price,
            msg.sender,
            allowedTokenAddress,
            allowedAddress,
            description
        );
        _openedDomainList.push(name);
        return _openedDomainMap[name].index;
    }

    function close(string calldata name) external {
        DomainInfo memory domainInfo = _openedDomainMap[name];
        require(domainInfo.operator!=address(0), "Domain is not opened");
        require(domainInfo.operator==msg.sender, "Only operator can close");
        delete _openedDomainList[domainInfo.index];
        delete _openedDomainMap[name];
    }

    function getOpenedDomainInfo(string calldata name) external view returns (DomainInfo memory domainInfo, address[] memory allowedAddress){
        domainInfo = _openedDomainMap[name];
        allowedAddress = domainInfo.allowedAddress;
    }

    function getOpenedDomainLength() external view returns(uint){
        return _openedDomainList.length;
    }

    function getOpenedDomainList(uint offset, uint pageSize) external view returns (string[] memory list){
        uint limit = _openedDomainList.length < (offset + pageSize) ? _openedDomainList.length : (offset + pageSize);
        list=new string[](limit-offset);
        uint i;        
        for(i=offset; i<limit; ++i){
            list[i]=_openedDomainList[i];
        }
    }

    function getSubnameLength() external view returns(uint){
        return _subnameList.length;
    }

    function getSubnameList(uint offset, uint pageSize) external view returns (SubnameInfo[] memory list){
        uint limit = _subnameList.length < (offset + pageSize) ? _subnameList.length : (offset + pageSize);
        list=new SubnameInfo[](limit-offset);
        uint i;
        for(i=offset; i<limit; ++i){
            list[i]=_subnameList[i];
        }
    }
    // function setSubnodeRecord(
    //         bytes32 parentNode,
    //         string calldata label,
    //         address owner,
    //         address resolver,
    //         uint64 ttl,
    //         uint32 fuses,
    //         uint64 expiry
    //     ) external returns (bytes32);

    function createSubnameWithLabel(string calldata label, string calldata openedDaminName, address owner, address resolver, uint32 fuses, uint32 childFuses, uint64 expiry) payable external returns(bytes32 node){
        DomainInfo storage domainInfo = _openedDomainMap[openedDaminName];
        if(domainInfo.allowedTokenAddress!=address(0) && domainInfo.subnameFormatRule!=3){
            require(IERC20(domainInfo.allowedTokenAddress).balanceOf(msg.sender)>=1*10**IERC20(domainInfo.allowedTokenAddress).decimals(), "Token balance must >= 1");
        }
        node = createSubnameCommon(domainInfo, label, owner, resolver, fuses, childFuses, expiry);
    }

    function createSubnameWithAuto(string calldata openedDaminName, address owner, address resolver, uint32 fuses, uint32 childFuses, uint64 expiry) payable external returns(bytes32 node){
        DomainInfo storage domainInfo = _openedDomainMap[openedDaminName];
        if(domainInfo.allowedTokenAddress!=address(0) && domainInfo.subnameFormatRule!=3){
            require(IERC20(domainInfo.allowedTokenAddress).balanceOf(msg.sender)>=1*10**IERC20(domainInfo.allowedTokenAddress).decimals(), "Token balance must >= 1");
        }
        node = createSubnameCommon(domainInfo, _uintToString(domainInfo.autoincreament), owner, resolver, fuses, childFuses, expiry);

        domainInfo.autoincreament = domainInfo.autoincreament + uint32(1);
    }

    function createSubnameWithNtfId(uint nftId, string calldata openedDaminName, address owner, address resolver, uint32 fuses, uint32 childFuses, uint64 expiry) payable external returns(bytes32 node){
        DomainInfo storage domainInfo = _openedDomainMap[openedDaminName];
        require(msg.sender==IERC721(domainInfo.allowedTokenAddress).ownerOf(nftId), "You are not owner of this NFT ID");

        node = createSubnameCommon(domainInfo, _uintToString(nftId), owner, resolver, fuses, childFuses, expiry); 
    }

    function createSubnameCommon(DomainInfo storage domainInfo, string memory label, address owner, address resolver, uint32 fuses, uint32 childFuses, uint64 expiry) internal returns(bytes32 node){
        uint len = domainInfo.allowedAddress.length;
        if(len>0){
            bool inAllowedList = false;            
            for(uint i=0;i<len;++i){
                if(domainInfo.allowedAddress[i]==msg.sender){
                    inAllowedList = true;
                    break;
                }
            }
            require(inAllowedList, "Only allowed address can register subname");
        }
        
        require(domainInfo.operator!=address(0), "Domain is not opened");
        require(domainInfo.soldSubnameCount<domainInfo.allowedSubnameCount, "Sold subname up limit");
        require(msg.value==(domainInfo.price+sellFee), "Pay price wrong");
        node = nameWrapper.setSubnodeRecord(domainInfo.node, label, owner, resolver, 0, 0, expiry);
        if(childFuses>0){
            bytes32 labelhash = keccak256(bytes(label));
            nameWrapper.setChildFuses(domainInfo.node, labelhash, childFuses, expiry);
        }
        if(fuses>0){
            nameWrapper.setFuses(node, uint16(fuses));
        }
        _subnameList.push(SubnameInfo(label, _openedDomainList[domainInfo.index])); // save subname

        domainInfo.soldSubnameCount = domainInfo.soldSubnameCount + uint32(1);
        if(msg.value-sellFee>0) payable (domainInfo.operator).transfer(msg.value-sellFee);
    }


    function fetchEth(uint amount, address toAddress) external onlyOwner{
        assert(amount<=address(this).balance);
        payable(toAddress).transfer(amount);
    }

    function _strConcat(string memory _a, string memory _b) internal pure returns (string memory){
        bytes memory _ba = bytes(_a);
        bytes memory _bb = bytes(_b);
        string memory ret = new string(_ba.length + _bb.length);
        bytes memory bret = bytes(ret);
        uint k = 0;
        for (uint i = 0; i < _ba.length; i++) bret[k++] = _ba[i];
        for (uint i = 0; i < _bb.length; i++) bret[k++] = _bb[i];
        return string(ret);
    }   

    function _toStr(uint256 value) internal pure returns(string memory) {
        bytes memory alphabet = "0123456789abcdef";
        bytes memory data = abi.encodePacked(value);
        bytes memory str = new bytes(1);
        uint i = data.length - 1;
        str[0] = alphabet[uint(uint8(data[i] & 0x0f))];
        return string(str);
    }

    function _uintToString(uint _uint) internal pure returns (string memory str) {
 
        if(_uint==0) return '0';
 
        while (_uint != 0) {
            uint remainder = _uint % 10;
            _uint = _uint / 10;
            str = _strConcat(_toStr(remainder),str);
        }
    }
}