// File: Weeklies.sol



// SPDX-License-Identifier: MIT
// An example of a consumer contract that directly pays for each request.
pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/ConfirmedOwner.sol";
import "@chainlink/contracts/src/v0.8/VRFV2WrapperConsumerBase.sol";

contract Lottery is VRFV2WrapperConsumerBase, ConfirmedOwner {
    event RequestSent(uint256 requestId, uint32 numWords);
    event RequestFulfilled(
        uint256 requestId,
        uint256[] randomWords,
        uint256 payment
    );
    event PurchaseTicket(
        address purchaser,
        uint8 amount,
        uint256 priceOfTickets
    );
    event Deposit(address sender, uint256 amount);
    event LotteryConduct(address winner, uint256 prize);
    struct RequestStatus {
        uint256 paid; // amount paid in link
        bool fulfilled; // whether the request has been successfully fulfilled
        uint256[] randomWords;
    }
    mapping(uint256 => RequestStatus) public s_requests; /* requestId --> requestStatus */
    // List of addreses with amount of tickets
    uint256 public currentRound;

    struct LotteryData {
        mapping(address => uint8) Tickets;
        address[] players;
        uint256 ticketsSold;
        uint256 winningAmount;
        uint256 lotteryEndTime;
        address winner;
    }
    //Make an array of lotteries
    LotteryData[] public _Lottery;

    uint8 MAX_TICKET_LIMIT = 10; //the maximum tickets one user can purchase
    uint256 MAX_RANDOM_NUMBER = 100;
    uint256 public ticketPrice;

    uint256 public randomNumber = 0;

    // past requests Id.
    uint256[] public requestIds;
    uint256 public lastRequestId;

    uint32 callbackGasLimit = 300000;

    // The default is 3, but you can set this higher.
    uint16 requestConfirmations = 3;

    uint32 numWords = 2;

    // Address LINK
    address linkAddress = 0x514910771AF9Ca656af840dff83E8264EcF986CA;

    // address WRAPPER
    address wrapperAddress = 0x5A861794B927983406fCE1D062e00b9368d97Df6;
    address public treasury;

    constructor(address _treasury)
        ConfirmedOwner(msg.sender)
        VRFV2WrapperConsumerBase(linkAddress, wrapperAddress)
    {
        ticketPrice = 25000000000000000; //ticket price in ethers
        currentRound = 0; //The first round
        treasury = _treasury; //the treasury wallet
        _Lottery.push();
        _Lottery[currentRound].lotteryEndTime = block.timestamp + 7 days;
    }

    function requestRandomWords() internal returns (uint256 requestId) {
        requestId = requestRandomness(
            callbackGasLimit,
            requestConfirmations,
            numWords
        );
        s_requests[requestId] = RequestStatus({
            paid: VRF_V2_WRAPPER.calculateRequestPrice(callbackGasLimit),
            randomWords: new uint256[](1),
            fulfilled: false
        });
        requestIds.push(requestId);
        lastRequestId = requestId;
        emit RequestSent(requestId, numWords);
        return requestId;
    }

    function fulfillRandomWords(
        uint256 _requestId,
        uint256[] memory _randomWords
    ) internal override {
        LotteryData storage _currentLottery = _Lottery[currentRound];
        require(s_requests[_requestId].paid > 0, "request not found");

        s_requests[_requestId].fulfilled = true;
        s_requests[_requestId].randomWords = _randomWords;

        //random Number for the player index
        randomNumber = _randomWords[0] % _currentLottery.ticketsSold;
        //random Number for the lottery conduct chances
        uint256 LotteryConductChances = _randomWords[1] % MAX_RANDOM_NUMBER;
        if (LotteryConductChances < 5) {
            //if the lottery conduct chances are less then 5% dont conduct
            NextRound();
            emit RequestFulfilled(
                _requestId,
                _randomWords,
                s_requests[_requestId].paid
            );
        } else {
            //conduct the lottery
            _currentLottery.winner = _currentLottery.players[randomNumber];
            uint256 prizeAmount = address(this).balance;
            uint256 treasuryAmount = (prizeAmount * 5) / 100; //calculating 5% of the total pool amount
            _currentLottery.winningAmount = prizeAmount - treasuryAmount;
            payable(_currentLottery.winner).transfer(
                prizeAmount - treasuryAmount
            );
            payable(treasury).transfer(treasuryAmount);
            emit RequestFulfilled(
                _requestId,
                _randomWords,
                s_requests[_requestId].paid
            );
            NextRound();
            emit LotteryConduct(_currentLottery.winner, prizeAmount);
        }
    }

    /**
     * Allow withdraw of Link tokens from the contract
     */
    function withdrawLink() public onlyOwner {
        LinkTokenInterface link = LinkTokenInterface(linkAddress);
        require(
            link.transfer(msg.sender, link.balanceOf(address(this))),
            "Unable to transfer"
        );
    }

    function UpdateTicketPrice(uint256 _price) external onlyOwner {
        ticketPrice = _price;
    }

    function NextRound() internal {
        //Move to the next round
        currentRound += 1;
        _Lottery.push();
        _Lottery[currentRound].lotteryEndTime = block.timestamp + 7 days;
    }

    //should autamaticallly calculate the amouunct of ethers
    function PurchaseTickets(uint8 amountOfTickets) external payable {
        LotteryData storage _currentLottery = _Lottery[currentRound];
        require(
            _currentLottery.lotteryEndTime >= block.timestamp,
            "The lottery duration has expired!"
        );
        uint8 UserTicketsPurchased = _currentLottery.Tickets[msg.sender];
        //check and see if the user wants to purchase more then 10 tokens
        require(
            (amountOfTickets + UserTicketsPurchased) <= MAX_TICKET_LIMIT,
            "Cannot purchase more then 10 Tickets"
        );
        uint256 priceOfTickets = amountOfTickets * ticketPrice;
        require(
            address(msg.sender).balance >= priceOfTickets,
            "The user doesnt have enough ETH to purchase the tickets"
        );
        require(
            msg.value >= priceOfTickets,
            "The user has not provided enough ETH"
        );

        (bool sent, ) = address(this).call{value: priceOfTickets}("");
        require(sent, "Unable to transfer");
        _currentLottery.Tickets[msg.sender] += amountOfTickets;
        _currentLottery.ticketsSold += amountOfTickets;
        for (uint256 i = 0; i < amountOfTickets; i++) {
            _currentLottery.players.push(msg.sender);
        }
        emit PurchaseTicket(msg.sender, amountOfTickets, ticketPrice);
    }

    function ConductLottery() external onlyOwner {
        LotteryData storage _currentLottery = _Lottery[currentRound];
        require(
            _currentLottery.winner == address(0),
            "Winner already selected"
        );
        //genrate a random number between 0-99
        requestRandomWords();
    }

    function DespositETH() external payable onlyOwner {
        //The owner can deposit some ethers to the smart contract but cannot withdraw them
        uint256 amount = msg.value;
        require(amount > 0, "No amount to deposit");
        (
            bool sent, /*bytes memory data*/

        ) = payable(address(this)).call{value: msg.value}("");
        require(sent, "Unable to transfer the ethers");
        emit Deposit(msg.sender, amount);
    }

    function getBalance() external view returns (uint256) {
        return address(this).balance;
    }

    function getParticipants() external view returns (address[] memory) {
        LotteryData storage _currentLottery = _Lottery[currentRound];
        return _currentLottery.players;
    }

    function getTicketsPurchased(address _user, uint256 _index)
        external
        view
        returns (uint8)
    {
        LotteryData storage _IndexLottery = _Lottery[_index];
        return _IndexLottery.Tickets[_user];
    }

    receive() external payable {
        //to receive the ETH
    }
}

// File: @chainlink/contracts/src/v0.8/VRFV2WrapperConsumerBase.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./interfaces/LinkTokenInterface.sol";
import "./interfaces/VRFV2WrapperInterface.sol";

/** *******************************************************************************
 * @notice Interface for contracts using VRF randomness through the VRF V2 wrapper
 * ********************************************************************************
 * @dev PURPOSE
 *
 * @dev Create VRF V2 requests without the need for subscription management. Rather than creating
 * @dev and funding a VRF V2 subscription, a user can use this wrapper to create one off requests,
 * @dev paying up front rather than at fulfillment.
 *
 * @dev Since the price is determined using the gas price of the request transaction rather than
 * @dev the fulfillment transaction, the wrapper charges an additional premium on callback gas
 * @dev usage, in addition to some extra overhead costs associated with the VRFV2Wrapper contract.
 * *****************************************************************************
 * @dev USAGE
 *
 * @dev Calling contracts must inherit from VRFV2WrapperConsumerBase. The consumer must be funded
 * @dev with enough LINK to make the request, otherwise requests will revert. To request randomness,
 * @dev call the 'requestRandomness' function with the desired VRF parameters. This function handles
 * @dev paying for the request based on the current pricing.
 *
 * @dev Consumers must implement the fullfillRandomWords function, which will be called during
 * @dev fulfillment with the randomness result.
 */
abstract contract VRFV2WrapperConsumerBase {
  LinkTokenInterface internal immutable LINK;
  VRFV2WrapperInterface internal immutable VRF_V2_WRAPPER;

  /**
   * @param _link is the address of LinkToken
   * @param _vrfV2Wrapper is the address of the VRFV2Wrapper contract
   */
  constructor(address _link, address _vrfV2Wrapper) {
    LINK = LinkTokenInterface(_link);
    VRF_V2_WRAPPER = VRFV2WrapperInterface(_vrfV2Wrapper);
  }

  /**
   * @dev Requests randomness from the VRF V2 wrapper.
   *
   * @param _callbackGasLimit is the gas limit that should be used when calling the consumer's
   *        fulfillRandomWords function.
   * @param _requestConfirmations is the number of confirmations to wait before fulfilling the
   *        request. A higher number of confirmations increases security by reducing the likelihood
   *        that a chain re-org changes a published randomness outcome.
   * @param _numWords is the number of random words to request.
   *
   * @return requestId is the VRF V2 request ID of the newly created randomness request.
   */
  function requestRandomness(
    uint32 _callbackGasLimit,
    uint16 _requestConfirmations,
    uint32 _numWords
  ) internal returns (uint256 requestId) {
    LINK.transferAndCall(
      address(VRF_V2_WRAPPER),
      VRF_V2_WRAPPER.calculateRequestPrice(_callbackGasLimit),
      abi.encode(_callbackGasLimit, _requestConfirmations, _numWords)
    );
    return VRF_V2_WRAPPER.lastRequestId();
  }

  /**
   * @notice fulfillRandomWords handles the VRF V2 wrapper response. The consuming contract must
   * @notice implement it.
   *
   * @param _requestId is the VRF V2 request ID.
   * @param _randomWords is the randomness result.
   */
  function fulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) internal virtual;

  function rawFulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) external {
    require(msg.sender == address(VRF_V2_WRAPPER), "only VRF V2 wrapper can fulfill");
    fulfillRandomWords(_requestId, _randomWords);
  }
}

// File: @chainlink/contracts/src/v0.8/ConfirmedOwner.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./ConfirmedOwnerWithProposal.sol";

/**
 * @title The ConfirmedOwner contract
 * @notice A contract with helpers for basic contract ownership.
 */
contract ConfirmedOwner is ConfirmedOwnerWithProposal {
  constructor(address newOwner) ConfirmedOwnerWithProposal(newOwner, address(0)) {}
}

// File: @chainlink/contracts/src/v0.8/interfaces/VRFV2WrapperInterface.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface VRFV2WrapperInterface {
  /**
   * @return the request ID of the most recent VRF V2 request made by this wrapper. This should only
   * be relied option within the same transaction that the request was made.
   */
  function lastRequestId() external view returns (uint256);

  /**
   * @notice Calculates the price of a VRF request with the given callbackGasLimit at the current
   * @notice block.
   *
   * @dev This function relies on the transaction gas price which is not automatically set during
   * @dev simulation. To estimate the price at a specific gas price, use the estimatePrice function.
   *
   * @param _callbackGasLimit is the gas limit used to estimate the price.
   */
  function calculateRequestPrice(uint32 _callbackGasLimit) external view returns (uint256);

  /**
   * @notice Estimates the price of a VRF request with a specific gas limit and gas price.
   *
   * @dev This is a convenience function that can be called in simulation to better understand
   * @dev pricing.
   *
   * @param _callbackGasLimit is the gas limit used to estimate the price.
   * @param _requestGasPriceWei is the gas price in wei used for the estimation.
   */
  function estimateRequestPrice(uint32 _callbackGasLimit, uint256 _requestGasPriceWei) external view returns (uint256);
}

// File: @chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol



// SPDX-License-Identifier: MIT
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/ConfirmedOwnerWithProposal.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./interfaces/OwnableInterface.sol";

/**
 * @title The ConfirmedOwner contract
 * @notice A contract with helpers for basic contract ownership.
 */
contract ConfirmedOwnerWithProposal is OwnableInterface {
  address private s_owner;
  address private s_pendingOwner;

  event OwnershipTransferRequested(address indexed from, address indexed to);
  event OwnershipTransferred(address indexed from, address indexed to);

  constructor(address newOwner, address pendingOwner) {
    require(newOwner != address(0), "Cannot set owner to zero");

    s_owner = newOwner;
    if (pendingOwner != address(0)) {
      _transferOwnership(pendingOwner);
    }
  }

  /**
   * @notice Allows an owner to begin transferring ownership to a new address,
   * pending.
   */
  function transferOwnership(address to) public override onlyOwner {
    _transferOwnership(to);
  }

  /**
   * @notice Allows an ownership transfer to be completed by the recipient.
   */
  function acceptOwnership() external override {
    require(msg.sender == s_pendingOwner, "Must be proposed owner");

    address oldOwner = s_owner;
    s_owner = msg.sender;
    s_pendingOwner = address(0);

    emit OwnershipTransferred(oldOwner, msg.sender);
  }

  /**
   * @notice Get the current owner
   */
  function owner() public view override returns (address) {
    return s_owner;
  }

  /**
   * @notice validate, transfer ownership, and emit relevant events
   */
  function _transferOwnership(address to) private {
    require(to != msg.sender, "Cannot transfer to self");

    s_pendingOwner = to;

    emit OwnershipTransferRequested(s_owner, to);
  }

  /**
   * @notice validate access
   */
  function _validateOwnership() internal view {
    require(msg.sender == s_owner, "Only callable by owner");
  }

  /**
   * @notice Reverts if called by anyone other than the contract owner.
   */
  modifier onlyOwner() {
    _validateOwnership();
    _;
  }
}

// File: @chainlink/contracts/src/v0.8/interfaces/OwnableInterface.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface OwnableInterface {
  function owner() external returns (address);

  function transferOwnership(address recipient) external;

  function acceptOwnership() external;
}