// Contract: ERC721
// Target function: function _safeTransfer(address from, address to, uint256 tokenId, bytes memory _data) internal virtual

    mapping (uint256 => address) private _tokenApprovals;

    // Mapping from owner to operator approvals
    mapping (address => mapping (address => bool)) private _operatorApprovals;

    // How many NFTs have been issued for each period;
    mapping (uint8 => uint16) public madePerPeriod;


    // unix time stamps in seconds for each month for the next 50 months
    // startTimes[49] means that you are predicting no flippening in the next 4 years
    // GMT Time
    uint32[] public startTimes = [1619827200, 1622505600, 1625097600, 1627776000, 1630454400, 1633046400,
    1635724800, 1638320400, 1640998800, 1643677200, 1646096400, 1648771200, 1651363200, 1654041600, 1656633600, 1659312000,
    1661990400, 1664582400, 1667260800, 1669856400, 1672534800, 1675213200, 1677632400, 1680307200, 1682899200, 1685577600,
    1688169600, 1690848000, 1693526400, 1696118400, 1698796800, 1701392400, 1704070800, 1706749200, 1709254800, 1711929600,
    1714521600, 1717200000, 1719792000, 1722470400, 1725148800, 1727740800, 1730419200, 1733014800, 1735693200, 1738371600,
    1740790800, 1743465600, 1746057600, 1748736000];


	// prices[x] represents the price of the xth token purchased that month in wei
    uint64[] public prices = [10000000000000000
    , 12500000000000000
    , 15000000000000000
    , 17500000000000000
    , 20000000000000000
    , 25000000000000000
    , 30000000000000000
    , 35000000000000000
    , 40000000000000000
    , 47500000000000000
    , 55000000000000000
    , 62500000000000000
    , 70000000000000000
    , 80000000000000000
    , 90000000000000000
    , 100000000000000000
    , 110000000000000000
    , 122500000000000000
    , 135000000000000000
    , 147500000000000000
    , 160000000000000000
    , 175000000000000000
    , 190000000000000000
    , 205000000000000000
    , 220000000000000000
    , 237500000000000000
    , 255000000000000000
    , 272500000000000000
    , 290000000000000000
    , 310000000000000000
    , 310000000000000000
    ];

   
    uint256 public numTokens = 0;

    // Which NFTs have already claimed their prize
    mapping (uint256 => bool) idsClaimed;

    // How Many NFTs have successfully claimed their prizes
    uint8 tokenClaimCount;

    address payable contractOwner;

    // The unix timestamp of when the flippening has occured
    // and the contract has been called, or 0.
    uint64 public flippeningTime = 0;

    // Amount [in wei] currently payable to winners
    uint256 public payoutBalance = 0;

    // Amount to be paid to devs and artists
    uint256 public artistBalance = 0;

	// Address of gitcoin's multisig wallet
	// gitcoin balance can only be sent to this address.
    address payable gitcoinMatchingMultiSig =  payable (0xde21F729137C5Af1b01d73aF1dC21eFfa2B8a0d6);

    // Amount [in wei] currently payable to gitcoin matching funds
    uint256 public gitcoinBalance = 0;
    
    // historical amount [in wei] already paid to gitcoin matching funds
    uint256 public gitcoinHistorical = 0;

    //price where ETH market cap flips Btc
    // This is how many wei can be bought for 1 btc
    int256 flippeningRatio = 6179713906000000000;

    /**
     * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
     */
    constructor () {
        _name = "FlippeningNFT";
        _symbol = "FLIP";

        // https://data.chain.link/btc-eth
		// Mainnet chainlink address
		// https://etherscan.io/address/0xdeb288F737066589598e9214E782fa5A8eD689e8
        priceFeed = AggregatorV3Interface(0xdeb288F737066589598e9214E782fa5A8eD689e8);
        contractOwner = payable(msg.sender);
    }


    function getLatestPrice() public view returns (int) {
        (
        uint80 roundID,
        int price,
        uint startedAt,
        uint timeStamp,
        uint80 answeredInRound
        ) = priceFeed.latestRoundData();
        return price;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return interfaceId == type(IERC721).interfaceId
        || interfaceId == type(IERC721Metadata).interfaceId
        || super.supportsInterface(interfaceId);
    }

    /**
     * @dev See {IERC721-balanceOf}.
     */
    function balanceOf(address owner) public view virtual override returns (uint256) {
        require(owner != address(0), "ERC721: balance query for the zero address");
        return _balances[owner];
    }

    function monthOf(uint256 tokenId) public view returns (uint8) {
        return _tokenMonths[tokenId];
    }

    /**
     * @dev See {IERC721-ownerOf}.
     */
    function ownerOf(uint256 tokenId) public view virtual override returns (address) {
        address owner = _owners[tokenId];
        require(owner != address(0), "ERC721: owner query for nonexistent token");
        return owner;
    }

    /**
     * @dev See {IERC721Metadata-name}.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev See {IERC721Metadata-symbol}.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev See {IERC721Metadata-tokenURI}.
     */
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

        string memory baseURI = _baseURI();
        return bytes(baseURI).length > 0
        ? string(abi.encodePacked(baseURI, tokenId.toString()))
        : '';
    }

    /**
     * @dev Base URI for computing {tokenURI}. Empty by default, can be overriden
     * in child contracts.
     */
    function _baseURI() internal view virtual returns (string memory) {
        return "https://www.ethflippening.com/uri/";
    }

    function createNFT(uint8 period) public payable {

        // Calculate the price and make sure they have sent enough
        require(msg.value >= prices[madePerPeriod[period]], 'You need to send at least the current price');

        // Make sure that the maximum number of tokens have not already been created for that period.
        require(madePerPeriod[period] < 31, 'Max tokens for that period already minted');

        // Tokens can no longer be minted after the flippening has been recorded
        require(flippeningTime == 0, 'Tokens must be created before flippening is recorded');
        
        // Only 50 periods are available
        require(period < 50, 'Only 50 months are available');

        // Create the token
        _mint(msg.sender, numTokens);

        // Update token month
        _tokenMonths[numTokens] = period;

        // Increment
        numTokens ++;

        // Update number per period
        madePerPeriod[period] ++;

        // Handle money sent
        payoutBalance = payoutBalance + msg.value * 45 / 100;
        gitcoinBalance = gitcoinBalance + msg.value * 6 / 100;
        artistBalance = artistBalance + msg.value * 49 / 100;
    }


    // This function is called when someone claims a prize
    // but can also be called by anyone to record the event
    // without claiming.
    function recordFlippening() public {
        int256 ratio = getLatestPrice();
     
       

        require(flippeningTime == 0, 'Flippening has already been recorded');

        // Check if the flippening has happened via the ratio
        // if so, record the current time
        if (ratio <= flippeningRatio) {
            flippeningTime = uint64(block.timestamp);
        }

        // Record the current time if we have already passed the end
        // and no flippening has occured
        if (flippeningTime == 0 && ratio > flippeningRatio && uint32(block.timestamp) > uint32(startTimes[49]) ) {
            flippeningTime = uint64(block.timestamp);
        }
    }


    function tokenClaimed(uint256 tokenId) public view returns(bool)  {
        return idsClaimed[tokenId];
    }

    function claimPrize(uint256 tokenId) public {

        require(msg.sender == ownerOf(tokenId), 'Only the token owner may claim');

        require(idsClaimed[tokenId] == false, 'This token has already claimed the prize');

        // Check if flippening has occured
        // If so, set timestamp
        if (flippeningTime == 0) {
            recordFlippening();
        }

        // Make sure the token is in the correct period
        // Either flippening time is greater than start time of the month you picked and less than the month you picked + 1
        // Or, you have picked the last month and the flippening time is greater than this

        require ((flippeningTime > startTimes[_tokenMonths[tokenId]] && flippeningTime < startTimes[_tokenMonths[tokenId] + 1]) ||
            (flippeningTime > startTimes[49] && _tokenMonths[tokenId] == 49), "You have not won");

        idsClaimed[tokenId] = true;

        // Transfer prize amount to token owner
        uint256 weiToSend = payoutBalance / (madePerPeriod[_tokenMonths[tokenId]] - tokenClaimCount);
        tokenClaimCount++;
        payoutBalance = payoutBalance - weiToSend;
    
        address payable winner = payable(msg.sender);
        (bool success, ) = winner.call{value: weiToSend}("");
        require(success, "Transfer failed.");
       
    }


    // Anyone can call this function to send gitcoin's share to their multisig wallet
    // This can be called with any balance, and the caller will be responsible for the
    // gas fee

    function donateToGitcoin() public {
        if (gitcoinBalance > 0) {
            uint256 toSend = gitcoinBalance;
            gitcoinHistorical += gitcoinBalance;
            gitcoinBalance = 0;
            (bool success, ) = gitcoinMatchingMultiSig.call{value: toSend}("");
            require(success, "Transfer failed.");
        }

    }

    // If called by an owner, withdraw the approriate amount
    function withdraw() public {
        require(payable(msg.sender) == contractOwner, 'Only the owner may call this function');
        uint256 toSend = artistBalance;
        artistBalance = 0;
        (bool success, ) = contractOwner.call{value: toSend}("");
        require(success, "Transfer failed.");
    }

    /**
     * @dev See {IERC721-approve}.
     */
    function approve(address to, uint256 tokenId) public virtual override {
        address owner = ERC721.ownerOf(tokenId);
        require(to != owner, "ERC721: approval to current owner");

        require(_msgSender() == owner || isApprovedForAll(owner, _msgSender()),
            "ERC721: approve caller is not owner nor approved for all"
        );

        _approve(to, tokenId);
    }

    /**
     * @dev See {IERC721-getApproved}.
     */
    function getApproved(uint256 tokenId) public view virtual override returns (address) {
        require(_exists(tokenId), "ERC721: approved query for nonexistent token");

        return _tokenApprovals[tokenId];
    }

    /**
     * @dev See {IERC721-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved) public virtual override {
        require(operator != _msgSender(), "ERC721: approve to caller");

        _operatorApprovals[_msgSender()][operator] = approved;
        emit ApprovalForAll(_msgSender(), operator, approved);
    }

    /**
     * @dev See {IERC721-isApprovedForAll}.
     */
    function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    /**
     * @dev See {IERC721-transferFrom}.
     */
    function transferFrom(address from, address to, uint256 tokenId) public virtual override {
        //solhint-disable-next-line max-line-length
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");

        _transfer(from, to, tokenId);
    }

    ///**
    // * @dev See {IERC721-safeTransferFrom}.k

    /**
     * @dev See {IERC721-safeTransferFrom}
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override {
        safeTransferFrom(from, to, tokenId, "");
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public virtual override {
        _safeTransfer(from, to, tokenId, _data);
    }