// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.9;

import {Rescuable} from "../utils/Rescuable.sol";
import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol";
import {ERC721, ERC721TokenReceiver} from "@solmate/tokens/ERC721.sol";
import {ERC1155TokenReceiver} from "@solmate/tokens/ERC1155.sol";
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";

import {TokenIdentifierLibrary, TokenIdentifier} from "./lib/TokenIdentifier.sol";
import {CrossPostAccount} from "./CrossPostAccount.sol";
import {IOmniAccount} from "./interfaces/IOmniAccount.sol";
import {TokenDelegate} from "./TokenDelegate.sol";

contract OmniAccount is IOmniAccount, CrossPostAccount, Rescuable, ERC721TokenReceiver, ERC1155TokenReceiver {
  TokenDelegate immutable tokenDelegate;

  // Maps TokenIdentifierHash to owner address for recieved nfts
  mapping(bytes32 => address) public owners;

  constructor(address oracleSigner, TokenDelegate tokenDelegate_) {
    setOmniOracleSigner(oracleSigner);
    tokenDelegate = tokenDelegate_;
    emit AccountCreated(address(this), msg.sender);
  }

  function createOrder(TokenIdentifier[] calldata tokens) external {
    for (uint256 i; i < tokens.length; ) {
      TokenIdentifier calldata token = tokens[i];

      IERC721 collection = IERC721(token.collection);

      tokenDelegate.spendFrom(collection, msg.sender, address(this), token.id);

      owners[TokenIdentifierLibrary.hash(token)] = msg.sender;

      unchecked {
        ++i;
      }
    }

    emit OrdersCreated(address(this), msg.sender, tokens);
  }

  function withdraw(TokenIdentifier[] calldata tokens) external {
    for (uint256 i; i < tokens.length; ) {
      if (owners[TokenIdentifierLibrary.hash(tokens[i])] != msg.sender) {
        revert Unauthorized();
      }

      ERC721 collection = ERC721(tokens[i].collection);
      collection.safeTransferFrom(address(this), msg.sender, tokens[i].id);
      delete owners[TokenIdentifierLibrary.hash(tokens[i])];

      unchecked {
        ++i;
      }
    }

    emit OrdersWithdrawn(address(this), msg.sender, tokens);
  }

  function setOwnersForItems(TokenIdentifier[] calldata tokens, address[] calldata ownerArray) external onlyOwner {
    if (tokens.length != ownerArray.length) {
      revert MismatchedInputLength();
    }

    for (uint256 i; i < tokens.length; ) {
      if (owners[TokenIdentifierLibrary.hash(tokens[i])] != address(0)) {
        revert Unauthorized();
      }

      ERC721 collection = ERC721(tokens[i].collection);

      if (collection.ownerOf(tokens[i].id) != address(this)) {
        revert InvalidOrder();
      }

      owners[TokenIdentifierLibrary.hash(tokens[i])] = ownerArray[i];
      unchecked {
        ++i;
      }
    }
  }

  function onERC721Received(
    address operator,
    address from,
    uint256 tokenId,
    bytes calldata data
  ) external virtual override returns (bytes4) {
    ERC721 collection = ERC721(msg.sender);
    if (collection.supportsInterface(type(IERC721).interfaceId)) {
      owners[TokenIdentifierLibrary.hash(TokenIdentifier({collection: address(collection), id: tokenId}))] = from;
    }
    return ERC721TokenReceiver.onERC721Received.selector;
  }

  function setPause(bool shouldPause) external onlyOwner {
    if (shouldPause) {
      _pause();
    } else {
      _unpause();
    }
  }

  function withdrawERC721(ERC721 token, uint256[] calldata ids, address recipient) external override onlyOwner {
    for (uint256 i; i < ids.length; ) {
      delete owners[TokenIdentifierLibrary.hash(TokenIdentifier({collection: address(token), id: ids[i]}))];

      token.safeTransferFrom(address(this), recipient, ids[i]);
      unchecked {
        ++i;
      }
    }
  }

  receive() external payable {}

  fallback() external payable {}
}
[{"inputs":[{"internalType":"address","name":"oracleSigner","type":"address"},{"internalType":"contract TokenDelegate","name":"tokenDelegate_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"InvalidMarketplaceOperator","type":"error"},{"inputs":[],"name":"InvalidNetwork","type":"error"},{"inputs":[],"name":"InvalidOrder","type":"error"},{"inputs":[],"name":"MismatchedInputLength","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"AccountCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"components":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"indexed":false,"internalType":"struct TokenIdentifier[]","name":"tokens","type":"tuple[]"}],"name":"OrdersCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"components":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"indexed":false,"internalType":"struct TokenIdentifier[]","name":"tokens","type":"tuple[]"}],"name":"OrdersWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"OMNI_ORACLE_SIGNER","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"internalType":"struct TokenIdentifier[]","name":"tokens","type":"tuple[]"}],"name":"createOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"isValidSignature","outputs":[{"internalType":"bytes4","name":"magicValue","type":"bytes4"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155BatchReceived","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"owners","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"marketplaceOperator","type":"address"},{"internalType":"address","name":"collection","type":"address"}],"internalType":"struct CrossPostAccount.MarketplaceApprovals[]","name":"collectionsToApprove","type":"tuple[]"},{"internalType":"bool","name":"approve","type":"bool"}],"name":"setApprovalForMarketplaces","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newSigner","type":"address"}],"name":"setOmniOracleSigner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"internalType":"struct TokenIdentifier[]","name":"tokens","type":"tuple[]"},{"internalType":"address[]","name":"ownerArray","type":"address[]"}],"name":"setOwnersForItems","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"shouldPause","type":"bool"}],"name":"setPause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"internalType":"struct TokenIdentifier[]","name":"tokens","type":"tuple[]"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC1155","name":"token","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"address","name":"recipient","type":"address"}],"name":"withdrawERC1155","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"withdrawERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC721","name":"token","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"address","name":"recipient","type":"address"}],"name":"withdrawERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"withdrawETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]