pragma solidity 0.8.20; //Do not change the solidity version as it negatively impacts submission grading
// SPDX-License-Identifier: MIT

import "@openzeppelin/contracts/access/Ownable.sol";
import "./YourToken.sol";

contract Vendor is Ownable {
    /////////////////
    /// Errors //////
    /////////////////

    // Errors go here...
    error InvalidEthAmount();
    error InsufficientVendorTokenBalance(uint256 available, uint256 required);
    error EthTransferFailed(address to, uint256 amount);
    error InvalidTokenAmount();
    error InsufficientVendorEthBalance(uint256 available, uint256 required);

    //////////////////////
    /// State Variables //
    //////////////////////

    YourToken public immutable yourToken;
    uint256 public constant tokensPerEth = 100;

    ////////////////
    /// Events /////
    ////////////////

    // Events go here...
    event BuyTokens(address indexed buyer, uint256 amountOfETH, uint256 amountOfTokens);
    event SellTokens(address indexed seller, uint256 amountOfTokens, uint256 amountOfETH);

    ///////////////////
    /// Constructor ///
    ///////////////////

    constructor(address tokenAddress) Ownable(msg.sender) {
        yourToken = YourToken(tokenAddress);
    }

    ///////////////////
    /// Functions /////
    ///////////////////

    function buyTokens() external payable {
      if (msg.value == 0) revert InvalidEthAmount();
      uint256 receivedToken = msg.value * tokensPerEth;
      uint256 availableToken = yourToken.balanceOf(address(this));
      if (availableToken < receivedToken) revert InsufficientVendorTokenBalance(availableToken, receivedToken);
      yourToken.transfer(msg.sender, receivedToken);
      emit BuyTokens(msg.sender, msg.value, receivedToken);
    }

    function withdraw() public onlyOwner {
      address ownerAddr = payable(owner());
      (bool sent,) = ownerAddr.call{value: address(this).balance}("");     
      if (!sent) revert EthTransferFailed(ownerAddr, address(this).balance);
    }

    function sellTokens(uint256 amount) public {
      if (amount == 0) revert InvalidTokenAmount();
      yourToken.transferFrom(msg.sender, address(this), amount);
      uint256 receivedEth = amount / tokensPerEth;
      uint256 contractEth = address(this).balance;
      if (contractEth < receivedEth) revert InsufficientVendorEthBalance(contractEth, receivedEth);
      (bool sent,) = payable(msg.sender).call{value: receivedEth}("");
      if (!sent) revert EthTransferFailed(msg.sender, receivedEth);
      emit SellTokens(msg.sender, amount, receivedEth);
    }
}
