// SPDX-License-Identifier: MIT
// File: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.3/contracts/utils/Context.sol
pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// File: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.3/contracts/access/Ownable.sol



pragma solidity ^0.8.0;


/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _setOwner(_msgSender());
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _setOwner(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _setOwner(newOwner);
    }

    function _setOwner(address newOwner) private {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: @chainlink/contracts/src/v0.8/VRFRequestIDBase.sol


pragma solidity ^0.8.0;

contract VRFRequestIDBase {

  /**
   * @notice returns the seed which is actually input to the VRF coordinator
   *
   * @dev To prevent repetition of VRF output due to repetition of the
   * @dev user-supplied seed, that seed is combined in a hash with the
   * @dev user-specific nonce, and the address of the consuming contract. The
   * @dev risk of repetition is mostly mitigated by inclusion of a blockhash in
   * @dev the final seed, but the nonce does protect against repetition in
   * @dev requests which are included in a single block.
   *
   * @param _userSeed VRF seed input provided by user
   * @param _requester Address of the requesting contract
   * @param _nonce User-specific nonce at the time of the request
   */
  function makeVRFInputSeed(
    bytes32 _keyHash,
    uint256 _userSeed,
    address _requester,
    uint256 _nonce
  )
    internal
    pure
    returns (
      uint256
    )
  {
    return uint256(keccak256(abi.encode(_keyHash, _userSeed, _requester, _nonce)));
  }

  /**
   * @notice Returns the id for this request
   * @param _keyHash The serviceAgreement ID to be used for this request
   * @param _vRFInputSeed The seed to be passed directly to the VRF
   * @return The id for this request
   *
   * @dev Note that _vRFInputSeed is not the seed passed by the consuming
   * @dev contract, but the one generated by makeVRFInputSeed
   */
  function makeRequestId(
    bytes32 _keyHash,
    uint256 _vRFInputSeed
  )
    internal
    pure
    returns (
      bytes32
    )
  {
    return keccak256(abi.encodePacked(_keyHash, _vRFInputSeed));
  }
}
// File: @chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol


pragma solidity ^0.8.0;

interface LinkTokenInterface {

  function allowance(
    address owner,
    address spender
  )
    external
    view
    returns (
      uint256 remaining
    );

  function approve(
    address spender,
    uint256 value
  )
    external
    returns (
      bool success
    );

  function balanceOf(
    address owner
  )
    external
    view
    returns (
      uint256 balance
    );

  function decimals()
    external
    view
    returns (
      uint8 decimalPlaces
    );

  function decreaseApproval(
    address spender,
    uint256 addedValue
  )
    external
    returns (
      bool success
    );

  function increaseApproval(
    address spender,
    uint256 subtractedValue
  ) external;

  function name()
    external
    view
    returns (
      string memory tokenName
    );

  function symbol()
    external
    view
    returns (
      string memory tokenSymbol
    );

  function totalSupply()
    external
    view
    returns (
      uint256 totalTokensIssued
    );

  function transfer(
    address to,
    uint256 value
  )
    external
    returns (
      bool success
    );

  function transferAndCall(
    address to,
    uint256 value,
    bytes calldata data
  )
    external
    returns (
      bool success
    );

  function transferFrom(
    address from,
    address to,
    uint256 value
  )
    external
    returns (
      bool success
    );

}

// File: @chainlink/contracts/src/v0.8/VRFConsumerBase.sol


pragma solidity ^0.8.0;



/** ****************************************************************************
 * @notice Interface for contracts using VRF randomness
 * *****************************************************************************
 * @dev PURPOSE
 *
 * @dev Reggie the Random Oracle (not his real job) wants to provide randomness
 * @dev to Vera the verifier in such a way that Vera can be sure he's not
 * @dev making his output up to suit himself. Reggie provides Vera a public key
 * @dev to which he knows the secret key. Each time Vera provides a seed to
 * @dev Reggie, he gives back a value which is computed completely
 * @dev deterministically from the seed and the secret key.
 *
 * @dev Reggie provides a proof by which Vera can verify that the output was
 * @dev correctly computed once Reggie tells it to her, but without that proof,
 * @dev the output is indistinguishable to her from a uniform random sample
 * @dev from the output space.
 *
 * @dev The purpose of this contract is to make it easy for unrelated contracts
 * @dev to talk to Vera the verifier about the work Reggie is doing, to provide
 * @dev simple access to a verifiable source of randomness.
 * *****************************************************************************
 * @dev USAGE
 *
 * @dev Calling contracts must inherit from VRFConsumerBase, and can
 * @dev initialize VRFConsumerBase's attributes in their constructor as
 * @dev shown:
 *
 * @dev   contract VRFConsumer {
 * @dev     constuctor(<other arguments>, address _vrfCoordinator, address _link)
 * @dev       VRFConsumerBase(_vrfCoordinator, _link) public {
 * @dev         <initialization with other arguments goes here>
 * @dev       }
 * @dev   }
 *
 * @dev The oracle will have given you an ID for the VRF keypair they have
 * @dev committed to (let's call it keyHash), and have told you the minimum LINK
 * @dev price for VRF service. Make sure your contract has sufficient LINK, and
 * @dev call requestRandomness(keyHash, fee, seed), where seed is the input you
 * @dev want to generate randomness from.
 *
 * @dev Once the VRFCoordinator has received and validated the oracle's response
 * @dev to your request, it will call your contract's fulfillRandomness method.
 *
 * @dev The randomness argument to fulfillRandomness is the actual random value
 * @dev generated from your seed.
 *
 * @dev The requestId argument is generated from the keyHash and the seed by
 * @dev makeRequestId(keyHash, seed). If your contract could have concurrent
 * @dev requests open, you can use the requestId to track which seed is
 * @dev associated with which randomness. See VRFRequestIDBase.sol for more
 * @dev details. (See "SECURITY CONSIDERATIONS" for principles to keep in mind,
 * @dev if your contract could have multiple requests in flight simultaneously.)
 *
 * @dev Colliding `requestId`s are cryptographically impossible as long as seeds
 * @dev differ. (Which is critical to making unpredictable randomness! See the
 * @dev next section.)
 *
 * *****************************************************************************
 * @dev SECURITY CONSIDERATIONS
 *
 * @dev A method with the ability to call your fulfillRandomness method directly
 * @dev could spoof a VRF response with any random value, so it's critical that
 * @dev it cannot be directly called by anything other than this base contract
 * @dev (specifically, by the VRFConsumerBase.rawFulfillRandomness method).
 *
 * @dev For your users to trust that your contract's random behavior is free
 * @dev from malicious interference, it's best if you can write it so that all
 * @dev behaviors implied by a VRF response are executed *during* your
 * @dev fulfillRandomness method. If your contract must store the response (or
 * @dev anything derived from it) and use it later, you must ensure that any
 * @dev user-significant behavior which depends on that stored value cannot be
 * @dev manipulated by a subsequent VRF request.
 *
 * @dev Similarly, both miners and the VRF oracle itself have some influence
 * @dev over the order in which VRF responses appear on the blockchain, so if
 * @dev your contract could have multiple VRF requests in flight simultaneously,
 * @dev you must ensure that the order in which the VRF responses arrive cannot
 * @dev be used to manipulate your contract's user-significant behavior.
 *
 * @dev Since the ultimate input to the VRF is mixed with the block hash of the
 * @dev block in which the request is made, user-provided seeds have no impact
 * @dev on its economic security properties. They are only included for API
 * @dev compatability with previous versions of this contract.
 *
 * @dev Since the block hash of the block which contains the requestRandomness
 * @dev call is mixed into the input to the VRF *last*, a sufficiently powerful
 * @dev miner could, in principle, fork the blockchain to evict the block
 * @dev containing the request, forcing the request to be included in a
 * @dev different block with a different hash, and therefore a different input
 * @dev to the VRF. However, such an attack would incur a substantial economic
 * @dev cost. This cost scales with the number of blocks the VRF oracle waits
 * @dev until it calls responds to a request.
 */
abstract contract VRFConsumerBase is VRFRequestIDBase {

  /**
   * @notice fulfillRandomness handles the VRF response. Your contract must
   * @notice implement it. See "SECURITY CONSIDERATIONS" above for important
   * @notice principles to keep in mind when implementing your fulfillRandomness
   * @notice method.
   *
   * @dev VRFConsumerBase expects its subcontracts to have a method with this
   * @dev signature, and will call it once it has verified the proof
   * @dev associated with the randomness. (It is triggered via a call to
   * @dev rawFulfillRandomness, below.)
   *
   * @param requestId The Id initially returned by requestRandomness
   * @param randomness the VRF output
   */
  function fulfillRandomness(
    bytes32 requestId,
    uint256 randomness
  )
    internal
    virtual;

  /**
   * @dev In order to keep backwards compatibility we have kept the user
   * seed field around. We remove the use of it because given that the blockhash
   * enters later, it overrides whatever randomness the used seed provides.
   * Given that it adds no security, and can easily lead to misunderstandings,
   * we have removed it from usage and can now provide a simpler API.
   */
  uint256 constant private USER_SEED_PLACEHOLDER = 0;

  /**
   * @notice requestRandomness initiates a request for VRF output given _seed
   *
   * @dev The fulfillRandomness method receives the output, once it's provided
   * @dev by the Oracle, and verified by the vrfCoordinator.
   *
   * @dev The _keyHash must already be registered with the VRFCoordinator, and
   * @dev the _fee must exceed the fee specified during registration of the
   * @dev _keyHash.
   *
   * @dev The _seed parameter is vestigial, and is kept only for API
   * @dev compatibility with older versions. It can't *hurt* to mix in some of
   * @dev your own randomness, here, but it's not necessary because the VRF
   * @dev oracle will mix the hash of the block containing your request into the
   * @dev VRF seed it ultimately uses.
   *
   * @param _keyHash ID of public key against which randomness is generated
   * @param _fee The amount of LINK to send with the request
   *
   * @return requestId unique ID for this request
   *
   * @dev The returned requestId can be used to distinguish responses to
   * @dev concurrent requests. It is passed as the first argument to
   * @dev fulfillRandomness.
   */
  function requestRandomness(
    bytes32 _keyHash,
    uint256 _fee
  )
    internal
    returns (
      bytes32 requestId
    )
  {
    LINK.transferAndCall(vrfCoordinator, _fee, abi.encode(_keyHash, USER_SEED_PLACEHOLDER));
    // This is the seed passed to VRFCoordinator. The oracle will mix this with
    // the hash of the block containing this request to obtain the seed/input
    // which is finally passed to the VRF cryptographic machinery.
    uint256 vRFSeed  = makeVRFInputSeed(_keyHash, USER_SEED_PLACEHOLDER, address(this), nonces[_keyHash]);
    // nonces[_keyHash] must stay in sync with
    // VRFCoordinator.nonces[_keyHash][this], which was incremented by the above
    // successful LINK.transferAndCall (in VRFCoordinator.randomnessRequest).
    // This provides protection against the user repeating their input seed,
    // which would result in a predictable/duplicate output, if multiple such
    // requests appeared in the same block.
    nonces[_keyHash] = nonces[_keyHash] + 1;
    return makeRequestId(_keyHash, vRFSeed);
  }

  LinkTokenInterface immutable internal LINK;
  address immutable private vrfCoordinator;

  // Nonces for each VRF key from which randomness has been requested.
  //
  // Must stay in sync with VRFCoordinator[_keyHash][this]
  mapping(bytes32 /* keyHash */ => uint256 /* nonce */) private nonces;

  /**
   * @param _vrfCoordinator address of VRFCoordinator contract
   * @param _link address of LINK token contract
   *
   * @dev https://docs.chain.link/docs/link-token-contracts
   */
  constructor(
    address _vrfCoordinator,
    address _link
  ) {
    vrfCoordinator = _vrfCoordinator;
    LINK = LinkTokenInterface(_link);
  }

  // rawFulfillRandomness is called by VRFCoordinator when it receives a valid VRF
  // proof. rawFulfillRandomness then calls fulfillRandomness, after validating
  // the origin of the call
  function rawFulfillRandomness(
    bytes32 requestId,
    uint256 randomness
  )
    external
  {
    require(msg.sender == vrfCoordinator, "Only VRFCoordinator can fulfill");
    fulfillRandomness(requestId, randomness);
  }
}

// File: contracts/SquidRaffle.sol


pragma solidity ^0.8.7;



abstract contract ReentrancyGuard {
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;
    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    modifier nonReentrant() {
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
        _status = _ENTERED;

        _;

        _status = _NOT_ENTERED;
    }
}

interface ISquidERC20 {
    function transfer(address recipient, uint amount) external returns (bool);
    function balanceOf(address account) external view returns (uint);
}


interface ISquidNFT {
    function ownerOf(uint256 tokenId) external view returns (address owner);
    function tokenByIndex(uint256 index) external view returns (uint256);
    function totalSupply() external view returns (uint256);
    function getConsonantsIndex(uint256 _tokenId) external view returns (uint8[3] memory);    
}


/**
* @title Squid Game Card NFT Raffle Event Contract
* https://twitter.com/GoSquidGame
*
*/

contract SquidRaffle is VRFConsumerBase, ReentrancyGuard, Ownable {
    
    bool public active = false;
    bool public ethPayDone = false;
    uint16 constant totalRounds = 20;
    uint16 constant tokensPerRound = 456;
    uint16 public ojingeoPrize = 1; 
    uint16 public grandPrize = 5; 
    address public nftAddress;
    address public squidAddress;
    address payable public donator;
    uint256 public constant roundWinnerBonus = 100000; //Squid ERC20 token bonus per round winner

    bytes32 internal keyHash;
    uint256 internal fee;
    address internal link;
    uint256 public ethWithdrawTime = block.timestamp + 30 days;

    struct RoundWinner {
        uint16 tokenId;
        uint16 tokenIndex;
        uint16 ethAmount;
        address winnerAddress;
    }
    
    struct SquidBonus {
        bool notPaid;
        address paidAddress;
    }
    
    mapping(uint256 => RoundWinner) public winners; // roundId => Winner
    mapping(uint256 => bytes32) public requests; // roundId => requestId
    mapping(bytes32 => uint256) public requestIds; //
    mapping(bytes32 => uint256) public results; // requestId => random seed by chainlink
    mapping(uint256 => SquidBonus) public squidPrizes; // tokenId -> squid erc20 bonus paid to
    
    /** Rinkeby 
     * LINK	Token 0x01BE23585060835E02B77ef475b0Cc51aA1e0709
     * VRF Coordinator	0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B
     * Key Hash	0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311
     * Fee	0.1 LINK
     * 
     * Ethereum Mainnet
     * LINK Token   0x514910771AF9Ca656af840dff83E8264EcF986CA
     * VRF Coordinator	0xf0d54349aDdcf704F77AE15b96510dEA15cb7952
     * Key Hash	0xAA77729D3466CA35AE8D28B3BBAC7CC36A5031EFDC430821C02BC31A238AF445
     * Fee	2 LINK -
     */

    constructor(address _nft) payable  
        VRFConsumerBase(
            0xf0d54349aDdcf704F77AE15b96510dEA15cb7952, // VRF Coordinator
            0x514910771AF9Ca656af840dff83E8264EcF986CA  // LINK Token
        )
    {
        keyHash = 0xAA77729D3466CA35AE8D28B3BBAC7CC36A5031EFDC430821C02BC31A238AF445;
        fee = 2 * 10 ** 18; // 0.1 LINK (Varies by network)
        nftAddress = _nft;        
        link = 0x514910771AF9Ca656af840dff83E8264EcF986CA;
    }
    
    /** 
     * Requests randomness 
     */
    function getRandomNumber() internal returns (bytes32 requestId) {
        require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK - fill contract with faucet");
        return requestRandomness(keyHash, fee);
    }

    /**
     * Callback function used by VRF Coordinator
     */
    function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
        results[requestId] = randomness;
        uint tokenId = requestIds[requestId];
        bool picked = _pickWinner(tokenId, randomness);
        require(picked, 'Callback function failed');
    }
    

    // function withdrawLink() external {} - Implement a withdraw function to avoid locking your LINK in the contract
    
    function startRaffle() external onlyOwner {
        active = true;
    }
    
    function allowSquidClaim(address _squidAddr) external onlyOwner {
        squidAddress = _squidAddr;
    }    
    
    function requestRoundWinner(uint _roundId) public {
        require(active == true, 'Raffle is not active yet');
        require(_roundId > 0 && _roundId <= totalRounds, 'invalid roundId');
        require(requests[_roundId] == bytes32(0), 'This roundId already requested');
        if (_roundId > 1) {
            require(winners[_roundId-1].tokenId != uint16(0), 'The previous round winner pick is not done yet');
        }
        ISquidNFT NFT = ISquidNFT(nftAddress);    
        uint tokenCounts = NFT.totalSupply();
        uint startIdx = tokensPerRound * (_roundId - 1) + 1;
        uint endIdx = startIdx + tokensPerRound - 1;
        require(endIdx <= tokenCounts, 'This round is not finished yet');
        bytes32 requestId = getRandomNumber();
        requests[_roundId] = requestId;
        requestIds[requestId] = _roundId;
    }
    
    function hasRandom(uint _roundId) public view returns (bool) {
        bytes32 requestId = requests[_roundId];
        if (results[requestId] != uint(0)) {
            return true;
        } else {
            return false;
        }
    }
    
    function getRoundRand(uint _roundId) public view returns (uint)  {
        return results[requests[_roundId]];
    }

    function deposit() external payable {
        require(donator == address(0) || donator == msg.sender, '');
        if (donator == address(0)) {
            donator = payable(msg.sender);
        }
        
    }

    function withdraw() external onlyOwner { 
        require(!active || ethPayDone == true || ethWithdrawTime < block.timestamp, 'Waiting time');
        uint amount = address(this).balance;
        (bool success, ) = donator.call{value: amount}("");
        require(success, "Failed to send Ether");
    }
    
    function withdrawLINK() external onlyOwner {
        require(!active || ethPayDone == true || ethWithdrawTime < block.timestamp, 'Waiting time');
        LinkTokenInterface linkAddr = LinkTokenInterface(link);
        require(linkAddr.transfer(msg.sender, linkAddr.balanceOf(address(this))), "Unable to transfer");
    } 
    
    function withdrawSquid() external onlyOwner {
        require(!active || ethWithdrawTime + 30 days < block.timestamp, 'Waiting time');
        ISquidERC20 Squid = ISquidERC20(squidAddress);
        require(Squid.transfer(_msgSender(), Squid.balanceOf(address(this))), "Unable to transfer");   
    }    

    function getBalance() public view returns (uint) {
        return address(this).balance;
    }       
    
    function _pickWinner(uint _roundId, uint _randSeed) internal nonReentrant returns (bool) {
        ISquidNFT NFT = ISquidNFT(nftAddress);    
        uint startIdx = tokensPerRound * (_roundId - 1) + 1;
        uint pickIdx = _randSeed  % tokensPerRound;
        uint winnerIdx = startIdx + pickIdx;
        uint tokenId = NFT.tokenByIndex(winnerIdx);
        address winnerAddress = NFT.ownerOf(tokenId);
        uint8[3] memory consonants = NFT.getConsonantsIndex(tokenId);
        uint16 ethAmount = 0;
        if (consonants[0] == 7 && consonants[1] == 14 && consonants[2] == 4) {         
            ethAmount = ojingeoPrize;
        } else if (consonants[0] == consonants[1] && consonants[0] == consonants[2]) {
            if (_roundId > 5) {
                ethAmount = grandPrize;
                grandPrize = 1;
            } else {
                ethAmount = 1;                           
            }
        } 
        winners[_roundId] = RoundWinner(uint16(tokenId), uint16(winnerIdx), ethAmount, winnerAddress);
        squidPrizes[tokenId] = SquidBonus(true, address(0));
        if (_roundId == totalRounds) {
            ethPayDone = true;
        }
        if (ethAmount > 0) {
            (bool sent, ) = winnerAddress.call{value: ethAmount*10**18}('');
            require(sent, 'Failed to send Ether');
        } 
        return true;
    }
        
    function claimSquid(uint _tokenId) public nonReentrant {
        require(squidAddress != address(0), 'Squid ERC20 address is not set yet');
        require(squidPrizes[_tokenId].notPaid == true, 'Already paid');
        ISquidNFT NFT = ISquidNFT(nftAddress);    
        address tokenOwner = NFT.ownerOf(_tokenId);
        require(tokenOwner == _msgSender(), 'The token owner is not the msg.sender');
        squidPrizes[_tokenId] = SquidBonus(false, tokenOwner);
        ISquidERC20 Squid = ISquidERC20(squidAddress);
        require(Squid.transfer(tokenOwner, roundWinnerBonus*10**18), "Unable to transfer");        
    }
    
    function clearRequest(uint _roundId) external onlyOwner {
        require(winners[_roundId].tokenId == uint16(0));
        requests[_roundId] = bytes32(0);
    }
}