contract BuyStarNFT is Ownable, Pausable, AccessControl, VRFConsumerBase
{

    bytes32 internal keyHash;
    uint256 internal fee;
    mapping(bytes32 => uint256) public requestId_tokenId;

    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
    bytes32 public constant SETBUYROOT_ROLE = keccak256("SETBUYROOT_ROLE");
    DKPContract public dpk;
    bytes4 constant InterfaceSignature_ERC721 = 0x80ac58cd;

    event ExtractCoinEvent(uint256 indexed coins);
    event BuyStarNFTEvent(address buyers,uint256 number);
    event RandomnessCallbackEvent(bytes32 requestId, uint256 tokenid, uint256 random);

    uint256 private _price = 0.25 * 10 ** 18;
    uint256 private _sellMaxCount = 400;
    uint256 private _sellOutCount = 0;
    uint256 private _canByCount = 5;
    uint256 private _tokenType = 60000;
    string[] private _params;
    address dpkAddress;
    address extractAddress;

    uint256 startTime;

    mapping(address=>bool) _buyRoleMap;
    mapping(address=>uint256) _buyInfoMap;

    uint256 public batchNumber = 0;
    mapping(address=>mapping(uint256=>uint256)) public limitation;



    constructor(address dpkContractAddress, address _vrfCoordinator, address _link, bytes32 _keyHash, uint256 _fee) 
    VRFConsumerBase(_vrfCoordinator, _link)
    {
        keyHash = _keyHash;
        fee = _fee;

        _setRoleAdmin(ADMIN_ROLE, ADMIN_ROLE);
        _setRoleAdmin(SETBUYROOT_ROLE, ADMIN_ROLE);
        _setupRole(ADMIN_ROLE, msg.sender);
        _setupRole(SETBUYROOT_ROLE, msg.sender);
        _setDPKContractAddress(dpkContractAddress);
        extractAddress = msg.sender;
    }

    function _setDPKContractAddress(address dpkContractAddress) private
    {
        DKPContract candidateContract = DKPContract(dpkContractAddress);
        require(candidateContract.supportsInterface(InterfaceSignature_ERC721));
        dpkAddress = dpkContractAddress;
        dpk = candidateContract;  
    }
    
    function setDPKContractAddress(address dpkContractAddress) public onlyOwner {
        _setDPKContractAddress(dpkContractAddress);
    }
    
    function getDPKContractAddress() public view returns(address) {
        return dpkAddress;
    }
    
    function setExtractAddress(address newExtractAddress) public onlyOwner {
        extractAddress = newExtractAddress;
    }
    function setSellMaxCount(uint256 num) public onlyOwner {
        _sellMaxCount = num;
    }
    function setCanBuyCount(uint256 num) public onlyOwner {
        _canByCount = num;
    }
    function setStartTime(uint256 num) public onlyOwner {
        startTime = num;
    }



    function setSellNFTInfo(uint256 tokenType,string[] memory params) public onlyOwner {
        require((params.length % 2) == 0,"params is key|value structure");
        _tokenType = tokenType;
        _params = new string[](0);
        for (uint256 i = 0; i < params.length; i++) {
            _params.push(params[i]);
        }
    }

    function setBuyRole(address[] memory accounts,bool bo) public onlyRole(SETBUYROOT_ROLE) {
        for (uint256 i = 0; i < accounts.length; i++) {
            _setBuyRole(accounts[i],bo);
        }
    }

    function _setBuyRole(address account,bool bo) private {
        require(_buyRoleMap[account] != bo, "Permission to exist");
        _buyRoleMap[account] = bo;
    }

    function getSellNFTType() public view returns(uint256) {
        return _tokenType;
    }

    function getSellNFTParams() public view returns(string[] memory) {
        return _params;
    }

    function getSellMaxCount() public view returns(uint256) {
        return _sellMaxCount;
    }

    function getSellOutCount() public view returns(uint256) {
        return _sellOutCount;
    }
    
    function getCanBuyCount() public view returns(uint256) {
        return _canByCount;
    }
    function getStartTime() public view returns(uint256) {
        return startTime;
    }
    function getAddressRoot(address account) public view returns(bool) {
        return _buyRoleMap[account];
    }

    function getAddressBuyCount(address account) public view returns(uint256) {
        return _buyInfoMap[account];
    }


    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

    function extractCoin() public onlyOwner {
        require(payable(address(this)).balance > 0,"balance is zero");

        uint256 costNumber = payable(address(this)).balance;
        address payable account = payable(extractAddress);
        account.transfer(costNumber);
        emit ExtractCoinEvent(costNumber);
    }

    function setPrice(uint256 price) public onlyOwner {
        _price = price;
    }
    
    function getPrice() public view returns(uint256) {
        return _price;
    }

    function mintMutiTokenForCaller(uint8 amount) payable public {
        require(!paused(), "Pausable: buy nft while paused");
        require(block.timestamp >= startTime, "not start");
        require(limitation[msg.sender][batchNumber] + amount <= _canByCount, "To reach the purchase limit");
        require(_sellMaxCount >= _sellOutCount + amount, "Sold out");
        require(msg.value >= _price * amount, "insufficient fund");

        _sellOutCount += amount;
        limitation[msg.sender][batchNumber] += amount;

        for (uint8 i = 0; i < amount; i++) {

            uint256 tokenId = dpk.mint(msg.sender);
            reqRandomNumber(tokenId);
        }

        emit BuyStarNFTEvent(msg.sender, amount);
    }


    function nextBatch() public onlyOwner {
        batchNumber += 1;
        _sellOutCount = 0;
    }


     
    function reqRandomNumber(uint256 tokenId) internal returns(bytes32) {
        
        require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK - fill contract with faucet");
        bytes32 requestId = requestRandomness(keyHash, fee);
        requestId_tokenId[requestId] = tokenId;
        return requestId;
    }

    /**
     * Callback function used by VRF Coordinator
     */
    function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override
    { 
        uint256 tokenId = requestId_tokenId[requestId];
        emit RandomnessCallbackEvent(requestId, tokenId, randomness);
    }

    //Implement a withdraw function to avoid locking your LINK in the contract
    function withdrawLink(address to) public onlyOwner {

        uint256 balance = LINK.balanceOf(address(this));
        assert(LINK.transfer(to, balance));
    } 


}
function withdrawLink(address to) public onlyOwner {

    uint256 balance = LINK.balanceOf(address(this));
    assert(LINK.transfer(to, balance));
} 

//Implement a withdraw function to avoid locking your LINK in the contract