contract IgorsManifest is ERC721, ReentrancyGuard {
    address public contractOwner;
    uint256 public snipeStep;
    uint256 public snipePrice;
    bool public snipeable;
    uint256 public blockTimerStartTime;
    string igorsHash;
    
    event Snipe(address indexed owner, uint256 price, uint256 step, uint256 blockTimestamp);
    event ExtendLeasehold(uint256 blockTimestamp);
    event DecreaseSnipeStep(uint256 price, uint256 step, uint256 blockTimestamp);

    constructor() {
        contractOwner = msg.sender;
        _safeMint(msg.sender, 1, ""); //Igor is minted as token ID number 1, and the contractOwner owns it initially.
        snipeStep = 0;
        snipePrice = snipePriceTable(snipeStep);
        snipeable = true;
        blockTimerStartTime = block.timestamp;
        igorsHash = "QmSGsx5Cs1zLxmMX8YjvGx1x1vYn47jzuFKy13yhM4S61q";
    }

    //This is a lookup table to determine the cost to snipe Igor from someone.
    function snipePriceTable(uint256 _snipeStep) internal pure returns(uint256 _snipePrice) {
        if (_snipeStep == 0) return 0.1 * 10 ** 18;
        else if (_snipeStep == 1) return 0.15 * 10 ** 18;
        else if (_snipeStep == 2) return 0.21 * 10 ** 18;
        else if (_snipeStep == 3) return 0.28 * 10 ** 18;
        else if (_snipeStep == 4) return 0.36 * 10 ** 18;
        else if (_snipeStep == 5) return 0.45 * 10 ** 18;
        else if (_snipeStep == 6) return 0.55 * 10 ** 18;
        else if (_snipeStep == 7) return 0.66 * 10 ** 18;
        else if (_snipeStep == 8) return 0.78 * 10 ** 18;
        else if (_snipeStep == 9) return 1 * 10 ** 18;
        else if (_snipeStep == 10) return 1.5 * 10 ** 18;
        else if (_snipeStep == 11) return 2.2 * 10 ** 18;
        else if (_snipeStep == 12) return 3 * 10 ** 18;
        else if (_snipeStep == 13) return 4 * 10 ** 18;
        else if (_snipeStep == 14) return 6 * 10 ** 18;
        else if (_snipeStep == 15) return 8.5 * 10 ** 18;
        else if (_snipeStep == 16) return 12 * 10 ** 18;
        else if (_snipeStep == 17) return 17 * 10 ** 18;
        else if (_snipeStep == 18) return 25 * 10 ** 18;
        else if (_snipeStep == 19) return 35 * 10 ** 18;
        else if (_snipeStep == 20) return 47 * 10 ** 18;
        else if (_snipeStep == 21) return 60 * 10 ** 18;
        else if (_snipeStep == 22) return 75 * 10 ** 18;
        else if (_snipeStep == 23) return 92 * 10 ** 18;
        else if (_snipeStep == 24) return 110 * 10 ** 18;
        else if (_snipeStep == 25) return 130 * 10 ** 18;
        else if (_snipeStep == 26) return 160 * 10 ** 18;
        else if (_snipeStep == 27) return 200 * 10 ** 18;
        else if (_snipeStep == 28) return 250 * 10 ** 18;
        else if (_snipeStep == 29) return 310 * 10 ** 18;
        else if (_snipeStep == 30) return 380 * 10 ** 18;
        else if (_snipeStep == 31) return 460 * 10 ** 18;
        else if (_snipeStep == 32) return 550 * 10 ** 18;
        else if (_snipeStep == 33) return 650 * 10 ** 18;
        else if (_snipeStep == 34) return 760 * 10 ** 18;
    }

    modifier onlyIgorOwner() {
        require(msg.sender == ownerOf(1), "Sender is not the owner of Igor.");
        _;
    }

    function snipeIgor() external payable nonReentrant {
        require(msg.sender != ownerOf(1), "You cannot snipe Igor from the address that already owns him.");
        require(msg.value == snipePrice, "The amount sent did not match the current snipePrice.");
        require(snipeable == true, "Sniping is permanently disabled. Igor is owned as a freehold now.");    
        address tokenOwner = ownerOf(1);
        //If the snipeStep is 0, then all proceeds go to the owner.
        if (snipeStep == 0) {
            snipeStep++;
            snipePrice = snipePriceTable(snipeStep);
            snipe(tokenOwner, msg.sender, 1);
            (bool sent,) = payable(tokenOwner).call{ value: msg.value }("");
            require(sent, "Failed to send Ether.");
        } else {
            //Else is all cases after Step 0.
             uint256 etherCatsRoyalty = (msg.value - snipePriceTable(snipeStep - 1)) * 25 / 100;
             uint256 payment = msg.value - etherCatsRoyalty;
            if (snipeStep < 34) {
                snipeStep++;
                snipePrice = snipePriceTable(snipeStep);
            }
            //Automatically stop sniping if Igor is sniped on Step 33.
            if (snipeStep == 34) {
                snipeable = false;
            }
            snipe(tokenOwner, msg.sender, 1);
            (bool paymentSent,) = payable(tokenOwner).call{ value: payment }("");
            require(paymentSent, "Failed to send Ether.");
            
            (bool royaltySent,) = payable(contractOwner).call{ value: etherCatsRoyalty }("");
            require(royaltySent, "Failed to send Ether.");
        }
        
        blockTimerStartTime = block.timestamp;
        emit Snipe(msg.sender, snipePrice, snipeStep, blockTimerStartTime);
    }

    //This option disables sniping permanently. It will behave like a normal ERC721 after this function is triggered. It can only be called by Igor's owner. 
    function permanentlyStopSniping() external payable onlyIgorOwner {
        require(snipeStep <= 20, "Igor can only be bought out on snipe steps before Step 21.");
        require(msg.value == 141 * 10 ** 18, "The amount sent did not match the freehold option amount.");
        require(snipeable == true, "Cannot disable sniping twice. Igor is already not snipeable.");
        snipeable = false;
        (bool sent,) = payable(contractOwner).call{ value: msg.value }("");
        require(sent, "Failed to send Ether.");
    }
    
    //To prevent people from reducing the snipe step, you must pay 1/1000th (0.1%) tax to the creator every two weeks. Activating this function early or multiple times does not result in time accumulating. It will always reset the countdown clock back to 1209600 seconds (two weeks).
    function extendLeasehold() external payable onlyIgorOwner {
        require(snipeStep >= 1, "You cannot extend the leasehold timer when it is step zero.");
        require(msg.value == snipePriceTable(snipeStep-1) / 1000, "The price to extend is 1/1000th of the current value, or the snipe step minus 1");
        require(snipeable == true, "Cannot extend. Igor is not snipeable anymore, sorry.");
        blockTimerStartTime = block.timestamp;
        (bool sent,) = payable(contractOwner).call{ value: msg.value }("");
        require(sent, "Failed to send Ether.");
        
        emit ExtendLeasehold(blockTimerStartTime);
    }

    //If the owner after 1209600 seconds (two weeks) has not paid the extension tax, then anyone can reduce the snipeStep by 1.
    function decreaseSnipeStep() external {
        require(block.timestamp - blockTimerStartTime > 1209600, "You cannot reduce the snipe step until after the lease is up.");
        require(snipeStep >= 1, "You cannot reduce the snipe step when it is at zero.");
        require(snipeStep < 34, "You cannot reduce the snipe step once it reaches step 34.");
        require(snipeable == true, "Igor is not snipeable anymore, sorry.");
        snipeStep--;
        snipePrice = snipePriceTable(snipeStep);
        blockTimerStartTime = block.timestamp;
        
        emit DecreaseSnipeStep(snipePrice, snipeStep, blockTimerStartTime);
    }
    
    //This function is for site owners to vet that Igor is only shown by the Ethereum address that owns him. This is conceptual, and only facilitates the possibility of restricting PFP NFTs from being displayed by accounts that do not own the NFT. It is up to a site to implement such a process on its own. It is not mandated by the terms of this contract. Reputable sites concerned about security will never store or even ask you to create a password. Reputable sites will require Web3 authentication for logins. Adding PFP avatar restrictions is trivial after adopting Web3 for authentication.
    function pfpOwner() external view returns (address igorsOwner, string memory igorsIPFSHash){
        return (ownerOf(1), igorsHash);
    }

    //There's only one Igor. Some frontends may look for this optional ERC721 implementation.
    function totalSupply() public pure returns (uint256) {
        return 1;
    }
}
//Igor's Manifest is an EtherCats production. Igor was illustrated and animated by Nadia Khuzina. The principle concept was developed by Woody Deck. The contract was written by Woody Deck and Edvard Gzraryan. Special thanks to Vitalik Buterin for his inspiration in challenging economic dogma.
function extendLeasehold() external payable onlyIgorOwner {
    require(snipeStep >= 1, "You cannot extend the leasehold timer when it is step zero.");
    require(msg.value == snipePriceTable(snipeStep-1) / 1000, "The price to extend is 1/1000th of the current value, or the snipe step minus 1");
    require(snipeable == true, "Cannot extend. Igor is not snipeable anymore, sorry.");
    blockTimerStartTime = block.timestamp;
    (bool sent,) = payable(contractOwner).call{ value: msg.value }("");
    require(sent, "Failed to send Ether.");
        
    emit ExtendLeasehold(blockTimerStartTime);
}

//To prevent people from reducing the snipe step, you must pay 1/1000th (0.1%) tax to the creator every two weeks. Activating this function early or multiple times does not result in time accumulating. It will always reset the countdown clock back to 1209600 seconds (two weeks).