// File: ArrngConsumer.sol



// SPDX-License-Identifier: MIT

/**
 *
 * @title ArrngConsumer.sol. Use arrng
 *
 * @author arrng https://arrng.io/
 *
 */

import {IArrngConsumer} from "./IArrngConsumer.sol";
import {IArrngController} from "./IArrngController.sol";

pragma solidity 0.8.19;

abstract contract ArrngConsumer is IArrngConsumer {
  IArrngController public immutable arrngController;

  /**
   * @dev constructor
   */
  constructor(address arrngController_) {
    arrngController = IArrngController(arrngController_);
  }

  /**
   *
   * @dev fulfillRandomWords: Do something with the randomness
   *
   * @param requestId: unique ID for this request
   * @param randomWords: array of random integers requested
   *
   */
  function fulfillRandomWords(
    uint256 requestId,
    uint256[] memory randomWords
  ) internal virtual;

  /**
   *
   * @dev receiveRandomness: receive RNG
   *
   * @param arrngRequestId_: unique ID for this request
   * @param randomNumbers_: array of random integers requested
   *
   */
  function receiveRandomness(
    uint256 arrngRequestId_,
    uint256[] calldata randomNumbers_
  ) external payable {
    require(msg.sender == address(arrngController), "arrng controller only");
    fulfillRandomWords(arrngRequestId_, randomNumbers_);
  }
}
// File: IArrngConsumer.sol



// SPDX-License-Identifier: MIT

/**
 *
 * @title IArrngConsumer.sol. Use arrng
 *
 * @author arrng https://arrng.io/
 *
 */

pragma solidity 0.8.19;

interface IArrngConsumer {
  /**
   *
   * @dev receiveRandomness: receive randomness
   *
   * @param arrngRequestId_: unique ID for this request
   * @param randomNumbers_: array of random integers requested
   *
   */
  function receiveRandomness(
    uint256 arrngRequestId_,
    uint256[] calldata randomNumbers_
  ) external payable;
}
// File: IArrngController.sol



// SPDX-License-Identifier: MIT

/**
 *
 * @title IArrngController.sol. Interface for the arrngController.
 *
 * @author arrng https://arrng.io/
 *
 */

pragma solidity 0.8.19;

interface IArrngController {
  event ENSReverseRegistrarSet(address newENSReverseRegistrar);
  event ENSNameSet(string ensName, bytes32 ensNameHash);
  event MinimumNativeTokenSet(uint256 minimumNativeToken);
  event MaximumNumberOfNumbersSet(uint256 newNumberLimited);
  event OracleAddressSet(address oracle);
  event TreasuryAddressSet(address treasury);
  event ArrngRequest(
    address indexed caller,
    uint64 indexed requestId,
    uint32 method,
    uint64 numberOfNumbers,
    uint64 minValue,
    uint64 maxvalue,
    uint64 ethValue,
    address refundAddress
  );
  event ArrngResponse(bytes32 requestTxnHash);
  event ArrngServed(
    uint128 indexed requestId,
    uint128 feeCharged,
    uint256[] randomNumbers,
    string apiResponse,
    string apiSignature
  );
  event ArrngRefundInsufficientTokenForGas(
    address indexed caller,
    uint256 requestId
  );

  /**
   * -------------------------------------------------------------
   * @dev ADMINISTRATION
   * -------------------------------------------------------------
   */

  /**
   *
   * @dev setENSReverseRegistrar: set the ENS register address
   *
   * @param ensRegistrar_: ENS Reverse Registrar address
   *
   */
  function setENSReverseRegistrar(address ensRegistrar_) external;

  /**
   *
   * @dev setENSName: used to set reverse record so interactions with this contract
   * are easy to identify
   *
   * @param ensName_: string ENS name
   *
   */
  function setENSName(string memory ensName_) external;

  /**
   *
   * @dev setMinimumNativeToken: set a new value of required native token for gas
   *
   * @param minNativeToken_: the new minimum native token per call
   *
   */
  function setMinimumNativeToken(uint176 minNativeToken_) external;

  /**
   *
   * @dev setMaximumNumberOfNumbers: set a new max number of numbers
   *
   * @param maxNumbersPerTxn_: the new max requested numbers
   *
   */
  function setMaximumNumberOfNumbers(uint16 maxNumbersPerTxn_) external;

  /**
   *
   * @dev setOracleAddress: set a new oracle address
   *
   * @param oracle_: the new oracle address
   *
   */
  function setOracleAddress(address payable oracle_) external;

  /**
   *
   * @dev setTreasuryAddress: set a new treasury address
   *
   * @param treasury_: the new treasury address
   *
   */
  function setTreasuryAddress(address payable treasury_) external;

  /**
   *
   * @dev withdrawNativeToken: pull native token to the treasuryAddress
   *
   * @param amount_: amount to withdraw
   *
   */
  function withdrawNativeToken(uint256 amount_) external;

  /**
   *
   * @dev withdrawERC20: pull ERC20 tokens to the treasuryAddress
   *
   * @param erc20Address_: the contract address for the token
   * @param amount_: amount to withdraw
   *
   */
  function withdrawERC20(address erc20Address_, uint256 amount_) external;

  /**
   *
   * @dev withdrawERC721: Pull ERC721s (likely only the ENS
   * associated with this contract) to the treasuryAddress.
   *
   * @param erc721Address_: The token contract for the withdrawal
   * @param tokenIDs_: the list of tokenIDs for the withdrawal
   *
   */
  function withdrawERC721(
    address erc721Address_,
    uint256[] memory tokenIDs_
  ) external;

  /**
   * -------------------------------------------------------------
   * @dev PROCESS REQUESTS
   * -------------------------------------------------------------
   */

  /**
   *
   * @dev requestRandomWords: request 1 to n uint256 integers
   * requestRandomWords is overloaded. In this instance you can
   * call it without explicitly declaring a refund address, with the
   * refund being paid to the tx.origin for this call.
   *
   * @param numberOfNumbers_: the amount of numbers to request
   *
   * @return uniqueID_ : unique ID for this request
   */
  function requestRandomWords(
    uint256 numberOfNumbers_
  ) external payable returns (uint256 uniqueID_);

  /**
   *
   * @dev requestRandomWords: request 1 to n uint256 integers
   * requestRandomWords is overloaded. In this instance you must
   * specify the refund address for unused native token.
   *
   * @param numberOfNumbers_: the amount of numbers to request
   * @param refundAddress_: the address for refund of native token
   *
   * @return uniqueID_ : unique ID for this request
   */
  function requestRandomWords(
    uint256 numberOfNumbers_,
    address refundAddress_
  ) external payable returns (uint256 uniqueID_);

  /**
   *
   * @dev requestRandomNumbersInRange: request 1 to n integers within
   * a given range (e.g. 1 to 10,000)
   * requestRandomNumbersInRange is overloaded. In this instance you can
   * call it without explicitly declaring a refund address, with the
   * refund being paid to the tx.origin for this call.
   *
   * @param numberOfNumbers_: the amount of numbers to request
   * @param minValue_: the min of the range
   * @param maxValue_: the max of the range
   *
   * @return uniqueID_ : unique ID for this request
   */
  function requestRandomNumbersInRange(
    uint256 numberOfNumbers_,
    uint256 minValue_,
    uint256 maxValue_
  ) external payable returns (uint256 uniqueID_);

  /**
   *
   * @dev requestRandomNumbersInRange: request 1 to n integers within
   * a given range (e.g. 1 to 10,000)
   * requestRandomNumbersInRange is overloaded. In this instance you must
   * specify the refund address for unused native token.
   *
   * @param numberOfNumbers_: the amount of numbers to request
   * @param minValue_: the min of the range
   * @param maxValue_: the max of the range
   * @param refundAddress_: the address for refund of native token
   *
   * @return uniqueID_ : unique ID for this request
   */

  function requestRandomNumbersInRange(
    uint256 numberOfNumbers_,
    uint256 minValue_,
    uint256 maxValue_,
    address refundAddress_
  ) external payable returns (uint256 uniqueID_);

  /**
   *
   * @dev requestWithMethod: public method to allow calls specifying the
   * arrng method, allowing functionality to be extensible without
   * requiring a new controller contract.
   * requestWithMethod is overloaded. In this instance you can
   * call it without explicitly declaring a refund address, with the
   * refund being paid to the tx.origin for this call.
   *
   * @param numberOfNumbers_: the amount of numbers to request
   * @param minValue_: the min of the range
   * @param maxValue_: the max of the range
   * @param method_: the arrng method to call
   *
   * @return uniqueID_ : unique ID for this request
   */
  function requestWithMethod(
    uint256 numberOfNumbers_,
    uint256 minValue_,
    uint256 maxValue_,
    uint32 method_
  ) external payable returns (uint256 uniqueID_);

  /**
   *
   * @dev requestWithMethod: public method to allow calls specifying the
   * arrng method, allowing functionality to be extensible without
   * requiring a new controller contract.
   * requestWithMethod is overloaded. In this instance you must
   * specify the refund address for unused native token.
   *
   * @param numberOfNumbers_: the amount of numbers to request
   * @param minValue_: the min of the range
   * @param maxValue_: the max of the range
   * @param refundAddress_: the address for refund of native token
   * @param method_: the arrng method to call
   *
   * @return uniqueID_ : unique ID for this request
   */
  function requestWithMethod(
    uint256 numberOfNumbers_,
    uint256 minValue_,
    uint256 maxValue_,
    address refundAddress_,
    uint32 method_
  ) external payable returns (uint256 uniqueID_);

  /**
   *
   * @dev serveRandomness: serve result of the call
   *
   * @param arrngRequestId_: unique request ID
   * @param callingAddress_: the contract to call
   * @param requestTxnHash_: the txn hash of the original request
   * @param responseCode_: 0 is success, !0 = failure
   * @param randomNumbers_: the array of random integers
   * @param refundAddress_: the address for refund of native token not used for gas
   * @param apiResponse_: the response from the off-chain rng provider
   * @param apiSignature_: signature for the rng response
   * @param feeCharged_: the fee for this rng
   *
   */
  function serveRandomness(
    uint256 arrngRequestId_,
    address callingAddress_,
    bytes32 requestTxnHash_,
    uint256 responseCode_,
    uint256[] calldata randomNumbers_,
    address refundAddress_,
    string calldata apiResponse_,
    string calldata apiSignature_,
    uint256 feeCharged_
  ) external payable;
}
// File: INextGenAdmins.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.19;

interface INextGenAdmins {

    // retrieve global admin
    function retrieveGlobalAdmin(address _address) external view returns(bool);

    // retrieve function admin
    function retrieveFunctionAdmin(address _address, bytes4 _selector) external view returns(bool);

    // retrieve collection admin
    function retrieveCollectionAdmin(address _address, uint256 _collectionID) external view returns(bool);

    // retrieve if the contract is admin contract
    function isAdminContract() external view returns (bool);

    // retrieve owner
    function owner() external view returns (address);

}
// File: INextGenCore.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.19;

interface INextGenCore {

    function retrievewereDataAdded(uint256 _collectionID) external view returns(bool);

    function viewTokensIndexMin(uint256 _collectionID) external view returns (uint256);

    function viewTokensIndexMax(uint256 _collectionID) external view returns (uint256);

    function viewCirSupply(uint256 _collectionID) external view returns (uint256);

    function airDropTokens(uint256 mintIndex, address _recipient, string memory _tokenData, uint256 _saltfun_o, uint256 _collectionID) external;

    function mint(uint256 mintIndex, address _mintingAddress , address _mintTo, string memory _tokenData, uint256 _saltfun_o, uint256 _collectionID, uint256 phase) external;

    function collectionFreezeStatus(uint256 _collectionID) external view returns(bool);

    function viewMaxAllowance(uint256 _collectionID) external view returns (uint256);

    function retrieveTokensMintedALPerAddress(uint256 _collectionID, address _address) external view returns(uint256);

    function retrieveTokensMintedPublicPerAddress(uint256 _collectionID, address _address) external view returns(uint256);

    function viewColIDforTokenID(uint256 _tokenid) external view returns (uint256);

    function burnToMint(uint256 mintIndex, uint256 _burnCollectionID, uint256 _tokenId, uint256 _mintCollectionID, uint256 _saltfun_o, address burner) external;

    function retrieveArtistAddress(uint256 _collectionID) external view returns(address);

    function setTokenHash(uint256 _collectionID, uint256 _mintIndex, bytes32 _hash) external;

    function retrieveTokenHash(uint256 _tokenid) external view returns(bytes32);

}
// File: RandomizerRNG.sol



// SPDX-License-Identifier: MIT

/**
 *
 *  @title: NextGen 6529 - RNG Randomizer Contract
 *  @date: 20-December-2023
 *  @version: 1.8
 *  @author: 6529 team
 */

pragma solidity ^0.8.19;

import "./ArrngConsumer.sol";
import "./INextGenCore.sol";
import "./INextGenAdmins.sol";

contract NextGenRandomizerRNG is ArrngConsumer {

    mapping(uint256 => uint256) public requestToToken;
    address gencore;
    INextGenCore public gencoreContract;
    INextGenAdmins private adminsContract;
    event Withdraw(address indexed _add, bool status, uint256 indexed funds);
    uint256 ethRequired;
    mapping(uint256 => uint256) public tokenToRequest;
    mapping(uint256 => uint256) public tokenIdToCollection;

    constructor(address _gencore, address _adminsContract, address _arRNG) ArrngConsumer(_arRNG) {
        gencore = _gencore;
        gencoreContract = INextGenCore(_gencore);
        adminsContract = INextGenAdmins(_adminsContract);
    }

    modifier FunctionAdminRequired(bytes4 _selector) {
        require(adminsContract.retrieveFunctionAdmin(msg.sender, _selector) == true || adminsContract.retrieveGlobalAdmin(msg.sender) == true, "Not allowed");
        _;
    }

    function requestRandomWords(uint256 tokenid, uint256 _ethRequired) public payable {
        require(msg.sender == gencore);
        uint256 requestId = arrngController.requestRandomWords{value: _ethRequired}(1, (address(this)));
        tokenToRequest[tokenid] = requestId;
        requestToToken[requestId] = tokenid;

    }

    function fulfillRandomWords(uint256 id, uint256[] memory numbers) internal override {
        gencoreContract.setTokenHash(tokenIdToCollection[requestToToken[id]], requestToToken[id], keccak256(abi.encodePacked(numbers,requestToToken[id])));
    }

    // function that calculates the random hash and returns it to the gencore contract
    function calculateTokenHash(uint256 _collectionID, uint256 _mintIndex, uint256 _saltfun_o) public {
        require(msg.sender == gencore);
        tokenIdToCollection[_mintIndex] = _collectionID;
        requestRandomWords(_mintIndex, ethRequired);
    }

    // function to update contracts

    function updateAdminContract(address _newadminsContract) public FunctionAdminRequired(this.updateAdminContract.selector) {
        require(INextGenAdmins(_newadminsContract).isAdminContract() == true, "Contract is not Admin");
        adminsContract = INextGenAdmins(_newadminsContract);
    }

    function updateCoreContract(address _gencore) public FunctionAdminRequired(this.updateCoreContract.selector) { 
        gencore = _gencore;
        gencoreContract = INextGenCore(_gencore);
    }

    // function to update cost

    function updateRNGCost(uint256 _ethRequired) public FunctionAdminRequired(this.updateRNGCost.selector) {
        ethRequired = _ethRequired;
    }

    // function to withdraw any balance from the smart contract

    function emergencyWithdraw() public FunctionAdminRequired(this.emergencyWithdraw.selector) {
        uint balance = address(this).balance;
        address admin = adminsContract.owner();
        (bool success, ) = payable(admin).call{value: balance}("");
        require(success, "ETH failed");
        emit Withdraw(msg.sender, success, balance);
    }

    receive() external payable {}

    // get randomizer contract status
    function isRandomizerContract() external view returns (bool) {
        return true;
    }
}