// File: /contracts/PapaFeeding.sol



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

import '@openzeppelin/contracts/security/ReentrancyGuard.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol';
import './interfaces/IERC20.sol';

contract PapaFeeding is ReentrancyGuard, Ownable {
    address internal constant UNISWAP_ROUTER_ADDRESS = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
    address internal constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

    // addresses
    address private reserveAddress;
    address private poolAddress;

    // contract variables
    bool public isPaused = false;
    bool public swapClosed = false;
    bool public claimClosed = false;

    IERC20 public papa;
    IUniswapV2Router02 public uniswapV2Router;

    // Stake struct
    struct Stake {
        uint256 amount;
        uint256 lockPeriod;
        uint256 lockTime;
        uint256 multiplier;
        bool claimed;
    }

    // Lock struct
    struct Lock {
        uint256 lockPeriod;
        uint256 multiplier;
    }

    // Campaign struct
    struct Campaign {
        address tokenAddress;
        bool active;
        uint256 totalAmount;
        uint256 maxAmount;
    }

    // user => stakes
    mapping (address => Stake[]) public stakes;
    // campaign => locks
    mapping (uint256 => Lock[]) public campaignLocks;
    
    // Campaigns
    Campaign[] public campaigns;

    event Feed(address indexed user, uint256 campaignId, uint256 amountIn, uint256 amountOutWithReward, uint256 lockPeriod, uint256 multiplier);
    event Claim(address indexed user, uint256 amount);

    /**
    * @dev Constructor
    * @param _papa Address of the Papa token
    * @param _reserveAddress Address of the reserve
    */
    constructor(address _papa, address _reserveAddress, address _poolAddress) {
        papa = IERC20(_papa);
        reserveAddress = _reserveAddress;
        poolAddress = _poolAddress;
        uniswapV2Router = IUniswapV2Router02(UNISWAP_ROUTER_ADDRESS);
    }

    /**
    * @dev Modifier to check if the contract is paused
    */
    modifier notPaused() {
        require(!isPaused, "Contract is paused");
        _;
    }

    /**
    * @dev Modifier to check if the swap is open
    */
    modifier swapOpen() {
        require(!swapClosed, "Swap is closed");
        _;
    }

    /**
    * @dev Modifier to check if the claim is open
    */
    modifier claimOpen() {
        require(!claimClosed, "Claim is closed");
        _;
    }

    /**
    * @dev Add a campaign
    * @param tokenAddress Address of the token
    * @param amount Amount of the token
    * @param active Boolean
    * @param locks uint256[] array
    * @param multipliers uint256[] array
    */
    function addCampaign(
        address tokenAddress,
        uint256 amount,
        bool active,
        uint256[] calldata locks,
        uint256[] calldata multipliers
    ) external onlyOwner {
        require(amount > 0, "Amount must be greater than 0");
        require(locks.length == multipliers.length, "Locks and multipliers length mismatch");
        // transfer the token amount to the contract
        papa.transferFrom(poolAddress, address(this), amount);
        // approve the max token amount for the uniswap router
        IERC20(tokenAddress).approve(address(uniswapV2Router), 2**256 - 1);
        // add the campaign
        Campaign memory campaign = Campaign(tokenAddress, active, 0, amount);
        campaigns.push(campaign);
        // add the locks
        for (uint256 i = 0; i < locks.length; i++) {
            campaignLocks[campaigns.length - 1].push(Lock(locks[i], multipliers[i]));
        }
    }

    /**
    * @dev Update a campaign
    * @param campaignId Id of the campaign
    * @param active Boolean
    */
    function updateCampaign(uint256 campaignId, bool active) external onlyOwner {
        campaigns[campaignId].active = active;
    }

    function closeCampaign(uint256 campaignId) external onlyOwner {
        uint256 amountLeft = campaigns[campaignId].maxAmount - campaigns[campaignId].totalAmount;
        // transfer the amount left to the reserve
        papa.transfer(reserveAddress, amountLeft);
        // set the campaign to inactive
        campaigns[campaignId].active = false;
    }

    /**
    * @dev Set the reserve address
    * @param _reserveAddress Address of the reserve
    */
    function setReserveAddress(address _reserveAddress) external onlyOwner {
        reserveAddress = _reserveAddress;
    }

    /**
    * @dev Set the pool address
    * @param _poolAddress Address of the pool
    */
    function setPoolAddress(address _poolAddress) external onlyOwner {
        poolAddress = _poolAddress;
    }

    /**
    * @dev Set the isPaused variable
    * @param _isPaused Boolean
    */
    function setPause(bool _isPaused) external onlyOwner {
        isPaused = _isPaused;
    }

    /**
    * @dev Set the swapClosed variable
    * @param _swapClosed Boolean
     */
    function setSwapClosed(bool _swapClosed) external onlyOwner {
        swapClosed = _swapClosed;
    }

    /**
    * @dev Set the claimClosed variable
    * @param _claimClosed Boolean
    */
    function setClaimClosed(bool _claimClosed) external onlyOwner {
        claimClosed = _claimClosed;
    }

    /**
    * @dev Get the stakes of a user
    * @param _user Address of the user
    * @return Stake[] memory
    */
    function getStakes(address _user) external view returns (Stake[] memory) {
        return stakes[_user];
    }

    /**
    * @dev Swap ETH for Papa and stake the Papa amount
    * @param amountOutMin Minimum amount of Papa to receive
    * @param deadline Unix timestamp after which the transaction will revert
    */
    function swap(
        uint256 campaignId,
        uint256 lockId,
        uint256 amountIn,
        uint256 amountOutMin,
        uint256 deadline
    ) external nonReentrant notPaused swapOpen
    {
        Campaign memory campaign = campaigns[campaignId];
        // check if the campaign is active
        require(campaign.active, "Campaign is not active");
        require(amountIn > 0, "Amount must be greater than 0");
        require(lockId < campaignLocks[campaignId].length, "Lock id does not exist");
        // transfer the token amount from the user to this contract
        IERC20(campaign.tokenAddress).transferFrom(msg.sender, address(this), amountIn);

        // get the lock
        Lock memory lock = campaignLocks[campaignId][lockId];
        
        // swap path
        address[] memory path = new address[](3);
        path[0] = campaign.tokenAddress;
        path[1] = WETH_ADDRESS;
        path[2] = address(papa);

        // make the swap
        uint[] memory amounts = uniswapV2Router.swapExactTokensForTokens(
            amountIn, amountOutMin, path, reserveAddress, deadline
        );
        // get the amount of Papa received
        uint256 amountOut = amounts[amounts.length - 1];
        // calculate the amount with the reward
        uint256 amountOutWithReward = amountOut * lock.multiplier / 100;
        // check if the campaign max amount is reached
        require(campaign.totalAmount + amountOutWithReward <= campaign.maxAmount, "Campaign max amount reached");
        // update the campaign total amount
        campaigns[campaignId].totalAmount = campaign.totalAmount + amountOutWithReward;
        // stake the papa amount
        stakes[msg.sender].push(Stake(
            amountOutWithReward,
            lock.lockPeriod,
            block.timestamp,
            lock.multiplier,
            false
        ));
        // emit the event
        emit Feed(msg.sender, campaignId, amountIn, amountOutWithReward, lock.lockPeriod, lock.multiplier);
    }

    /**
    * @dev Claim the staked Papa amount
    * @param stakeId Stake ID
    */
    function claim(uint256 stakeId) external nonReentrant notPaused claimOpen {
        Stake memory stake = stakes[msg.sender][stakeId];
        require(!stake.claimed, "Stake already claimed");
        require(stake.lockTime + stake.lockPeriod <= block.timestamp, "Stake is still locked");
        stakes[msg.sender][stakeId].claimed = true;
        papa.transfer(msg.sender, stake.amount);
        emit Claim(msg.sender, block.timestamp);
    }

    /**
    * @dev Emergency function to withdraw the Papa amount
    */
    function emergency() external onlyOwner {
        papa.transfer(msg.sender, papa.balanceOf(address(this)));
    }

}

// File: /contracts/interfaces/IERC20.sol



pragma solidity ^0.8.0;

interface IERC20 {
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function transfer(address to, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
    function burn(uint256 amount) external;
}

// File: @uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol



pragma solidity >=0.6.2;

import './IUniswapV2Router01.sol';

interface IUniswapV2Router02 is IUniswapV2Router01 {
    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountETH);
    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountETH);

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable;
    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
}

// File: @uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol



pragma solidity >=0.6.2;

interface IUniswapV2Router01 {
    function factory() external pure returns (address);
    function WETH() external pure returns (address);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB, uint liquidity);
    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external payable returns (uint amountToken, uint amountETH, uint liquidity);
    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB);
    function removeLiquidityETH(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountToken, uint amountETH);
    function removeLiquidityWithPermit(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountA, uint amountB);
    function removeLiquidityETHWithPermit(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountToken, uint amountETH);
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    function swapTokensForExactTokens(
        uint amountOut,
        uint amountInMax,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        payable
        returns (uint[] memory amounts);
    function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
        external
        returns (uint[] memory amounts);
    function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        returns (uint[] memory amounts);
    function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
        external
        payable
        returns (uint[] memory amounts);

    function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
    function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
    function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
    function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (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: @openzeppelin/contracts/security/ReentrancyGuard.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}

// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @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() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        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 {
        _transferOwnership(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");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}