contract TimelockWallet is Ownable {
  /// @notice SafeERC20 library using for IERC20.
  using SafeERC20 for IERC20;

  /// @notice SafeMath library using for math.
  using SafeMath for uint256;

  uint256 public depositId;

  ///@notice struct for storing TokenLockedDetails
  struct TokenLockedDetails {
    address tokenAddress;
    uint256 startTime;
    uint256 endTime;
    uint256 lockedAmount;
    bool isWithdraw;
  }

  /// @notice tokens mapping to store nTokens Locked.
  mapping(uint256 => TokenLockedDetails) public lockingDetails;

  /// @notice event TokenLocked emit on every token locking.
  event TokenLocked(
    address indexed tokenAddress,
    uint256 indexed startTime,
    uint256 indexed endTime,
    uint256 depositId,
    uint256 lockedTokens
  );

  /// @notice event TokenUnLocked emit on every token locking.
  event TokenUnLocked(
    address indexed tokenAddress,
    uint256 indexed depositId,
    uint256 unLockedTime,
    uint256 unlockedTokens
  );

  /// @notice event Withdraw emit on every withdraw.
  event Withdraw(address tokenAddress, uint256 time);

  /**
   * @notice lock token function to handle token locking.
   * @notice revert on if token address is zero.
   * @notice revert on if noOfToken is not gt zero.
   * @notice please approve first than call otherwise it will be failed.
   * @param token ERC20 token.
   * @param noOfTokens number of Tokens to locked.
   */

  function lockToken(
    IERC20 token,
    uint256 noOfTokens,
    uint256 lockingPeriod
  ) external onlyOwner {
    address tokenAddress = address(token);
    require(tokenAddress != address(0), "TimelockWallet: invalid address");
    require(noOfTokens > 0, "TimelockWallet: invalid amount");
    require(lockingPeriod > 0, "TimelockWallet: invalid locking period");

    lockingDetails[depositId] = TokenLockedDetails({
      tokenAddress: tokenAddress,
      startTime: getNow(),
      endTime: getNow().add(lockingPeriod),
      lockedAmount: noOfTokens,
      isWithdraw: false
    });

    // make sure approve first
    token.safeTransferFrom(_msgSender(), address(this), noOfTokens);
    emit TokenLocked(
      tokenAddress,
      getNow(),
      getNow().add(lockingPeriod),
      depositId,
      noOfTokens
    );
    depositId++;
  }

  function unLockToken(uint256 _depositId) external onlyOwner {
    TokenLockedDetails storage lockTokens = lockingDetails[_depositId];

    require(_depositId < depositId, "TimelockWallet: unknown deposit id");
    require(getNow() > lockTokens.endTime, "TimelockWallet: tokens locked");
    require(lockTokens.isWithdraw == false, "TimelockWallet: already withdrew");
    // change the status
    lockTokens.isWithdraw = true;

    require(
      IERC20(lockTokens.tokenAddress).balanceOf(address(this)) >=
        lockTokens.lockedAmount,
      "TimelockWallet: Insufficient Balance"
    );

    IERC20(lockTokens.tokenAddress).safeTransfer(
      _msgSender(),
      lockTokens.lockedAmount
    );
    emit TokenUnLocked(
      lockTokens.tokenAddress,
      depositId,
      getNow(),
      lockTokens.lockedAmount
    );
  }

  /**
   * @notice safeWithdraw for handling admin withdrawals.
   * @notice revert on if token address is zero.
   * @notice revert on if there is no withdrawble token.
   * @param token ERC20 token.
   */

  function safeWithdraw(IERC20 token, uint256 nTokens) external onlyOwner {
    address tokenAddress = address(token);

    require(tokenAddress != address(0), "TimelockWallet: invalid address");
    require(nTokens > 0, "TimelockWallet: tokens not withdrawble");

    require(
      IERC20(tokenAddress).balanceOf(address(this)) >= nTokens,
      "TimelockWallet: Insufficient Balance"
    );

    token.safeTransfer(_msgSender(), nTokens);
    emit Withdraw(address(token), getNow());
  }

  /**
   * @notice getNow function for getting block timestamp.
   * @return uint256 -> block.timestamp
   */

  function getNow() internal view returns (uint256) {
    return block.timestamp;
  }
}
function getNow() internal view returns (uint256) {
  return block.timestamp;
}

/**
 * @notice getNow function for getting block timestamp.
 * @return uint256 -> block.timestamp
 */