// File: src/FishfoodFunder.sol



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

contract FishfoodFunder {
    error TooSoon();
    error TooDepleted();
    error TooMuch();
    error TooLitte();
    error TooRich();
    error TooExpensive();
    error TooBad();

    event FundingSent(address indexed recipient, uint256 amount);

    uint256 public upperRequestLimit;
    uint256 public lowerRequestLimit;
    uint256 public balanceLimit;
    uint256 public adminCount;
    uint256 public gasLimit;

    struct FishfoodFundingStatus {
        bool authorized;
        bool isAdmin;
        uint256 timestampOfLastCall;
    }

    mapping(address fishfoodAddress => FishfoodFundingStatus status) public
        fishFoodFundingStatus;

    constructor() {
        upperRequestLimit = 0.3 ether;
        lowerRequestLimit = 0.05 ether;
        balanceLimit = 0.15 ether;
        gasLimit = 30 gwei;
        fishFoodFundingStatus[msg.sender].isAdmin = true;
        adminCount = 1;
    }

    function getFunding(uint256 amountRequested) external {
        // Only permit authorized addresses to receive fishfood funding.
        if (!fishFoodFundingStatus[msg.sender].authorized) {
            revert TooBad();
        }

        // Revert with a useful message if the contract is broke.
        if (amountRequested > address(this).balance) {
            revert TooDepleted();
        }

        // Don't be greedy.
        if (amountRequested > upperRequestLimit) {
            revert TooMuch();
        }

        // Don't get confused by wei vs ETH.
        if (amountRequested < lowerRequestLimit) {
            revert TooLitte();
        }

        // Don't be out here spending 10 bucks to request 11.
        if (tx.gasprice > gasLimit) {
            revert TooExpensive();
        }

        // Don't ask for money if you already have a lot.
        if (msg.sender.balance > balanceLimit) {
            revert TooRich();
        }

        // Only permit one request every week.
        if (
            fishFoodFundingStatus[msg.sender].timestampOfLastCall + 7 days
                > block.timestamp
        ) {
            revert TooSoon();
        }

        // Update the last request block timestamp.
        fishFoodFundingStatus[msg.sender].timestampOfLastCall = block.timestamp;

        emit FundingSent(msg.sender, amountRequested);

        // If all the checks pass, send the funds.
        bool sent = payable(msg.sender).send(amountRequested);
        require(sent, "Failed to send");
    }

    function setUpperRequestLimit(uint256 newLimit) external {
        if (!fishFoodFundingStatus[msg.sender].isAdmin) {
            revert TooBad();
        }

        upperRequestLimit = newLimit;
    }

    function setLowerRequestLimit(uint256 newLimit) external {
        if (!fishFoodFundingStatus[msg.sender].isAdmin) {
            revert TooBad();
        }

        lowerRequestLimit = newLimit;
    }

    function setBalanceLimit(uint256 newLimit) external {
        if (!fishFoodFundingStatus[msg.sender].isAdmin) {
            revert TooBad();
        }

        balanceLimit = newLimit;
    }

    function setGasLimit(uint256 newLimit) external {
        if (!fishFoodFundingStatus[msg.sender].isAdmin) {
            revert TooBad();
        }

        gasLimit = newLimit;
    }

    function authorizeAddress(address fishfoodAddress) external {
        if (!fishFoodFundingStatus[msg.sender].isAdmin) {
            revert TooBad();
        }

        fishFoodFundingStatus[fishfoodAddress].authorized = true;
    }

    function batchAuthorizeAddresses(address[] memory fishfoodAddresses)
        external
    {
        if (!fishFoodFundingStatus[msg.sender].isAdmin) {
            revert TooBad();
        }

        for (uint256 i = 0; i < fishfoodAddresses.length; ++i) {
            fishFoodFundingStatus[fishfoodAddresses[i]].authorized = true;
        }
    }

    function deauthorizeAddress(address fishfoodAddress) external {
        if (!fishFoodFundingStatus[msg.sender].isAdmin) {
            revert TooBad();
        }

        fishFoodFundingStatus[fishfoodAddress].authorized = false;
    }

    function batchDeauthorizeAddresses(address[] memory fishfoodAddresses)
        external
    {
        if (!fishFoodFundingStatus[msg.sender].isAdmin) {
            revert TooBad();
        }

        for (uint256 i = 0; i < fishfoodAddresses.length; ++i) {
            fishFoodFundingStatus[fishfoodAddresses[i]].authorized = false;
        }
    }

    function addAdmin(address newAdmin) external {
        if (!fishFoodFundingStatus[msg.sender].isAdmin) {
            revert TooBad();
        }

        fishFoodFundingStatus[newAdmin].isAdmin = true;
        ++adminCount;
    }

    function removeAdmin(address newAdmin) external {
        if (
            !fishFoodFundingStatus[msg.sender].isAdmin
            // Don't allow the last admin to be removed if there are funds
            // in the contract.
            || (adminCount == 1 && address(this).balance > 0)
        ) {
            revert TooBad();
        }

        fishFoodFundingStatus[newAdmin].isAdmin = false;
        --adminCount;
    }

    function isAuthorized(address fishfoodAddress)
        external
        view
        returns (bool)
    {
        return fishFoodFundingStatus[fishfoodAddress].authorized;
    }

    function lastRequestTimestamp(address fishfoodAddress)
        external
        view
        returns (uint256)
    {
        return fishFoodFundingStatus[fishfoodAddress].timestampOfLastCall;
    }

    function withdraw() external {
        if (!fishFoodFundingStatus[msg.sender].isAdmin) {
            revert TooBad();
        }

        bool sent = payable(msg.sender).send(address(this).balance);
        require(sent, "Failed to send");
    }

    receive() external payable { }
}